JavaScript中的new关键词
编辑推荐:3月31日前,点击注册激活 Coding.net 立赠30天付费会员 ,体验极速代码托管服务!
这两天学习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__指向构造函数的prototype:obj.__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关键词)创建了一个叫name的object
也就是说:开头的几行代码至少创建了2个object,一个是Person,类型为function的object;另一个是name,类型为object的object。
Function() 和 Object()
这是JavaScript中预定义好的构造器。一切function(比如Person())都是由Function()构造出来的;而Object的prototype将会被所有object继承。

Function的创建过程
当执行function Person() { this.name = 'w3cplus'}时,相当于 var name = new Function('this.name = "w3cplus"'),也就是说,这行代码本身,将使用预定义好的Function()的constructor构造一个function型object,即name。在这个过程中JavaScript做了下面这些事情。
首先创建一个object,Person指向这个object。通过typeof可以判断出这个object是function。

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

也就是说,在执行任意类似 var fn = new Fn()时,都会把Fn的prototype赋值给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
用下图可以将这几者关系完全串起来:

其实这几者关系还是很复杂的,建议阅读:
- Constructors Considered Mildly Confusing
- constructor、proto 和 prototype 区别和关系
- 《You Don’t Know JavaScript》笔记(5)原型
- JavaScript 中的原型和原型对象
- js中proto和prototype的区别和关系?
- Js中Prototype、proto、Constructor、Object、Function关系介绍
- 一张图理解prototype、proto和constructor的三角关系
- proto VS. prototype in JavaScript
- 从proto和prototype来深入理解JS对象和原型链
- proto in ECMAScript 6
JavaScript中的new真的只做了3件事?
前面在介绍constructor的时候,我们不难发现name的constructor属性就是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。
扩展阅读
- Constructors Considered Mildly Confusing
- Creating instances without new
- 对象的原型链之由来
- new关键字的玄机,以及其它
- 你真的弄明白new了吗
- JavaScript的实例化与继承:请停止使用new关键字
- What is the 'new' keyword in JavaScript?
- 談談JavaScript中的function constructor和關鍵字new
如需转载,烦请注明出处:https://www.w3cplus.com/javascript/javascript-new-keyword.html




