9

我试图在这样的代码中模拟 JavaScript 中的“新”运算符:

Function.method('new', function ( ) {
    var objPrototype = Object.create(this.prototype);
    var instance = this.apply(objPrototype, arguments);

    return instance;
});

但是,为了涵盖所有情况,return 语句应如下所示:

return (typeof instance === 'object' && instance ) || objPrototype;

现在进行测试:

var SomeClass = function (param1, param2) {
    this.param1 = param1;
    this.param2 = param2;
};

var test1 = String.new('test1'); //in this case, the "instance" variable is an object
var test2 = SomeClass.new('test1', 'test2'); // in this case, the "instance" variable is undefined

这正是“新”运营商所做的吗?有没有要处理的案子?

4

4 回答 4

9

规范

11.2.2 新的运算符# Ⓣ</a> Ⓡ</a> Ⓖ</a>

产生式 NewExpression : new NewExpression的评估如下:

  1. ref是评估NewExpression的结果。
  2. 构造函数GetValueref)。
  3. 如果Type( constructor ) 不是 Object,则抛出TypeError异常。
  4. 如果构造函数没有实现 [[Construct]] 内部方法,则抛出TypeError异常。
  5. 返回在构造函数上调用 [[Construct]] 内部方法的结果,不提供任何参数(即一个空的参数列表)。

产生式 MemberExpression : new MemberExpression Arguments 的评估如下:

  1. ref是评估MemberExpression的结果。
  2. 构造函数GetValueref)。
  3. argList成为评估Arguments的结果,生成参数值的内部列表(11.2.4)。
  4. 如果Type( constructor ) 不是 Object,则抛出TypeError异常。
  5. 如果构造函数没有实现 [[Construct]] 内部方法,则抛出TypeError异常。
  6. 返回在constructor上调用 [[Construct]] 内部方法的结果,提供列表argList作为参数值。

在任何一种情况下,所有步骤都正确执行:

var objPrototype = Object.create(this.prototype);    // 1-4 1-5
var instance = this.apply(objPrototype, arguments);  // 5   6

兴趣点是 2.状态
规范[[construct]]

当使用可能为空的参数列表调用 Function 对象 F 的 [[Construct]] 内部方法时,将执行以下步骤:

  • 设 obj 为新创建的原生 ECMAScript 对象。
    . . .
  • 令 result 为调用 F 的 [[Call]] 内部属性的结果,提供 obj 作为 this 值,并提供传递给 [[Construct]] 的参数列表作为 args。
  • 如果Type结果)是对象,则返回结果
  • 返回obj

typeof obj返回"object"for null,whilenull不是对象。但是,由于null是一个虚假值,因此您的代码也可以按预期工作:

return (typeof instance === 'object' && instance ) || objPrototype;
于 2012-05-03T10:07:54.060 回答
8

运算符new接受一个函数F和: argumentsnew F(arguments...)它执行三个简单的步骤:

  1. 创建类的实例。它是一个空对象,其 __proto__属性设置为F.prototype。初始化实例。

  2. 使用传递的参数调用函数 F,并将 this 设置为实例。

  3. 返回实例

现在我们了解了 new 运算符的作用,我们可以在 Javascript 中实现它。

    function New (f) {
/*1*/  var n = { '__proto__': f.prototype };
       return function () {
/*2*/    f.apply(n, arguments);
/*3*/    return n;
       };
     }

只是一个小测试,看看它是否有效。

function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype = {
  print: function () { console.log(this.x, this.y); }
};

var p1 = new Point(10, 20);
p1.print(); // 10 20
console.log(p1 instanceof Point); // true

var p2 = New (Point)(10, 20);
p2.print(); // 10 20
console.log(p2 instanceof Point); // true
于 2012-05-03T09:58:09.610 回答
2

这里的答案对标准 ES5 都是有效的,在编写它们的时候就已经存在,但它们并不是适用于所有 ES6 上下文的通用解决方案,所以我想对它们进行扩展。简短的回答是问题中的代码:

Function.method('new', function ( ) {
  var objPrototype = Object.create(this.prototype);
  var instance = this.apply(objPrototype, arguments);

  return instance;
});

将在标准 ES6 环境中更好地实现为

Function.method('new', function ( ) {
  return Reflect.construct(this, arguments);
});

这绝对简化了事情。

Reflect.construct作为系统的一部分在 ES6 中被引入Proxy,但它具有类似这种情况的一般用途。

这是现在首选方法的原因是.apply不再适用于每种类型的函数的简单原因。上一个答案解释了new调用内部语言函数[[Construct]]来初始化参数。使用方法.apply本质上取代了自动对象创建和调用逻辑[[Construct]],而是手动创建对象然后调用函数,该函数使用它的[[Call]]内部方法而不是[[Construct]].

对函数的调用是 ES6 中变化的一部分。在 ES5 中,您几乎唯一要构建的是一个正常值function Foo(){},因此您可以对此做出假设。在 ES6class Foo {}中引入了语法,类语法创建的构造函数有更多的限制,所以关于 ES5 的假设不适用。最重要的是,明确禁止 ES6 类使用[[Call]]. 执行以下操作将引发异常:

class Foo {}
Foo();

.call这与和是相同的问题.apply。它们不是函数构造函数,它们是函数调用函数。所以如果你尝试在 ES6 类上使用它们,它们会抛出异常。

Reflect.construct通过实际调用[[Construct]]而不是调用来避免这些问题[[Call]],而是通过无需new.

于 2017-08-03T22:04:01.890 回答
0

这是使用该方法的替代__proto__方法。它与 OP 最初的开始方式一致......

function New(fn) {
    var newObj = Object.create(fn.prototype);
    return function() {
        fn.apply(newObj, arguments);
        return newObj;
    };
}

这是一种更简洁的方式,它也通过了原型链测试。

于 2014-12-02T05:30:55.013 回答