在JavaScript中模拟'new'运算符

时间:2012-05-03 09:28:12

标签: javascript

我尝试在这样的代码中用JavaScript模拟'new'运算符:

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 个答案:

答案 0 :(得分:9)

来自specification

  

11.2.2新运营商#

     

生产 NewExpression new NewExpression 评估如下:

     
      
  1. ref 成为评估 NewExpression 的结果。
  2.   
  3. 构造函数GetValue ref )。
  4.   
  5. 如果Type构造函数)不是Object,则抛出TypeError例外。
  6.   
  7. 如果构造函数未实现[[Construct]]内部方法,则抛出TypeError例外。
  8.   
  9. 返回在构造函数上调用[[Construct]]内部方法的结果,不提供任何参数(即,一个空的参数列表)。
  10.         

    生产 MemberExpression new MemberExpression参数   评估如下:

         
        
    1. ref 成为评估 MemberExpression 的结果。
    2.   
    3. 构造函数GetValue ref )。
    4.   
    5. argList 成为评估 Arguments 的结果,生成一个参数值的内部列表(11.2.4)。
    6.   
    7. 如果Type构造函数)不是Object,则抛出TypeError例外。
    8.   
    9. 如果构造函数未实现[[Construct]]内部方法,则抛出TypeError例外。
    10.   
    11. 返回在构造函数上调用[[Construct]]内部方法的结果,提供列表 argList 作为参数值。
    12.   

在任何一种情况下,都会正确遵循所有步骤:

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

兴趣点是2.
The specification for [[construct]]州:

  

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

     
      
  • 让obj成为新创建的本机ECMAScript对象   . . .
  •   
  • 让结果成为调用F的[[Call]]内部属性的结果,提供obj作为此值并提供参数列表   作为args传入[[Construct]]。
  •   
  • 如果Type结果)是对象,则返回结果
  •   
  • 返回 obj
  •   

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

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

答案 1 :(得分:6)

new运算符使用函数Fargumentsnew F(arguments...)。它有三个简单的步骤:

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

  2. 调用函数F并传递参数并将其设置为     是实例。

  3. 返回实例

  4. 现在我们了解了新运算符的作用,我们可以在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
    

答案 2 :(得分:0)

以下是使用__proto__方法的替代方法。它符合OP最初的开始......

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

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

答案 3 :(得分:0)

这里的答案对于标准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作为Proxy系统的一部分在ES6中引入,但它具有类似这种情况的一般用途。

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

对函数的调用是ES6中更改的一部分。在ES5中,您构建的唯一内容是正常function Foo(){}值,因此您可以对此进行假设。在ES6中,引入了class Foo {}语法,并且由类语法创建的构造函数具有更多限制,因此关于ES5的假设不适用。最重要的是,明确禁止ES6类使用[[Call]]。执行以下操作将引发异常:

class Foo {}
Foo();

这与.call.apply相同。它们不是函数构造函数,它们是函数调用函数。因此,如果您尝试在ES6类上使用它们,它们将抛出异常。

Reflect.construct通过实际调用[[Construct]]而不是[[Call]]来避免这些问题,但是通过可以在没有new的情况下使用的API公开它