想要手寫一個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。
額,暫時就這樣吧...