new操作符是JavaScript中实例化对象时使用的操作符。自己动手实现一个new,能帮我们理解它背后的机理。
前情提要
本问题讨论基于如下类定义(人有名字,“说名字”在原型上):
function Person(name) {
this.name = name
}
Person.prototype.sayMyName = function () {
console.log(this.name)
}
原理分析
我们知道,对象是形如{k: v}
的键值对的集合,其中v
可为各种类型。
如果用空对象x = {}
调用构造函数,则构造函数做的this.name = name
,就相当于x.name = name
,即给x
新增了name
属性并赋值。这便是在”构造“(或者说构建、填充)这一对象的过程。
用x
调用构造函数后,x
确实有了name
属性,但却不一定是Person——小猫小狗也可以有名字。因此,我们需要指定x
的原型为Person,相当于说明x
是按Person的坯子制造出来的。
在指定了原型后,自然而然的,x
也可以调用原型链上的方法如sayMyName
了。
代码实现
根据上面的分析,不难写出如下代码:
function New(cons, ...args) { // cons为构造函数,...args为构造函数的参数
let x = {}
cons.apply(x, args) // 应用构造函数,填充属性
x.__proto__ = cons.prototype // 修改原型。注意对象上的是__proto__,构造函数(类)上的是prototype
// 注:形如__xxx__的属性显然是不希望被直接修改的属性,因此平时没有必要,就不要操作__proto__
return x
}
进行测试:
let p = New(Person, 'Bob')
console.log(p) // { name: 'Bob', ... }
console.log(p instanceof Person) // true
一点小问题
虽然很少这么做,但还是要提到:构造函数中可以中途返回值!
如果这个值是基本类型,则单纯退出构造函数,不再执行下面的构造操作,也就是这个对象只构造到当前状态;如果这个值是引用数据类型,例如一个对象,则构造的结果就是这个对象,之前的所有构造操作都失去作用。
因此,代码实现中可能出现应用x = cons.apply({}, args)
的情况,可以考虑进行特别处理。