JavaScript中的new关键词

编辑推荐: 掘金是一个高质量的技术社区,从 CSS 到 Vue.js,性能优化到开源类库,让你不错过前端开发的每一个技术干货。 点击链接查看最新前端内容,或到各大应用市场搜索「 掘金」下载APP,技术干货尽在掌握中。

这两天学习JavaScript的数据类型以及字符转换过程中接触到了new这个关键词。比如new String()new Boolean()new Number()之类的。如果我们通过typeof来判断他们的类型的话,得到的结果都是object。那么在JavaScript中,new关键词有哪些知识点需要了解呢?

new和JavaScript关系

初学者会问,JavaScript和Java有什么关系,记得网友吐槽:

(^_^)这并不重要,不是我们今天要了解的事情。

JavaScript虽然听起来或看起来和Java很像,但实际上可以说是非常的不同,只不过当初为了更好的推广JavaScript,为了让它和Java有更多的相似性,吸引更多的Java程序员。因为在JavaScript中有了类似Java的语法var bar = new foo()。这样的方式在Java中可以根据某个类别class来创建对象,但在JavaScript中并没有真正的class这样的东东,这么做的目的只是为了让Java使用者在看到JavaScript的时候觉得有股亲切感,有股熟悉感。

那么在JavaScript中的new能做什么呢?

JavaScript中的new关键词做了什么

关于JavaScript中的new关键词的内容在网上非常多,很多人说new干了三件事情:

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

将上面的这个我们用JavaScript代码来表达。创建一个Person的函数:

function Person() {
    this.name = 'w3cplus'
}

根据上面描述的,new Person()做了:

  • 创建一个空对象:var obj = {}
  • 将空对象的__proto__指向构造函数的prototypeobj.__proto__ = Person().prototype
  • 使用空对象作为上下文调用构造函数: Person.call(obj)

来看一下事实是不是和上述描述的一样:

function Person() {
    this.name = 'w3cplus'
}

var obj = new Person();

console.dir(obj);

咱们先不管是不是真的做了三件事,先来看一下new这个过程都发生了些什么事情?

在JavaScript中new这个关键词其实也是众多运算符(Operators)中的其中一种。前面说过,当我们使用new关键词,实际上先创建了一个空的对象。接着Person这个函数会被执行(Invoke)。当函数在执行的时候,在执行上下文(Execution Context)中会有一个this被创建。而当我们使用new的时候,函数里的this会被指定成刚刚所建立的那个空对象。

所以当执行Person()这个函数时,执行到this.name时,因为this现在指向的是那个空对象,所以实际上是在帮这个空对象赋值(属性名称和属性值)。

在这样的过程中,只要这个函数没有指定return为其他对象时,它就会直接传回给我们这个新创建的对象。接着让我们来看这个函数的代码,从而更清楚的了解其执行的过程。

Person()函数有被执行

在上面的函数基础上增加一行新代码:

function Person() {
    this.name = 'w3cplus';
    this.age = 7;
    console.log('这个函数已经被执行了!')
}

var name = new Person();
console.log(name);

从Chrome浏览器console.log()打印出来的结果告诉我们,当使用new构建对象时,这个Person()函数其实已经被执行了。

通过new会帮我们创建一个空的对象

接着把Person()函数修改成这样:

function Person() {
    console.log(this);
}
var name = new Person()

上图已经告诉我们,new Person()创建了一个空对象Person {}。如果Person()函数中有return出其他的对象,则新对象会覆盖旧对象。

function Person() {
    this.name = 'w3cplus';
    this.age = 7;
    return {'Return': '原本this的内容就不会返回'}
}

var name = new Person();
console.log(name); //  {'Return': '原本this的内容就不会返回'}

除了这种情况,如果函数Person()有自己返回的值,但不是一个对象,比如:

function Person() {
    this.name = 'w3cplus';
    this.age = 7;
    return 1;
}

var name = new Person();
console.log(name); //  { name: 'w3cplus'}  

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

构造函数的实际应用

上面的内容告诉我们,可以通过function的方法来创建一个新的对象,如果我们想要创建出同属性名称,但不同属性值的对象时,可以把对象的属性值当作函数的参数。这就是通过构造函数创建出许多不同的对象:

function Person(name, age) {
    this.name = name;
    this.age = age;
}

