想要手写一个new操作符,首先得知道new到底做了什么?new一个构造函数,最终我们得到的是一个对象,那么我们从工厂模式和构造函数模式上来看看这背后的发生了什么。
首先,我们先来看工厂模式下的一个函数,创建一个新的对象:
// 工厂模式
function person (name, age, gender) {
var obj = {};
obj.name = name;
obj.age = age;
obj.gender = gender;
return obj;
}
// 使用工厂函数创建一个新对象
const p0 = person('Mary', 18, 'female');
// 这样我们得到一个新的对象 {name: 'Mary', age: 18, gender: 'famale'}
然后,我们再看构造函数模式下创建一个新的对象:
// 构造函数模式下
function Person (name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
// 使用构造函数创建一个对象
const p1 = new Person('Jerry', 20, 'male');
// 这样我们可以得到一个新的对象 {name: 'Jerry', age: 20, gender: 'male'}
再然后,对比这两种模式,似乎看到些许奥秘。this。
可以看到,构造函数中,对象出现的地方都用了 this 进行了替代,并且在new的时候,直接可以把这个对象return出来。
到了这里,我们看到了 new 操作符其中的一个作用,就是创建了一个新的对象,并且把构造函数中的this(执行环境)给了这个新建的这个对象。
然后,我们再看,使用构造函数还可能用到一些公共的属性和方法,也就是会在prototype上定义一些公共的方法:
// 在原型上定义一些公共方法,以便所有的实例对象都可以使用
// 接上
Person.prototype = {
sayName: function () {
console.log(this.name)
}
}
const p1 = new Person('Jerry', 20, 'male');
p1.sayName(); // -> 'Jerry'
// p1 这个实例,可以使用构造函数原型上的方法。
可以看到,利用构造函数创建的这个实例对象,可以访问到构造函数原型上的方法。那么 new 一下,让实例对象有了这个能力。这是怎么做到的? 立马可以想到 原型链。也就是说,在new的时候,js底层手动的将新创建的这个对象的原型链等于了这个构造函数的原型,用代码表示:
// -> 用代码表示一下就是:
p1.__proto__ = Person.prototype;
// 这样原型链打通之后,就可以直接访问 构造函数原型上的属性和方法了;
至此,可以得到 new 操作符最重要和核心的两个功能:
- 创建一个新的对象,并分配给构造函数的this
- 手动把新建对象的原型链分配给构造函数的prototype
那么,搞清了new主要干了什么事情之后,就可以开始手写new了:
// 开始手写 new方法
function myNew (func, ...args) {
//1、新建一个新的对象
var obj = {};
//2、手动改变新建对象的原型链
obj.__proto__ = func.prototype;
//3、改变this指向 执行构造函数
func.call(obj, ...args);
//4、返回这个新对象
return obj;
}
想起创建对象还有个方法 Object.create() : 方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。附上 MDN 方便查看: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
可以再简写一下:
// 利用 Object.create()方法 简化一下
function myNew (func, ...args) {
var obj = Object.create(func.prototype);
func.call(obj, ...args);
return obj;
}
附加题: 这里改变this指向的时候只是用了call,但是传参的 args 是一个数组,那么为什么这里不直接用 apply?这只是因为 call的性能会比apply好。为什么呢? 附上github上issue : https://github.com/noneven/__/issues/6。
额,暂时就这样吧...