new 的作用是什么❓
老规矩,上 Demo:
function Test(name) {
this.name = name
}
Test.prototype.sayName = function () {
console.log(this.name)
}
const t = new Test('yck')
console.log(t.name) // 'yck'
t.sayName() // 'yck'
从上面一个例子中我们可以得出这些结论:
- new 通过构造函数 Test 创建出来的实例可以访问到构造函数原型对象的属性;
- new 通过构造函数 Test 创建出来的实例可以访问到构造函数原型链中的属性,也就是说通过 new 操作符,实例与构造函数通过原型链连接了起来;
但是当下的构造函数 Test 并没有显式 return 任何值(默认返回 undefined),如果我们让它返回值会发生什么事情呢?
function Test(name) {
this.name = name
return 1
}
const t = new Test('yck')
console.log(t.name) // 'yck'
虽然上述例子中的构造函数中返回了 1,但是这个返回值并没有任何的用处,得到的结果还是和之前的例子完全一样。
那么通过这个例子,我们又可以得出一个结论:
- 构造函数如果返回原始值(虽然例子中只有返回了 1,但是你可以试试其他的原始值,结果还是一样的),那么这个返回值毫无意义。
试完了返回原始值,我们再来试试返回对象会是怎样
function Test(name) {
this.name = name
console.log(this) // Test { name: 'yck' }
return { age: 26 }
}
const t = new Test('yck')
console.log(t) // { age: 26 }
console.log(t.name) // 'undefined'
通过这个例子我们可以发现,虽然构造函数内部的 this 还是依旧正常工作的,但是当返回值为对象时,这个返回值就会被正常的返回出去。
那么通过这个例子,我们再次得出了一个结论:
- 构造函数如果返回值为对象,那么这个返回值会被正常使用
这两个例子告诉了我们一点,构造函数尽量不要返回值。因为返回原始值不会生效,返回对象会导致 new 操作符没有作用。
通过以上几个例子,相信大家也大致了解了 new 操作符的作用了,接下来我们就来根据这些作用手动实现 new 操作符。
手动实现 new 操作符
首先我们再来回顾下 new 操作符的几个作用:
- new 操作符会返回一个对象,所以我们需要在内部创建一个对象;
- 这个对象,能够拥有构造函数中的 this 上的任意属性,即初始化实例对象的属性和方法;
- 这个对象可以访问到构造函数原型上的属性,所以需要将对象与构造函数链接起来;
- 返回原始值(包括null和undefined)需要忽略,返回对象需要正常处理;
接受洗礼的时刻到了,上菜:
function create(Con, ...args) {
let obj = {}
Object.setPrototypeOf(obj, Con.prototype)
let result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}
这就是一个完整的实现代码,我们通过以下几个步骤实现了它:
- 首先函数接受不定量的参数,第一个参数为构造函数,接下来的参数传入构造函数;
- 然后内部创建一个空对象 obj;
- 因为 obj 对象需要访问到构造函数原型链上的属性,所以我们通过 setPrototypeOf 将两者联系起来。这段代码等同于 obj.proto = Con.prototype,也可使用 Object.create(Con.prototype)
- 将 obj 绑定到构造函数的 this 指针上,并且传入剩余的参数,实现 this 指针的绑定,完成 obj 属性的初始化;(构造函数中 this 上的所有属性都复制一份到 obj 上);
- 判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 obj,这样就实现了忽略构造函数返回的原始值;