var w3cplus = new Person('w3cplus', 7);
console.log(w3cplus);

var alibaba = new Person('Alibaba', 18);
console.log(alibaba);

这样就创建了两个对象:

说到这里,有几个概念需要了解,会对上面的内容有更好的了解。

Constructor

在JavaScript中没有class,也就是说没有class里的构造函数。那么object是怎么被创建的呢?

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

function Person() {
    this.name = 'w3cplus';
    this.age = 7;
}
var name = new Person();
console.log(name.constructor);

从上面的输出结果就可以告诉我们,使用构造器:constructor创建出来了object。也就是说,constructor其实就是Function,而它本身也是一个object

  • function Person() {}是一个构造器
  • var name = new Person()是用这个构造器(通过new关键词)创建了一个叫nameobject

也就是说:开头的几行代码至少创建了2object,一个是Person,类型为functionobject;另一个是name,类型为objectobject

Function() 和 Object()

这是JavaScript中预定义好的构造器。一切function(比如Person())都是由Function()构造出来的;而Objectprototype将会被所有object继承。

Function的创建过程

当执行function Person() { this.name = 'w3cplus'}时,相当于 var name = new Function('this.name = "w3cplus"'),也就是说,这行代码本身,将使用预定义好的Function()constructor构造一个functionobject,即name。在这个过程中JavaScript做了下面这些事情。

首先创建一个objectPerson指向这个object。通过typeof可以判断出这个objectfunction

这个时候给Person附上__proto__属性,并且让它等于Function这个构造器的prototype(这个也是预定义好的)。它的结果返回是true

也就是说,在执行任意类似 var fn = new Fn()时,都会把Fnprototype赋值给fn__proto__,也就是说,fn.__proto__Fn.prototype此时会指向同一个对象。

除此之外,还可以给Person创建call属性,该属性是个function。因此我们可以这样写:Person.call()

同时也可以为Person创建construct属性,改属性也是一个function。在执行var name = new Person()时,即会调用这个construct属性。

是不是有点晕,不过有网友这样总结:

  • Object是对象的祖先
  • Function是函数的祖先
  • 函数可以做构造器
  • 对象是函数new出来的
  • 构造器的prototype是对象
  • 对象的__proto__指向构造器的prototype

用下图可以将这几者关系完全串起来:

其实这几者关系还是很复杂的,建议阅读:

JavaScript中的new真的只做了3件事?

前面在介绍constructor的时候,我们不难发现nameconstructor属性就是Person。那么我们就可以猜测new是不是至少还做了第四件事:

name.constructor = Person

上面的结果看上去没问题,下面我们进行一点修改。这里我们修改掉原型的constructor属性:

function Person() {
    this.name = 'w3cplus';
    this.age = 7;
}
Person.prototype.constructor = function Other() {
    this.name = 'damo';
    this.age = 30;
}
var name = new Person();
console.log(name.constructor);

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

name.constructor = Person.prototype.constructor

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

简单的总结一下:JavaScript中的new关键词至少做了四件事情:

function Person() {
    this.name = 'w3cplus';
    this.age = 7;
}
var name = new Person()

// 1:创建一个空对象obj
var obj = {}

// 2:设置obj的__proto__为原型
obj.__proto__ = Person.prototype;

// 3:使用obj作为上下文调用Person函数
var ret = Person.call(obj);

// 4:如果构造函数返回的是原始值,那么这个返回值会被忽略
//    如果构造函数返回的是对象,就会覆盖构造的实例
if (typeof ret == 'object') {
    return ret;
} else {
    return obj
}

不过在StackOverflow网站上看到一个贴子《What is the 'new' keyword in JavaScript?》中一个高赞回帖说,JavaScript中的new至少做了五件事情。另外有一篇文章专门对此帖做了相关分析,有兴趣的同学可以点击这里阅读

JavaScript中new不足之处

使用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则完全走向了另外一条查找路径:

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

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

function foo(){   
    // 如果忘了使用关键字,这一步骤会悄悄帮你修复这个问题
    if ( !(this instanceof foo) )
        return new foo();

    // 构造函数的逻辑继续……
}

可以看出new并不是一个很好的实践。有关于这方面更详细的介绍可以阅读《JS: Creating instances without new》一文。

总结

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

扩展阅读

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.w3cplus.com/javascript/javascript-new-keyword.html

返回顶部