【问题标题】:How to use es6 constructor instructions with a different context如何在不同的上下文中使用 es6 构造函数指令
【发布时间】:2019-07-04 11:14:27
【问题描述】:

是否可以通过更改“this”上下文(调用、应用或其他)在另一个实例上使用 es6 构造函数指令?这可以使用 es5“类”。这是我的意思的一个小例子:

function ES5() {
  this.foo = 'foo';
}

class ES6 {
  constructor() {
    this.bar = 'bar';
  }
}

var a = new ES6();
ES5.call(a);
console.log(a.foo + a.bar); //foobar



var b = new ES5();
//Reflect.construct(ES6); ??
ES6.call(b); //TypeError: Class constructor ES6 cannot be invoked without 'new'

console.log(b.foo + b.bar); //how to get foobar here too?

编辑: 我的问题与 new 关键字无关。我正在寻找的答案是如何使用另一个“this”上下文(带或不带 new 关键字)运行放置在 es6 构造函数中的指令。

【问题讨论】:

  • 我认为这个问题不应该被关闭
  • @Chg 对“是否可以通过更改“this”上下文(调用、应用或其他)在另一个实例上使用 es6 类构造函数指令的答案?没有。合适的解决方法将取决于用例。
  • 我认为你的问题是你试图在不调用构造函数的情况下创建一个 ES6 对象。你试过apply而不是call吗?
  • 为什么要这样做?对 JavaScript 的一个流行批评是,函数和 this 令人困惑,因为函数履行了多个角色。对此的回应是引入了更多特殊用途的构造,例如只能以特定方式使用的类。如果你需要做一些类不能做的事情,不要使用类。
  • 这似乎是XY problem。与其寻求帮助以尝试解决您的问题,不如告诉我们您的实际问题。你到底想做什么?

标签: javascript ecmascript-6 constructor ecmascript-5


【解决方案1】:

正如 cmets 和您自己所指出的,尝试使用自定义 this 上下文调用类构造函数确实不是您想要尝试的,如果有任何解决方法的话。这是故意变硬的!

如果由于某些原因不可避免地足以证明棘手的解决方法是合理的,您可以在下面找到两个部分解决方案。它们都有各自的不完美之处 - 根据您的具体情况,其中之一可能仍然适合您的需求。


解决方法 1

虽然无法在构造函数调用中直接设置this,但可以将this 的原型设置为您选择的对象

为此,您可以使用 Reflect.construct() 使用自定义 new.target 值调用内部 [[Construct]] 方法。然后this 将被初始化为继承自new.target.prototype 的对象。

以您的示例为基础:

function ES5() {
    this.foo = 'foo';
}

class ES6 {
    constructor() {
        this.bar = 'bar';
    }
}

let b = new ES5();

function TemporaryHelperConstructor() {}
TemporaryHelperConstructor.prototype = b;

b = Reflect.construct( ES6, [], TemporaryHelperConstructor ); // The third argument corresponds to the value of new.target

console.log( b.foo + b.bar ); // foobar !

Reflect.construct() 和内部[[Construct]] 方法的确切工作原理在规范的26.1.29.2.2 部分中进行了描述)

潜在问题

  • 实际上调用类构造函数时this 绑定到b,它是在this 绑定到一个直接继承自b 的空对象时调用的。如果您或类构造函数依赖于 Object.getOwnPropertyNames()Object.getPrototypeOf() 等方法,这可能会导致问题。

解决方法 2

虽然不可能在不引起TypeError的情况下调用类构造函数的内部[[Call]]方法,但可以提取附加到类构造函数的代码块并从中创建一个普通函数,然后您可以使用自定义 this 绑定来调用它。

您可以使用Function.prototype.toString() 方法将类构造函数的代码块提取为字符串。然后Function() 构造函数可以用这个字符串创建一个普通函数,您可以通过Function.prototype.apply() 使用自定义this 绑定来调用它。

以您的示例为基础:

function ES5() {
    this.foo = 'foo';
}

class ES6 {
    constructor() {
        this.bar = 'bar';
    }
}

const b = new ES5();

const constructorBody = ES6.toString().match( /(?<=constructor\(\) ){[^}]*}/ )[0]
const ordinaryFunction = Function( constructorBody )

ordinaryFunction.apply( b ); // No TypeError

console.log( b.foo + b.bar ); // foobar !

请注意,这个 sn-p 使用一个极其简化的正则表达式来进行演示。为了使事情变得健壮,您需要考虑字符串和 cmets 中的嵌套花括号和花括号。如果需要,您还需要提取构造函数参数。

(根据规范的19.2.3.5 部分,您可以依靠Function.prototype.toString() 的足够一致的输出来使这种方法跨实现工作。)

潜在问题

  • new.target 将在执行普通函数时设置为 undefined(与 [[Call]] 调用一样),如果类构造函数使用它可能会导致问题。
  • 原始类构造函数的闭包将丢失给使用Function() (MDN) 创建的新函数,如果类构造函数依赖它们,这可能会导致ReferenceErrors
  • 如果将这种方法应用于使用super() 的派生类,则会导致SyntaxError,这在普通函数中是无效的语法。

结论

您的问题没有完美的解决方案。如果您的用例足够简单,那么您仍然可以实现您想要的。部分解决方法会带来自身的有害问题 - 小心行事!

【讨论】:

  • 谢谢奥利弗!代理在您的第二个解决方法中的目的是什么?没有它,它似乎工作得很好,因为 normalFunction.apply(b) 给了我想要的结果。
  • @ChG 是的,你是对的。我想我的想法是你可以简单地使用ES6Proxy 作为你的替代类,也可以用它做任何你想做的事情。我已经把它拿出来了,因为它可能会使事情变得不必要地复杂化,而不会增加太多理解。
  • 这些解决方案都不适用于我的具体情况,但这就是问题的答案!谢谢!
猜你喜欢
  • 1970-01-01
  • 2018-08-08
  • 2017-05-02
  • 1970-01-01
  • 2017-06-29
  • 2016-12-09
  • 1970-01-01
  • 2011-12-15
  • 1970-01-01
相关资源
最近更新 更多