[toc]
分析:new做了什麼
new
操作符通過執行自定義構造函數或者js內置構造函數,從而生成一個實例對象。
mdn上把內部操作大概分爲4步:
- 創建一個空的簡單JavaScript對象(即{ } );
- 鏈接該對象(即設置該對象的構造函數)到另一個對象 ;
- 將步驟1新創建的對象作爲this的上下文 ;
- 如果該函數沒有返回對象,則返回this。
通過一個簡單的demo感受下上面的步驟
function Person (name){
this.name = name
}
let p = new Person('jack');
console.log(`p:`, p); // { name: 'jack' }
console.log(`p.__proto__===Person.prototype:`,p.__proto__===Person.prototype); //true
可以看到new操作符執行Person構造函數後,返回了一個內部創建的新對象,並且以這個對象爲上線文環境執行了一遍Person函數,最後將其返回,同時對象p的原型屬性指向構造函數的原型,這樣也就保證了實例能夠訪問在構造函數原型中定義的屬性和方法。
上面的demo中構造函數是沒有返回值的,如果說構造函數有返回值呢,如下
function Person (name){
this.name = name;
return {age: 18}
}
let p = new Person('jack');
console.log(`p:`, p); // { age: 18 }
如果構造函數最後返回了一個對象,就會直接將其返回,而不是內部創建的新對象。
經過測試發現,除了返回對象,如果返回其他類型,只要最後返回的類型爲引用類型object
或者function
(Function
,Object
,Array
,Date
,Error
,Regexp
,要排除null
,因爲typeof null === 'object'
)就會直接將其返回,而其他基本類型都會返回內部新創建的對象。
自定義實現
這裏我們嘗試通過封裝一個myNew
方法模擬new操作符的主要功能:接受若干參數,第一個參數爲構造函數ctr
,其餘爲構造器所需參數,myNew(ctr, arg1, arg2,...)
第一步
這裏的第一步把mdn中的1、2步放在了一起:創建一個新對象,並將其__proto__
屬性指向構造函數的prototype
屬性
function myNew(ctr) {
let obj = Object.create(ctr.prototype);
}
也可以使用如下方法
function myNew (ctr){
let obj = {};
obj.__proto__ = ctr.prototype;
}
第二步
獲取到參數之後,以內部新創建的對象obj
爲上線文執行構造函數,作用是爲obj
賦值
function myNew(ctr) {
let obj = Object.create(ctr.prototype);
const args = [].slice.call(arguments, 1);
let result = ctr.apply(obj, args);
console.log(`obj:`,obj);
}
上面的const args = [].slice.call(arguments, 1);
用於將arguments
類數組轉爲數組並獲取參數,也可以通過Array.form(arguments).slice(1)
或者[...arguments].slice(1)
實現。
第三步
對執行構造函數後的返回值result
做兼容處理。
如果構造函數最終返回對象、函數、數組、日期等其他引用類型及Symbol,會將其直接返回,其他基本類型及null
、undefined
會返回內部新創建的對象實例。
function myNew(fn) {
let obj = Object.create(fn.prototype);
const args = [].slice.call(arguments, 1);
let result = fn.apply(obj, args);
var isObj = (typeof result === 'object' && result !== null);
var isFn = typeof result === 'function';
return (isObj || isFn) ? result : obj;
}
測試
最後,簡單測試一下
沒有返回值
function Person(name) {
this.name = name;
}
let p = myNew(Person,'jack');
console.log(`p:`,p);
有返回值
function Person(name) {
this.name = name;
return {age: 33}
}
let p = myNew(Person,'jack');
console.log(`p:`,p);