转载

你真的弄明白了new吗

好久没有写点东西了,总觉得自己应该写点牛逼的,却又不知道如何下笔。既然如此,还是回归最基本的吧,今天就来说一说这个 new 。关于javascript的 new 关键字的内容上网搜一搜还真不少,大家都说 new 干了3件事:

  • 创建一个空对象
  • 将空对象的 __proto__ 指向构造函数的 prototype
  • 使用空对象作为上下文调用构造函数

文字比较难懂,翻译成javascript:

function Base() {    this.str = "aa"; } // new Base()干了下面的事 var obj  = {};   obj.__proto__ = Base.prototype;   Base.call(obj);    

想想是这么回事哈,那就赶快试试:

var b = new Base();   console.dir(b); // Base {str: 'aa', __proto__: Base}   

好像是正确的,但是真的正确吗???

真的就3件事?

每个对象都有一个 constructor 属性,那么我们来试试看 new 出来的实例的 constructor 是什么吧。

console.dir(b.constructor); // [Function: Base]   

可以看出实例b的 constructor 属性就是 Base ,那么我们可以猜测 new 是不是至少还做了第4件事:

b.constructor = Base;   

以上结果看似正确,下面我们进行一点修改,这里我们修改掉原型的 constructor 属性:

Base.prototype.constructor = function Other(){ };   var b = new Base();   console.dir(b.constructor); // [Function: Other]   

情况就不一样了,可以看出,之前的猜测是错误的,第4件事应该是这样的:

b.constructor = Base.prototype.constructor;   

如果构造函数有返回值呢?

一般情况下构造函数没有返回值,但是我们依旧可以得到该对象的实例;如果构造函数有返回值,凭直觉来说情况应该会不一样。我们对于之前的构造函数进行一点点修改:

function Base() {    this.str = "aa";  return 1;  // return "a";  // return true; } var b = new Base();   console.dir(b); // { str: 'aa'}    

我们在构造函数里设置的返回值好像没什么用,返回的还是原来对象的实例,换一些例子试试:

function Base() {    this.str = "aa";  return [1];  // return {a:1}; } var b = new Base();   console.dir(b); // [1] or {a: 1}    

此时结果就不一样了,从上面的例子可以看出, 如果构造函数返回的是原始值,那么这个返回值会被忽略,如果返回的是对象,就会覆盖构造的实例

new至少做了5件事

总结一下, new 至少做了5件事:

// new Base();  // 1.创建一个空对象 obj var obj = {};   // 2.设置obj的构造函数为原型的constructor obj.constructor = Base.prototype.constructor;   // 3.设置obj的__proto__为原型 obj.__proto__ = Base.prototype;   // 4.使用obj作为上下文调用Base函数 var ret = Base.call(obj);   // 5.如果构造函数返回的是原始值,那么这个返回值会被忽略,如果返回的是对象,就会覆盖构造的实例 if(typeof ret == 'object'){       return ret; } else {     return obj; } 

new的不足

在《Javascript语言精粹》(Javascript: The Good Parts)中,道格拉斯认为应该避免使用 new 关键字:

If you forget to include the new prefix when calling a constructor function, then this will not be bound to the new object. Sadly, this will be bound to the global object, so instead of augmenting your new object, you will be clobbering global variables. That is really bad. There is no compile warning, and there is no runtime warning.

大意是说在应该使用 new 的时候如果忘了 new 关键字,会引发一些问题。最重要的问题就是影响了原型查找,原型查找是沿着 __proto__ 进行的,而任何函数都是 Function 的实例,一旦没用使用 new ,你就会发现什么属性都查找不到了,因为相当于直接短路了。如下面例子所示,没有使用 new 来创建对象的话,就无法找到原型上的fa1属性了:

function F(){ }   F.prototype.fa1 = "fa1";  console.log(F.fa1);       // undefined   console.log(new F().fa1); // fa1   

这里我配合一张图来说明其中原理,黄色的线为原型链,使用 new 构造的对象可以正常查找到属性 fa1 ,没有使用 new 则完全走向了另外一条查找路径:

你真的弄明白了new吗

以上的问题对于有继承的情况表现得更为明显,沿着原型链的方法和属性全都找不到,你能使用的只有短路之后的 Function.prototype 的方法了。

当然了,你遗忘使用任何关键字都会引起一系列的问题。再退一步说,这个问题是完全可以避免的:

function foo()   {   // 如果忘了使用关键字,这一步骤会悄悄帮你修复这个问题  if ( !(this instanceof foo) )   return new foo();  // 构造函数的逻辑继续…… }  

可以看出 new 并不是一个很好的实践,道格拉斯将这个问题描述为:

This indirection was intended to make the language seem more familiar to classically trained programmers, but failed to do that, as we can see from the very low opinion Java programmers have of JavaScript. JavaScript’s constructor pattern did not appeal to the classical crowd. It also obscured JavaScript’s true prototypal nature. As a result, there are very few programmers who know how to use the language effectively.

简单来说,JavaScript是一种prototypical类型语言,在创建之初,是为了迎合市场的需要,让人们觉得它和Java是类似的,才引入了 new 关键字。Javascript本应通过它的Prototypical特性来实现实例化和继承,但 new 关键字让它变得不伦不类。

再说一点关于constructor的

虽然使用 new 创建新对象的时候用到了 constructor 属性,但是这个属性似乎并没有什么用,也许设置这个属性就是一种习惯,能够让其他人直观理解对象之间的关系。

参考

正文到此结束
Loading...