资讯专栏INFORMATION COLUMN

你真的弄明白 new 了吗

tolerious / 362人阅读

摘要:如果构造函数有返回值呢一般情况下构造函数没有返回值,但是我们依旧可以得到该对象的实例如果构造函数有返回值,凭直觉来说情况应该会不一样。欢迎光临小弟博客我的博客原文你真的弄明白了吗参考再谈面向对象编程的实例化与继承请停止使用关键字

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

创建一个空对象

将空对象的__proto__指向构造函数的prototype

使用空对象作为上下文调用构造函数

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

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

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

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

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

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

真的就3件事?

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

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

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

javascriptb.constructor = Base;

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

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。如下图所示:

这里非常感谢zonxin同学指出我的错误。

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

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

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了吗

参考

再谈javascript面向对象编程

JavaScript的实例化与继承:请停止使用new关键字

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85678.html

相关文章

  • 真的明白javascript中的原型和原型链了吗

    摘要:补充的知识这个是原型中的自带属性,指向构造函数这个属性其实是浏览器实现的,不是标准的访问原型的方式中规定的正式方法是以上知识,最终的图如下思考题在文章开头我们说过函数也是对象,既然是对象就有原型,那的原型指向谁呢是吗 文章开头说的话 首先你必须明白(或者记住)的JavaScript常识: 在JavaScript中每个函数都有一个prototype属性 在JavaScript中每个对象...

    Harpsichord1207 评论0 收藏0
  • 真的明白javascript中的原型和原型链了吗

    摘要:补充的知识这个是原型中的自带属性,指向构造函数这个属性其实是浏览器实现的,不是标准的访问原型的方式中规定的正式方法是以上知识,最终的图如下思考题在文章开头我们说过函数也是对象,既然是对象就有原型,那的原型指向谁呢是吗 文章开头说的话 首先你必须明白(或者记住)的JavaScript常识: 在JavaScript中每个函数都有一个prototype属性 在JavaScript中每个对象...

    LiuRhoRamen 评论0 收藏0
  • 说下性能优化

    摘要:性能优化追求的是什么是你的网页可以以最快的速度打开比如说用户点一下啪的就开了点哪里哪里开什么操作都是立刻有反馈关键字是速度试想未来有一天到了时代每个人的网速都是的那你还优化什么你的网站就算是大小也不怕可是那是遥远的未来当下网速还没有这么快。 性能优化 追求的是什么, 是你的网页可以 以最快的速度 打开, 比如说用户点一下啪的就开了点哪里哪里开, 什么操作都是立刻有反馈. 关键字是:速度...

    王伟廷 评论0 收藏0
  • 关于python编码,真的明白了吗

    摘要:二进制位,是计算机内部数据储存的最小单位,是一个位二进制数。由于源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为编码。 计算机存储的几个概念 想要彻底搞清楚编码问题,我们必须要先搞清楚计算机是怎么存储数据的,这就涉及到了计算机基础的几个概念了,开篇我们就先来捋捋这几个容易混淆的概念。 bit 二进制位, 是计算机内部数据储存的最小单位,1...

    darcrand 评论0 收藏0
  • 一份Docker的反方辩论——我还是用Heroku好了

    摘要:那都是老一套了。已死已经没有人用了。现在所有的一切都在容器化,它是未来。这是确保它可靠的唯一方式。我现在需要一个是的,为了稳定性。我猜是规模的对,没错。我明白了,好吧,我懂了。那我来重复一遍确保我领悟了这些。 这是一篇在国外社区非常火的文章。由CircleCI创始人所写,追逐热点新技术的程序员与只想做个简单web应用的程序员对话,Docker到底能否解决简单小应用的问题吗?Herok...

    codeGoogle 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<