覆盖 JavaScript 对象中的调用

huangapple go评论68阅读模式
英文:

Overwrite call in javascript object

问题

以下是您提供的代码的翻译部分:

class Z {
  constructor() {}
  a() {
    console.log('a');
  }
  get b() {
    console.log('b');
  }
  c() {
    console.log('c');
  }
}

class B {
  constructor(z) {
    this.z = z;
  }
}

let z = new Z();
let b = new B(z);
b.a(); // 错误
b.b; // 错误

B 类旨在作为方法混合和变量捆绑,但出于某种原因,我无法将 Z 扩展到 B。当我调用一个方法或检索 z 上的属性时,我能够检查是否可以通过 this.z 访问它,如果可以,直接返回它吗?

对于如何编写这个结构的任何想法将不胜感激。


我不能直接使用 extend 关键字的原因是,Z 由一个库提供,并且实例是在可调用或类的静态方法中构造的。我对“函数类构造函数”之类的东西一无所知,例如 _super__proto__

我之所以考虑这一点,是因为你可以在 Python 中定义 __call____getitem__ 来实现这一目的。我不确定这是否可能,如果你能帮助我,我将非常高兴。类似以下功能:

class Z {
  a() {
    try {
      return this.z.a();
    } catch (error) {}
  }
}

但会适用于任何检索尝试。


感谢所有评论中的建议。
请注意以下不是最小工作示例,而是真实场景。


/*库源代码*/
function fromTextArea(textarea, options) {
  options = options ? copyObj(options) : {};
  options.value = textarea.value;
  if (!options.tabindex && textarea.tabIndex) { options.tabindex = textarea.tabIndex; }
  if (!options.placeholder && textarea.placeholder) { options.placeholder = textarea.placeholder; }
  // 如果该文本区域聚焦或具有autofocus且没有其他元素聚焦,则将autofocus设置为true。
  if (options.autofocus == null) {
    var hasFocus = activeElt();
    options.autofocus = hasFocus == textarea ||
      textarea.getAttribute("autofocus") != null && hasFocus == document.body;
  }

  function save() { textarea.value = cm.getValue(); }

  var realSubmit;
  if (textarea.form) {
    on(textarea.form, "submit", save);
    // 使submit方法执行正确的可耻的hack。
    if (!options.leaveSubmitMethodAlone) {
      var form = textarea.form;
      realSubmit = form.submit;
      try {
        var wrappedSubmit = form.submit = function () {
          save();
          form.submit = realSubmit;
          form.submit();
          form.submit = wrappedSubmit;
        };
      } catch (e) { }
    }
  }

  options.finishInit = function (cm) {
    cm.save = save;
    cm.getTextArea = function () { return textarea; };
    cm.toTextArea = function () {
      cm.toTextArea = isNaN; // 防止运行两次
      save();
      textarea.parentNode.removeChild(cm.getWrapperElement());
      textarea.style.display = "";
      if (textarea.form) {
        off(textarea.form, "submit", save);
        if (!options.leaveSubmitMethodAlone && typeof textarea.form.submit == "function") { textarea.form.submit = realSubmit; }
      }
    };
  };

  textarea.style.display = "none";
  var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); },
    options);
  return cm
}

