JavaScript中的new关键词
这两天学习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.fedev.cn/javascript/javascript-new-keyword.htmlnike air max 90 ebay