new 一个函数, 都会发生什么?
- 创建一个新的空对象
- 将构造函数的作用域赋给新对象(因此
this
就指向了这个新对象) - 执行构造函数中的代码(为这个新对象添加属性)
- 如果这个函数有返回值, 则返回; 否则默认返回新对象
new的实现
由于无法模拟 JavaScript 的关键字, 我们创建一个 myNew 函数来模拟。
在这个函数中, 第一个参数是构造函数,第二个参数开始, 是构造函数中的参数。
我们首先用一个使用new的例子作为测试:
function Person(name, sex) {
this.name = name
this.sex = sex
this.sayHello = function() {
console.log(this.name + ': hello')
}
}
var p = new Person('zhangsan', male)
p.sayHello() // zhangsan: hello
console.log(p.sex) // male
console.log(p.__proto__ === Person.prototype) // true
接下来开始实现myNew()
第一步
创建一个新的对象
function myNew() {
var obj = {}
}
第二步
把新对象的构造原型链 连接到构造函数的原型对象上, 从而新对象就可以访问构造函数中的属性和方法。
构造函数是我们传入的第一个参数。由于 arguments
是类数组, 我们不能直接使用 shift()
,我们可以使用call
来调用Array
中的shift
方法,获取构造函数 construc:
var construc = Array.prototype.shift.call(arguments)
由于第一步中创建对象的原型指向Object.prototype
, 而我们需要将原型指向构造函数的原型,所以我们使用Object.create()
来创建对象。Object.create(object, objectProps)
这种方法第一个参数就是要创建的对象的原型,如果赋值为null
则得到的 obj 没有任何原型。
我们想要新对象使用构造函数的原型,只需要这样做:
var obj = Object.create(construc.prototype)
第二步完成时, 我们的代码如下:
function myNew() {
var construc = Array.prototype.shift.call(arguments)
var obj = Object.create(construc.prototype)
}
第三步
执行构造函数中的代码(为这个新对象添加属性):
construc.apply(obj, arguments)
第四步
如果这个函数有返回值, 则返回; 否则默认返回新对象。
首先,要获取这个返回值:
var res = construc.apply(obj, arguments)
但是,new
这个关键字, 并不是所有返回值都原封不动的返回的。如果返回的是 undefined, null 以及基本类型的时候,都会返回新的对象;而只有返回对象的时候,才会返回构造函数的返回值。
因此, 我们要判断 res
是不是 object 类型,如果是 object 类型,返回 res,否则返回obj。
return res instanceof Object ? res : obj
现在我们就完整的实现了myNew()
function myNew() {
var construc = Array.prototype.shift.call(arguments)
var obj = Object.create(construc .prototype)
var res = myConstructor.apply(obj, arguments)
return res instanceof Object ? res : obj
}
// 测试
var p = myNew(Person, 'zhangsan', 'male')
p.sayHello() // zhangsan: hello
console.log(p.sex) // male
console.log(p.__proto__ === Person.prototype) // true