// 我正在处理的应用程序的一部分
class CodeEditor {
  constructor(
    container,
    generic,
    types,
    modes,
    options,
  ) {
    this.generic = generic;
    this.types = types;
    this.modes = modes;

    /*
    .code-container
      .cm-header-bar
        .cm-code-compile-wrapper
        button.dropdown-button
      textarea
    */
    this.container = container;
    this.textarea = container.querySelector('textarea');
    this.header = this.container.querySelector('.cm-header-bar');
    this.compileBtnWrapper = this.header.querySelector('.cm-code-compile-wrapper');
    this.compileBtn = document.createElement('div');
    this.compileBtn.classList.add('cm-code-compile');
    this.compileBtn.innerText = 'compile';
    this.options = options;

    this.mode = this.textarea.getAttribute('id');
    this.options.mode.name = this.mode;
    this.editor = CodeMirror.fromTextArea(this.textarea, this.options);
    // 编辑器设置
    this.editor.on("gutterClick", function (cm, n) {
      let info = cm.lineInfo(n);
      cm.setGutterMarker(n, "breakpoints", info.gutterMarkers ? null : makeMarker());
    });
    // 可编译
    if (this.mode !== this.generic) this.compilable();
    this.dropdown = dropdown(this.header.querySelector('.dropdown-button'), '预先处理', generic);
    Object.keys(this.modes).forEach(mode => {
      this.dropdown.addOption(mode, () => {
        htmlEditor.setOption('mode', { name: this.name, globalVars: true });
        this.mode = mode;
        this.editor.refresh();
        play();
        if (mode !== this.generic) this.compilable();
      }, mode === this.mode);
    });
  }

  get name() {
    return this.types[this.mode];
  }

  compilable() {
    this.compileBtnWrapper.appendChild(this.compileBtn);
    let temp = {};
    const oxidize = () => {
      this.compileBtn.onclick = () => {
        temp.code = this.getValue();
        temp.mode = this.mode;
        // 编译
        this.dropdown.onOption(this.generic);
        this.setValue(this.modes[this.mode]());
        this.options.mode.name = this.types[this.generic];
        this.setOption('mode', this.options.mode);
        this.compileBtn.classList.add('active');
        this.compileBtn.innerText = 'restore';
        // 撤销
        reduce();
      }
    }
    const reduce = () => {
      this.compileBtn.onclick = () => {
        this.dropdown.onOption(temp.mode);
        this.setValue(temp.code);
        this.options.mode.name = temp.mode;
        this.setOption('mode', this.types[temp.mode]);
        this.compileBtn.classList.remove('active');
        this.compileBtn.innerText = 'compile';
        // 撤销
        oxidize();
      }
    }
    oxidize();
  }

  /* 
  我正在优化一个大型项目实例以前是一个


<details>
<summary>英文:</summary>

I am a newbie in JavaScript, trying to extend class variable prototype without inheritance

```js

class Z{
	constructor(){}
	a(){console.log(&#39;a&#39;)}
	get b(){console.log(&#39;b&#39;)}
	c(){console.log(&#39;c&#39;)}
}

class B{
	constructor(z){
		this.z = z;
	}
}

let z = new Z();
let b = new B(z);
b.a(); // error
b.b; // error

the B class is intended to be just a method mixin and variable bundle, however for some reason, I cannot extend Z to B. When I calling a method, or retrieve a prop on z, can I inspect whether it can be accessed through this.z, if so, return it directly?

Any ideas of how to write this structure will be more than appreciated.


The reason I cannot use extend keyword directly, is that Z is provided by a lib, and the instance is constructed in a callable, or static on class. I am not familiar with function class constructor at all, things such as _super or __proto__.

And why I thought about this is that, you can define a dunder __call__ or __getitem__ in python to serve this purpose. I am not sure if it is possible at all, I will be so glad if you can give me a hand. Something functional like:

class Z{
  a()
    try{
      return this.z.a()
    }catch(){/}
  }
}

But would apply for any retrieving attempt.


Thanks all comments for suggestion.
Note following are not minimum working example but real life occasion.


/* lib source code */
function fromTextArea(textarea, options) {
  options = options ? copyObj(options) : {};
  options.value = textarea.value;
  if (!options.tabindex &amp;&amp; textarea.tabIndex) { options.tabindex = textarea.tabIndex; }
  if (!options.placeholder &amp;&amp; textarea.placeholder) { options.placeholder = textarea.placeholder; }
  // Set autofocus to true if this textarea is focused, or if it has
  // autofocus and no other element is focused.
  if (options.autofocus == null) {
    var hasFocus = activeElt();
    options.autofocus = hasFocus == textarea ||
      textarea.getAttribute(&quot;autofocus&quot;) != null &amp;&amp; hasFocus == document.body;
  }

  function save() { textarea.value = cm.getValue(); }

  var realSubmit;
  if (textarea.form) {
    on(textarea.form, &quot;submit&quot;, save);
    // Deplorable hack to make the submit method do the right thing.
    if (!options.leaveSubmitMethodAlone) {
      var form = textarea.form;
      realSubmit = form.submit;
      try {
        var wrappedSubmit = form.submit = function () {
          save();
          form.submit = realSubmit;
          form.submit();
          form.submit = wrappedSubmit;
        };
      } catch (e) { }
    }
  }

  options.finishInit = function (cm) {
    cm.save = save;
    cm.getTextArea = function () { return textarea; };
    cm.toTextArea = function () {
      cm.toTextArea = isNaN; // Prevent this from being ran twice
      save();
      textarea.parentNode.removeChild(cm.getWrapperElement());
      textarea.style.display = &quot;&quot;;
      if (textarea.form) {
        off(textarea.form, &quot;submit&quot;, save);
        if (!options.leaveSubmitMethodAlone &amp;&amp; typeof textarea.form.submit == &quot;function&quot;) { textarea.form.submit = realSubmit; }
      }
    };
  };

  textarea.style.display = &quot;none&quot;;
  var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); },
    options);
  return cm
}

