你真的弄明白 new 了吗

很久没有写点东西了,总以为本身应该写点牛逼的,却又不知道如何下笔。既然如此,仍是回归最基本的吧,今天就来讲一说这个new。关于javascript的new关键字的内容上网搜一搜还真很多,你们都说new干了3件事:javascript

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

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

javascriptfunction Base() {
    this.str = "aa";
}

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

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

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

好像是正确的,可是真的正确吗???shell

真的就3件事?

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

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

能够看出实例b的constructor属性就是Base,那么咱们能够猜想new是否是至少还作了第4件事:segmentfault

javascriptb.constructor = Base;

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

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

状况就不同了,能够看出,以前的猜想是错误的,第4件事应该是这样的:函数

javascriptb.constructor = Base.prototype.constructor;

这里犯了一个错误,那就是没有理解好这个constructor的实质:当咱们建立一个函数时,会自动生成对应的原型,这个原型包含一个constructor属性,使用new构造的实例,能够经过原型链查找到constructor。以下图所示:this

constructor

这里很是感谢zonxin同窗指出个人错误。spa

若是构造函数有返回值呢?

通常状况下构造函数没有返回值,可是咱们依旧能够获得该对象的实例;若是构造函数有返回值,凭直觉来讲状况应该会不同。咱们对于以前的构造函数进行一点点修改:

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

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

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

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

new至少作了4件事

总结一下,new至少作了4件事:

javascript// new Base();

// 1.建立一个空对象 obj
var obj = {};
// 2.设置obj的__proto__为原型
obj.__proto__ = Base.prototype;
// 3.使用obj做为上下文调用Base函数
var ret = Base.call(obj);
// 4.若是构造函数返回的是原始值,那么这个返回值会被忽略,若是返回的是对象,就会覆盖构造的实例
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属性了:

javascriptfunction F(){ }
F.prototype.fa1 = "fa1";

console.log(F.fa1);       // undefined
console.log(new F().fa1); // fa1

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

原型查找

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

固然了,遗忘使用任何关键字都会引发一系列的问题。再退一步说,这个问题是彻底能够避免的:

javascriptfunction 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属性,可是这个属性彷佛并无什么用,也许设置这个属性就是一种习惯,可以让其余人直观理解对象之间的关系。

欢迎光临小弟博客:Superlin's Blog
个人博客原文:你真的弄明白new了吗

参考

相关文章
相关标签/搜索