一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情。
大家经常使用构造函数,那么大家是否清楚new一个函数的过程发生了什么呢?是否尝试过自己写一个函数来代替new来更好的理解它呢?
内置new代码
function Foo(name){
this.name = name
}
Foo.prototype.getName = function(){
console.log(this.name)
return this.name
}
const foo = new Foo("dog");
foo.getName()
上述代码执行的结果是“dog”。
new 的过程
如果想要重新写一个函数来代替new运算符,我们最好先弄清楚new的过程发生了什么:
- 创建一个空对象obj,对象添加属性__proto__ ,将该属性链接至构造函数的原型对象
- 把函数执行,将this指向对象obj
- 处理返回值
- 如果该函数返回了对象、函数,那么返回该对象
- 如果该函数返回了基本类型值,那么返回this(obj)
重写new
//1.创建一个新的函数_new,传入参数Ct(构造函数名字)和其他参数
function _new(Ct, ...params){
//2. 在对象内创建一个函数,并且添加__proto__属性
let obj = {};
obj.__proto__ = Ct.prototype;
//3. 执行函数
let res = Ct.call(obj, ...params);
//4. 处理返回值
//4.1返回值是函数或者正常对象,typeof null值为object
if(res != null && (typeof(res)== 'function' || typeof(res)== 'object')){
return res
}
//4.2返回值是基本类型值
return obj
}
注意步骤3中使用了call改变了this的指向,不要忘记参数的传递。当然也可以使用apply方法
验证
_new (Foo,"cat").getName()执行结果是“cat”,符合预期。
优化
虽然结果满足要求了,而且是按照MDN上的说明步骤写的代码,但是我们还要考虑一下兼容性。因为印象中IE对__proto__的支持似乎不太友好,检查发现IE6-10都不支持。
于是想到了用另外的办法,create方法
虽然这个方法在其他浏览器上也会存在不支持的情况,但那些版本基本上存世要比IE6-8少得多。所以采取舍弃其他浏览器低版本的支持而去支持IE9-10.我感觉这个是比较“划算”的。
代码修改如下:
function _new2(Ct, ...params){
let obj = Object.create(Ct.prototype);
let res = Ct.call(obj, ...params);
if(res != null && (typeof(res)== 'function' || typeof(res)== 'object')){
return res
}
return obj
}
_new2 (Foo,"man").getName()
代码执行结果符合预期。
总结
如果想要重写内置new的话,首先要了解new的过程发生了什么,这就可以去看一下MDN等说明,但是在实际代码书写的时候还要考虑代码在浏览器上的兼容性,适当的做出取舍。