// a part of the application I am working on
class CodeEditor{
	constructor(
		container, 
		generic, 
		types, 
		modes, 
		options, 
	){
		this.generic = generic;
		this.types = types;
		this.modes = modes;

		/*
		.code-container
			.cm-header-bar
				.cm-code-compile-wrapper
				button.dropdown-button
			textarea
		*/
		this.container = container;
		this.textarea = container.querySelector(&#39;textarea&#39;);
		this.header = this.container.querySelector(&#39;.cm-header-bar&#39;);
		this.compileBtnWrapper = this.header.querySelector(&#39;.cm-code-compile-wrapper&#39;);
		this.compileBtn = document.createElement(&#39;div&#39;);
		this.compileBtn.classList.add(&#39;cm-code-compile&#39;);
		this.compileBtn.innerText = &#39;compile&#39;;
		this.options = options;

		this.mode = this.textarea.getAttribute(&#39;id&#39;);
		this.options.mode.name = this.mode;
		this.editor = CodeMirror.fromTextArea(this.textarea, this.options);
		// editor setup
		this.editor.on(&quot;gutterClick&quot;, function(cm, n) {
		  	let info = cm.lineInfo(n);
		  	cm.setGutterMarker(n, &quot;breakpoints&quot;, info.gutterMarkers ? null : makeMarker());
		});
		// compilable
		if(this.mode !== this.generic)this.compilable();
		this.dropdown = dropdown(this.header.querySelector(&#39;.dropdown-button&#39;), &#39;预先处理&#39;, generic);
		Object.keys(this.modes).forEach(mode=&gt;{
			this.dropdown.addOption(mode, ()=&gt;{
				htmlEditor.setOption(&#39;mode&#39;, { name: this.name, globalVars: true });
				this.mode = mode;
				this.editor.refresh();
				play();
				if(mode !== this.generic)this.compilable();
			}, mode === this.mode);
		});
	}

	get name(){
		return this.types[this.mode];
	}

	compilable(){
		this.compileBtnWrapper.appendChild(this.compileBtn);
		let temp = {};
		const oxidize = () =&gt; {
			this.compileBtn.onclick = () =&gt; {
				temp.code = this.getValue();
				temp.mode = this.mode ;
				// compile
				this.dropdown.onOption(this.generic);
				this.setValue(this.modes[this.mode]());
				this.options.mode.name = this.types[this.generic];
				this.setOption(&#39;mode&#39;, this.options.mode);
				this.compileBtn.classList.add(&#39;active&#39;);
				this.compileBtn.innerText = &#39;restore&#39;;
				// undo
				reduce();
			}
		}
		const reduce = () =&gt; {
			this.compileBtn.onclick = () =&gt; {
				this.dropdown.onOption(temp.mode);
				this.setValue(temp.code);
				this.options.mode.name = temp.mode;
				this.setOption(&#39;mode&#39;, this.types[temp.mode]);
				this.compileBtn.classList.remove(&#39;active&#39;);
				this.compileBtn.innerText = &#39;compile&#39;;
				// undo
				oxidize();
			}
		}
		oxidize();
	}

	/* 
	I am optimizing a big proj, the instance used to be a 
	CodeMirror. However, it produces a lot of redundant 
	self-occurring parts, and I do not want to pass parameters
	all day, therefore I wrapped it with a CodeEditor instance
	However, other code in this project would call the CodeMirror 
	prototype method, since I cannot find a way to extends 
	CodeMirror methods from this.editor to this, I need to 
	redefine them all (following are only a tiny part)
	*/
	setValue(v){return this.editor.setValue(v)}
	getValue(){return this.editor.getValue()}
	setOption(...args){return this.editor.setOption(...args)};
	focus(){return this.editor.focus()}

	// more functionality
	compiled(){return this.modes[this.mode]() || &#39;&#39;};
	raw(){return this.getValue().trimEnd()}
}

CodeMirror 5.63.3: https://codemirror.net/5/doc/releases.html

Sorry that I did not find a CDN that provides non-minimised code.

答案1

得分: 1

以下是翻译好的内容,不包括代码部分:

You could inject the Z prototype in the prototype chain like this:
你可以像这样将 Z 的原型注入原型链中:

Object.setPrototypeOf(B.prototype, Z.prototype);

This establishes this chain:
这将建立这个链:

b → B.prototype → Z.prototype → Object.prototype

If you need b to also have access to whatever instance properties have been set on z (so not on its proto object), then add one more step in the chain like this:
如果你需要 b 也能访问在 z 上设置的实例属性(而不是在其原型对象上),那么可以像这样在链中添加一步:

Object.setPrototypeOf(B.prototype, z);

This establishes this chain:
这将建立这个链:

b → B.prototype → z → Z.prototype → Object.prototype

In case b must be created as an instance of Z.prototype, and not of B.prototype, then you should actually not define the B class at all, but define a decorator function:
如果必须将 b 创建为 Z.prototype 的实例,而不是 B.prototype 的实例,那么实际上你不应该定义 B 类,而应该定义一个装饰器函数:

function decorateNewZ() {
    return Object.assign(new Z(), {
        // Any extra methods
        x() { console.log('x') }
    });
}
英文:

You could inject the Z prototype in the prototype chain like this:

Object.setPrototypeOf(B.prototype, Z.prototype);

This establishes this chain:

b → B.prototype → Z.prototype → Object.prototype

<!-- begin snippet: js hide: true console: true babel: false -->

<!-- language: lang-js -->

// Library code
class Z{
constructor() {}
a()     { console.log(&#39;a&#39;) }
get b() { console.log(&#39;b&#39;) }
c()     { console.log(&#39;c&#39;) }
}
const z = new Z();
// End library code
class B{
constructor() {}
x()     { console.log(&#39;x&#39;) }
}
Object.setPrototypeOf(B.prototype, Z.prototype);
let b = new B();
b.a();
b.b;
b.x(); 
z.test = 1;
console.log(b.test); // Undefined (see next snippet)

<!-- end snippet -->

If you need b to also have access to whatever instance properties have been set on z (so not on its proto object), then add one more step in the chain like this:

Object.setPrototypeOf(B.prototype, z);

This establishes this chain:

b → B.prototype → z → Z.prototype → Object.prototype

<!-- begin snippet: js hide: true console: true babel: false -->

<!-- language: lang-js -->

// Library code
class Z{
constructor() {}
a()     { console.log(&#39;a&#39;) }
get b() { console.log(&#39;b&#39;) }
c()     { console.log(&#39;c&#39;) }
}
const z = new Z();
// End library code
class B{
constructor() {}
x()     { console.log(&#39;x&#39;) }
}
Object.setPrototypeOf(B.prototype, z);
let b = new B();
b.a();
b.b;
b.x(); 
z.test = 1;
console.log(b.test); // 1

<!-- end snippet -->

In case b must be created as an instance of Z.prototype, and not of B.prototype, then you should actually not define the B class at all, but define a decorator function:

function decorateNewZ() {
return Object.assign(new Z(), {
// Any extra methods
x() { console.log(&#39;x&#39;) }
});
}

<!-- begin snippet: js hide: true console: true babel: false -->

<!-- language: lang-js -->

// Library code
class Z{
constructor() {}
a()     { console.log(&#39;a&#39;) }
get b() { console.log(&#39;b&#39;) }
c()     { console.log(&#39;c&#39;) }
}
const z = new Z();
// End library code
function decorateZ() {
return Object.assign(new Z(), {
// Any extra methods
x() { console.log(&#39;x&#39;) }
});
}
let b = decorateZ();
b.a();
b.b;
b.x(); 

<!-- end snippet -->

答案2

得分: 1

An entirely ES6-class based approach provides everything (all of the necessary prototype (re)wiring) for free.

The following implementation returns a class which extends the constructor of any provided type-instance (either of class' or of pure constructor functions or of built-ins). It also supports the naming of the returned class constructor.

function createExtendedClassFromInstanceType(
  className, instance
) {
  const { constructor: SuperClass } = instance;
  if ('function' === typeof SuperClass) {
    return ({
      [className]: class extends SuperClass {
        constructor(...args) {
          super(...args);
          // own implementation
        }
      }
    })[className];
  }
}

class NonAccessibleType {
  constructor() {
  }
  // all prototypal
  a() {
    console.log('`a()` logs from custom prototype');
  }
  get b() {
    console.log('`b` getter logs from custom prototype');
  }
  c () {
    console.log('`c()` logs from custom prototype');
  }
}
const customInstance = new NonAccessibleType;

const SubType = createExtendedClassFromInstanceType(
  'SubType', customInstance
);
let subType = new SubType;

console.log('invoking `subType.a()` ...');
subType.a();
console.log('accessing `subType.b` ...');
subType.b;

console.log(
  '\ncustomInstance.constructor.name ...',
  customInstance.constructor.name
);
console.log(
  'subType.constructor.name ...',
  subType.constructor.name
);

console.log(
  '\n(customInstance instanceof NonAccessibleType) ?..',
  (customInstance instanceof NonAccessibleType)
);
console.log(
  '(customInstance instanceof SubType) ?..',
  (customInstance instanceof SubType)
);
console.log(
  '(subType instanceof NonAccessibleType) ?..',
  (subType instanceof NonAccessibleType)
);
console.log(
  '(subType instanceof SubType) ?..',
  (subType instanceof SubType)
);

Note, regarding the OP's mentioning of...

"The B class is intended to be just a method mixin and variable bundle..."

... the OP might have a look into the answer of "How to create an extended ES6-class constructor from a provided base class and from additionally provided and to be mixed-in behavior?" where the same base technique of a class creating factory got used for covering also the mixin aspect instead of just the one of inheritance.

英文:

An entirely ES6-class based approach provides everything (all of the necessary prototype (re)wiring) for free.

The following implementation returns a class which extends the constructor of any provided type-instance (either of class' or of pure constructor functions or of built-ins). It also supports the naming of the returned class constructor.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

function createExtendedClassFromInstanceType(
className, instance
) {
const { constructor: SuperClass } = instance;
if (&#39;function&#39; === typeof SuperClass) {
return ({
[className]: class extends SuperClass {
constructor(...args) {
super(...args);
// own implementation
}
}
})[className];
}
}
class NonAccessibleType {
constructor() {
}
// all prototypal
a() {
console.log(&#39;`a()` logs from custom prototype&#39;);
}
get b() {
console.log(&#39;`b` getter logs from custom prototype&#39;);
}
c () {
console.log(&#39;`c()` logs from custom prototype&#39;);
}
}
const customInstance = new NonAccessibleType;
const SubType = createExtendedClassFromInstanceType(
&#39;SubType&#39;, customInstance
);
let subType = new SubType;
console.log(&#39;invoking `subType.a()` ...&#39;);
subType.a();
console.log(&#39;accessing `subType.b` ...&#39;);
subType.b;
console.log(
&#39;\ncustomInstance.constructor.name ...&#39;,
customInstance.constructor.name
);
console.log(
&#39;subType.constructor.name ...&#39;,
subType.constructor.name
);
console.log(
&#39;\n(customInstance instanceof NonAccessibleType) ?..&#39;,
(customInstance instanceof NonAccessibleType)
);
console.log(
&#39;(customInstance instanceof SubType) ?..&#39;,
(customInstance instanceof SubType)
);
console.log(
&#39;(subType instanceof NonAccessibleType) ?..&#39;,
(subType instanceof NonAccessibleType)
);
console.log(
&#39;(subType instanceof SubType) ?..&#39;,
(subType instanceof SubType)
);

<!-- language: lang-css -->

.as-console-wrapper { min-height: 100%!important; top: 0; }

<!-- end snippet -->

Note, regarding the OP's mentioning of ...

> "... the B class is intended to be just a method mixin and variable bundle ..."

... the OP might have a look into the answer of "How to create an extended ES6-class constructor from a provided base class and from additionally provided and to be mixed-in behavior?" where the same base technique of a class creating factory got used for covering also the mixin aspect instead of just the one of inheritance.

答案3

得分: 0

抱歉,JavaScript不像Python一样提供getattr,因此您最好的选择是手动创建代理方法,例如:

class B {
    constructor(z) {
        let proto = Object.getPrototypeOf(z)
        for (let p of Object.getOwnPropertyNames(proto)) {
            let val = proto[p]
            if (typeof val === 'function')
                this[p] = val.bind(z)
        }
    }
}

请注意,val.bind(z) 将在传递的 z 对象(“外观”)的上下文中调用 Z 方法。您也可以使用 val.bind(this),这将使用 B 对象作为上下文(“混入”)。

英文:

Unfortunately, javascript doesn't provide getattr like in python, so your best bet is to create proxy methods manually, for example:

class B {
constructor(z) {
let proto = Object.getPrototypeOf(z)
for (let p of Object.getOwnPropertyNames(proto)) {
let val = proto

if (typeof val === &#39;function&#39;) this

= val.bind(z) } } }

Note that val.bind(z) would invoke Z methods in the context of the passed z object (="facade"). You can also do val.bind(this), which would use the B object as a context (="mixin").

答案4

得分: 0

以下是您要翻译的内容:

You can use a Proxy although Proxies ain't the most performant thing in JS.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

class Bar {
  value = 0;

  foo() {
    return "Bar.foo()";
  }

  bar(val) {
    this.value = val;
  }
}

class Foo {
  constructor() {
    //this.editor = new Bar();
    this.$editor = new Bar();
  }
}

Object.setPrototypeOf(Foo.prototype, new Proxy(
  Object.getPrototypeOf(Foo.prototype),
  {
    get(t, p, r) {
      //return Reflect.get(p !== "editor" && r.editor || t, p);
      return Reflect.get(r.$editor, p);
    },
  })
)

var f = new Foo();
console.log(f.foo());
console.log(f.value);
f.bar(42);
console.log(f.value);

<!-- end snippet -->
英文:

You can use a Proxy although Proxies ain't the most performant thing in JS.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

class Bar {
value = 0;
foo() {
return &quot;Bar.foo()&quot;;
}
bar(val) {
this.value = val;
}
}
class Foo {
constructor() {
//this.editor = new Bar();
this.$editor = new Bar();
}
}
Object.setPrototypeOf(Foo.prototype, new Proxy(
Object.getPrototypeOf(Foo.prototype),
{
get(t, p, r) {
//return Reflect.get(p !== &quot;editor&quot; &amp;&amp; r.editor || t, p);
return Reflect.get(r.$editor, p);
},
})
)
var f = new Foo();
console.log(f.foo());
console.log(f.value);
f.bar(42);
console.log(f.value);

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年3月9日 21:09:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/75685061.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定