JavaScript中new关键字的实现--JS基础篇(七)

362 阅读5分钟

写在前面

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();

image.png

在浏览器控制台使用Object.getPrototypeOf(arr)能够查看实例对象的原型(即[[prototype]]属性所指向的对象),上图这些也就是构造函数Array的原型对象上的方法。

也就是说,创建实例对象时,new操作符必须要做的是:将新对象的隐式原型(.__proto__)指向构造函数的显式原型(Array),这样实例对象才能够去共享构造函数的原型上的方法和属性。

那么new还做了什么事呢?我们接着分析。

在给实例对象添加属性或者方法的时候,我们还可以直接为构造函数传递一个参数,例如:

let arr = new Array(10);

这表示我需要一个长度为10的空数组。我们都知道,构造函数Array()的原型上有length这个属性,表示数组的长度。所以当我们往构造函数里面传递长度后,new操作符内部能够准确将将新对象数组的长度也改为10。

image.png

有意思的来了,为什么new操作符能够在构造函数Array()中修改实例对象arrlength属性呢?且这里应该是只修改了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,如果你觉得这篇文章还不错,请给个小赞,这将是我持续创作的动力,感谢!

本人拙见,若有错误,敬请指正。