写在前面
1.从字面量到动态管理,对象:你也太细节了吧--JS基础篇(四) - 掘金 (juejin.cn)
2.吃透JavaScript:掌握构造函数与原型链的深层奥秘--JS基础篇(五) - 掘金 (juejin.cn)
3.深入剖析this之后,面试官:想不到你会手写call( )!--JS基础篇(六) - 掘金 (juejin.cn)
前三期我介绍了对象的创建和动态管理、原型和原型链和this关键字,这些内容都为我们接下来去理解操作服符new的执行机制奠定了基础。至于手写new关键字这件事,是因为new的执行机制在某些复杂开发场景以及面试过程都有涉及。
new操作符的任务
在日常开发中,我们经常会使用new去创建一个构造函数的实例对象,这个实例对象默认继承了构造函数原型上的所有方法。
下面我们就来看看new操作符对一个构造函数进行的操作。
let arr = new Array();
在浏览器控制台使用Object.getPrototypeOf(arr)能够查看实例对象的原型(即[[prototype]]属性所指向的对象),上图这些也就是构造函数Array的原型对象上的方法。
也就是说,创建实例对象时,new操作符必须要做的是:将新对象的隐式原型(.__proto__)指向构造函数的显式原型(Array),这样实例对象才能够去共享构造函数的原型上的方法和属性。
那么new还做了什么事呢?我们接着分析。
在给实例对象添加属性或者方法的时候,我们还可以直接为构造函数传递一个参数,例如:
let arr = new Array(10);
这表示我需要一个长度为10的空数组。我们都知道,构造函数Array()的原型上有length这个属性,表示数组的长度。所以当我们往构造函数里面传递长度后,new操作符内部能够准确将将新对象数组的长度也改为10。
有意思的来了,为什么new操作符能够在构造函数Array()中修改实例对象arr的length属性呢?且这里应该是只修改了arr的length属性,对于Array()原型上的length没有改变。
这就不得不提到上期我们讲的this关键字了。在一个构造函数中,this指代的是当前对象,我们可以通过this.xxx的形式为当前对象添加或修改一个属性。
当上面的构造函数Array()接收到length:10后,如果需要只改变实例对象的length属性,则需要通过this.length = 10这样的操作去完成。但是,修改属性length时,this所指代的对象必须是它的实例对象,所以还应该先将构造函数中的this指向修改为指向实例对象。也就是:
Array.call(arr)
换句话说,new操作符执行机制中相对重要的一环就是:修改构造函数的this绑定。 在上述例子中,new操作符将构造函数中的this指向修改为了实例对象,而非其本身,这样就能够顺利地修改实例对象的length。
//模拟构造函数的行为
function Array(length){
this.length = length;//此时this应当已经指向实例对象arr
}
我们简单梳理一下new执行机制的关键:
- 将实例对象的隐式原型执行构造函数的显式原型(即将实例对象的.__proto__指向Constructor.prototype)
- 将构造函数的this指向修改为指向实例对象。
那么接下来让我们一起写一遍,这当中也有一些细节。
new的自定义实现
动手之前还是梳理一下步骤。
首先,new操作符之所以能够创建一个构造函数的实例化对象,必定是先创建了一个空对象。
function myNew(){
const instance = {};
}
创建一个对象字面量即可,不过在此之前,我们还需要接收参数,因为构造函数中是可以接收参数的。
function myNew(Constructor,...args){
const instance = {};
}
获取到参数和构造函数本身,则可以开始实现核心机制:
修改原型指向:
function myNew(Constructor,...args){
const instance = {};
Object.setPrototypeOf(instance,Array.prototype);//修改实例对象隐式原型
}
修改构造函数this指向:
function myNew(Constructor,...args){
const instance = {};
Object.setPrototypeOf(instance,Array.prototype);
Constructor.call(instance,...args);//修改构造函数this指向
}
最后,还需要一个返回值:
function myNew(Constructor,...args){
const instance = {};
Object.setPrototypeOf(instance,Array.prototype);
Constructor.call(instance,...args);
return instance;
}
简化版本:
function myNew(Constructor,...args){
const instance = Object.create(Array.prototype);
Constructor.call(instance,...args);
return instance;
}
其中这里const instance = Object.create(Array.prototype);,创建了一个原型对象为Array的新对象。这一期内容其实很简单,几行代码就实现了手写new操作符。
一个细节
这里的call()也可以换为apply(),参数可以解构传递,也可以不解构传递。但是对于call来说,它只接收以逗号隔开的参数列表,故而需要`...args。
总结
本期内容我们讲了:
- new的执行机制
- 修改实例对象的隐式原型指向
- 修改构造函数的this指向
- new的自定义实现
- 一个细节
好久没有写文章了,不过接下来两个月都会foucs,如果你觉得这篇文章还不错,请给个小赞,这将是我持续创作的动力,感谢!