从MDN上,我们知道:
-
new运算符的作用:定义对象类型或构造函数内置对象的实例。 -
new运算符的语法:new Constructor([arguments]),其中Constructor是一个具有对象实例的类或者函数,arguments参数是可选的。
function Dog() {
// 这里的this指向的就是Dog的实例
this.name = 'lucky';
this.color = 'black';
}
// 此时的dog就是undefined
// 因为:Dog函数在没有指定return时,默认返回的就是undefined
var dog = Dog();
// 此时dogInst就拥有了name,color等属性
// 说明:new运算符对this做了"手脚",使得dogInst拥有了name、color属性
var dogInst = new Dog();
那这里的new Dog()背后到底做了哪些工作?不过从上面的例子可以知道:
- 构造函数的调用方式影响着最后的结果;
new调用方式改变了this的指向(apply、call、bind);
我们通过模拟new的行为自己实现一个New运算符,以此来分析new运算符内部到底做了什么:
function Dog(name) {
this.name = name;
// ...other properties
}
function New(Ctor) {
// 第一步,创建新对象
// 注意:arguments为类数组,需要借用Array实例上的slice方法
// es6中,我们可以使用spread syntax(...iterableObj)处理arguments对象
var o = Object.create(null);
// 第二步,新对象继承构造函数原型上的所有属性
o.__proto__ = Ctor.prototype;
var args = Array.prototype.slice.call(arguments, 1);
// 第三步,执行构造函数,将所有属性添加到新建的对象上
var res = Ctor.apply(o, args);
// 第四步,判断执行构造函数的方式,返回对应结果
return Object.prototype.toString.call(res) === '[object Object]' ? res : o;
}
var dogInst = New(Dog, 'lucky');
总结
创建new运算符的四个步骤:
- 新建一个对象
(无原型的Object.create(null))。目的是保存new出来的实例的所有属性; - 将构造函数的原型赋值给新创建的对象的原型。目的是将构造函数原型上的属性继承下来;
- 调用构造函数,并将内部
this指向新建的对象。目的是让构造函数内的属性全部转交到该对象上,而要使得this指向改变,方法有三:apply、call、bind; - 判断构造函数调用的方式,如果是
new的调用方式,则返回经过加工后的新对象,如果是普通调用方式,则直接返回构造函数调用时的返回值;
小结
曾经看似很难的问题,都经不起时间的认真推敲,所以需要多多思考。