可以點擊上方的話題JS基礎系列
,查看往期文章
這篇文章寫于2018年11月05日
,new模擬實現
,Object.create
是面試高頻考點,之前發布在掘金有近2萬人閱讀,現在發布到公眾號聲明原創。
1. 前言
這是面試官問系列的第一篇,旨在幫助讀者提升JS
基礎知識,包含new、call、apply、this、繼承
相關知識。面試官問系列
文章如下:感興趣的讀者可以點擊閱讀。
1.面試官問:能否模擬實現JS的new操作符
2.面試官問:能否模擬實現JS的bind方法
3.面試官問:能否模擬實現JS的call和apply方法
4.面試官問:JS的this指向
5.面試官問:JS的繼承
用過Vuejs
的同學都知道,需要用new
操作符來實例化。
new?Vue({el:?'#app',mounted(){},
});
那么面試官可能會問是否想過new
到底做了什么,怎么模擬實現呢。
附上之前寫文章寫過的一段話:已經有很多模擬實現
new
操作符的文章,為什么自己還要寫一遍呢。學習就好比是座大山,人們沿著不同的路登山,分享著自己看到的風景。你不一定能看到別人看到的風景,體會到別人的心情。只有自己去登山,才能看到不一樣的風景,體會才更加深刻。
2. new 做了什么
先看簡單例子1:
//?例子1
function?Student(){
}
var?student?=?new?Student();
console.log(student);?//?{}
// student 是一個對象。
console.log(Object.prototype.toString.call(student));?//?[object?Object]
//?我們知道平時聲明對象也可以用new?Object();?只是看起來更復雜
//?順便提一下?`new?Object`(不推薦)和Object()也是一樣的效果
//?可以猜測內部做了一次判斷,用new調用
/**?if?(!(this?instanceof?Object))?{
*????return?new?Object();
*??}
*/
var?obj?=?new?Object();
console.log(obj)?//?{}
console.log(Object.prototype.toString.call(student));?//?[object?Object]typeof?Student?===?'function'?//?true
typeof?Object?===?'function'?//?true
從這里例子中,我們可以看出:一個函數用new
操作符來調用后,生成了一個全新的對象。而且Student
和Object
都是函數,只不過Student
是我們自定義的,Object
是JS
本身就內置的。再來看下控制臺輸出圖,感興趣的讀者可以在控制臺試試。
與new Object()
生成的對象不同的是new Student()
生成的對象中間還嵌套了一層__proto__
,它的constructor
是Student
這個函數。
//?也就是說:
student.constructor?===?Student;
Student.prototype.constructor?===?Student;
2.1 小結1:從這個簡單例子來看,new
操作符做了兩件事:
創建了一個全新的對象。
這個對象會被執行
[[Prototype]]
(也就是__proto__
)鏈接。
接下來我們再來看升級版的例子2:
//?例子2
function?Student(name){console.log('賦值前-this',?this);?//?{}this.name?=?name;console.log('賦值后-this',?this);?//?{name:?'若川'}
}
var?student?=?new?Student('若川');
console.log(student);?//?{name:?'若川'}
由此可以看出:這里Student
函數中的this
指向new Student()
生成的對象student
。
2.2 小結2:從這個例子來看,new
操作符又做了一件事:
生成的新對象會綁定到函數調用的
this
。
接下來繼續看升級版例子3:
//?例子3
function?Student(name){this.name?=?name;//?this.doSth();
}
Student.prototype.doSth?=?function()?{console.log(this.name);
};
var?student1?=?new?Student('若');
var?student2?=?new?Student('川');
console.log(student1,?student1.doSth());?//?{name:?'若'}?'若'
console.log(student2,?student2.doSth());?//?{name:?'川'}?'川'
student1.__proto__?===?Student.prototype;?//?true
student2.__proto__?===?Student.prototype;?//?true
//?__proto__?是瀏覽器實現的查看原型方案。
//?用ES5 則是:
Object.getPrototypeOf(student1)?===?Student.prototype;?//?true
Object.getPrototypeOf(student2)?===?Student.prototype;?//?true
例子3 控制臺輸出圖
關于JS的原型關系我之前看到這張圖,覺得很不錯,分享給大家。
2.3 小結3:這個例子3再一次驗證了小結1中的第2點。也就是這個對象會被執行[[Prototype]]
(也就是__proto__
)鏈接。并且通過new Student()
創建的每個對象將最終被[[Prototype]]
鏈接到這個Student.protytype
對象上。
細心的同學可能會發現這三個例子中的函數都沒有返回值。那么有返回值會是怎樣的情形呢。那么接下來請看例子4
//?例子4
function?Student(name){this.name?=?name;//?Null(空)?null//?Undefined(未定義)?undefined//?Number(數字)?1//?String(字符串)'1'//?Boolean(布爾)?true//?Symbol(符號)(第六版新增)?symbol//?Object(對象)?{}//?Function(函數)?function(){}//?Array(數組)?[]//?Date(日期)?new?Date()//?RegExp(正則表達式)/a///?Error?(錯誤)?new?Error()//?return?/a/;
}
var?student?=?new?Student('若川');
console.log(student);?{name:?'若川'}
我測試這七種類型后MDN JavaScript類型,得出的結果是:前面六種基本類型都會正常返回{name: '若川'}
,后面的Object
(包含Functoin
, Array
, Date
, RegExg
, Error
)都會直接返回這些值。
2.4 由此得出 小結4:
如果函數沒有返回對象類型
Object
(包含Functoin
,Array
,Date
,RegExg
,Error
),那么new
表達式中的函數調用會自動返回這個新的對象。
結合這些小結,整理在一起就是:
創建了一個全新的對象。
這個對象會被執行
[[Prototype]]
(也就是__proto__
)鏈接。生成的新對象會綁定到函數調用的
this
。通過
new
創建的每個對象將最終被[[Prototype]]
鏈接到這個函數的prototype
對象上。如果函數沒有返回對象類型
Object
(包含Functoin
,Array
,Date
,RegExg
,Error
),那么new
表達式中的函數調用會自動返回這個新的對象。
3. new 模擬實現
知道了這些現象,我們就可以模擬實現new
操作符。直接貼出代碼和注釋
/***?模擬實現?new?操作符*?@param??{Function}?ctor?[構造函數]*?@return?{Object|Function|Regex|Date|Error}??????[返回結果]*/
function?newOperator(ctor){if(typeof?ctor?!==?'function'){throw?'newOperator?function?the?first?param?must?be?a?function';}//?ES6?new.target?是指向構造函數newOperator.target?=?ctor;//?1.創建一個全新的對象,//?2.并且執行[[Prototype]]鏈接// 4.通過`new`創建的每個對象將最終被`[[Prototype]]`鏈接到這個函數的`prototype`對象上。var?newObj?=?Object.create(ctor.prototype);//?ES5?arguments轉成數組?當然也可以用ES6?[...arguments],?Aarry.from(arguments);//?除去ctor構造函數的其余參數var?argsArr?=?[].slice.call(arguments,?1);// 3.生成的新對象會綁定到函數調用的`this`。//?獲取到ctor函數返回結果var?ctorReturnResult?=?ctor.apply(newObj,?argsArr);//?小結4?中這些類型中合并起來只有Object和Function兩種類型?typeof?null?也是'object'所以要不等于null,排除nullvar?isObject?=?typeof?ctorReturnResult?===?'object'?&&?ctorReturnResult?!==?null;var?isFunction?=?typeof?ctorReturnResult?===?'function';if(isObject?||?isFunction){return?ctorReturnResult;}// 5.如果函數沒有返回對象類型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),那么`new`表達式中的函數調用會自動返回這個新的對象。return?newObj;
}
最后用模擬實現的newOperator
函數驗證下之前的例子3:
//?例子3?多加一個參數
function?Student(name,?age){this.name?=?name;this.age?=?age;//?this.doSth();//?return?Error();
}
Student.prototype.doSth?=?function()?{console.log(this.name);
};
var?student1?=?newOperator(Student,?'若',?18);
var?student2?=?newOperator(Student,?'川',?18);
//?var?student1?=?new?Student('若');
//?var?student2?=?new?Student('川');
console.log(student1,?student1.doSth());?//?{name:?'若'}?'若'
console.log(student2,?student2.doSth());?//?{name:?'川'}?'川'student1.__proto__?===?Student.prototype;?//?true
student2.__proto__?===?Student.prototype;?//?true
//?__proto__?是瀏覽器實現的查看原型方案。
//?用ES5 則是:
Object.getPrototypeOf(student1)?===?Student.prototype;?//?true
Object.getPrototypeOf(student2)?===?Student.prototype;?//?true
可以看出,很符合new
操作符。讀者發現有不妥或可改善之處,歡迎指出。回顧這個模擬new
函數newOperator
實現,最大的功臣當屬于Object.create()
這個ES5
提供的API
。
4. Object.create() 用法舉例
我之前整理的一篇文章中也有講過,可以翻看JavaScript 對象所有API解析
MDN Object.create()
Object.create(proto, [propertiesObject])
方法創建一個新對象,使用現有的對象來提供新創建的對象的__proto__。它接收兩個參數,不過第二個可選參數是屬性描述符(不常用,默認是undefined
)。
var?anotherObject?=?{name:?'若川'
};
var?myObject?=?Object.create(anotherObject,?{age:?{value:18,},
});
//?獲得它的原型
Object.getPrototypeOf(anotherObject)?===?Object.prototype;?//?true?說明anotherObject的原型是Object.prototype
Object.getPrototypeOf(myObject);?//?{name:?"若川"}?//?說明myObject的原型是{name:?"若川"}
myObject.hasOwnProperty('name');?// false;?說明name是原型上的。
myObject.hasOwnProperty('age');?//?true?說明age是自身的
myObject.name;?//?'若川'
myObject.age;?//?18;
對于不支持ES5
的瀏覽器,MDN
上提供了ployfill
方案。
if?(typeof?Object.create?!==?"function")?{Object.create?=?function?(proto,?propertiesObject)?{if?(typeof?proto?!==?'object'?&&?typeof?proto?!==?'function')?{throw?new?TypeError('Object?prototype?may?only?be?an?Object:?'?+?proto);}?else?if?(proto?===?null)?{throw?new?Error("This?browser's?implementation?of?Object.create?is?a?shim?and?doesn't?support?'null'?as?the?first?argument.");}if?(typeof?propertiesObject?!=?'undefined')?throw?new?Error("This?browser's?implementation?of?Object.create?is?a?shim?and?doesn't?support?a?second?argument.");function?F()?{}F.prototype?=?proto;return?new?F();};
}
到此,文章就基本寫完了。感謝讀者看到這里。
5. 最后總結一下:
new
做了什么:
創建了一個全新的對象。
這個對象會被執行
[[Prototype]]
(也就是__proto__
)鏈接。生成的新對象會綁定到函數調用的
this
。通過
new
創建的每個對象將最終被[[Prototype]]
鏈接到這個函數的prototype
對象上。如果函數沒有返回對象類型
Object
(包含Functoin
,Array
,Date
,RegExg
,Error
),那么new
表達式中的函數調用會自動返回這個新的對象。
怎么模擬實現
//?去除了注釋
function?newOperator(ctor){if(typeof?ctor?!==?'function'){throw?'newOperator?function?the?first?param?must?be?a?function';}newOperator.target?=?ctor;var?newObj?=?Object.create(ctor.prototype);var?argsArr?=?[].slice.call(arguments,?1);var?ctorReturnResult?=?ctor.apply(newObj,?argsArr);var?isObject?=?typeof?ctorReturnResult?===?'object'?&&?ctorReturnResult?!==?null;var?isFunction?=?typeof?ctorReturnResult?===?'function';if(isObject?||?isFunction){return?ctorReturnResult;}return?newObj;
}
讀者發現有不妥或可改善之處,歡迎指出。另外覺得寫得不錯,可以點個贊,也是對我的一種支持。
推薦閱讀
我在阿里招前端,我該怎么幫你?(現在還可以加模擬面試群)
如何拿下阿里巴巴 P6 的前端 Offer
如何準備阿里P6/P7前端面試--項目經歷準備篇
大廠面試官常問的亮點,該如何做出?
如何從初級到專家(P4-P7)打破成長瓶頸和有效突破
若川知乎問答:2年前端經驗,做的項目沒什么技術含量,怎么辦?
若川知乎高贊:有哪些必看的 JS庫?
末尾
你好,我是若川,江湖人稱菜如若川,歷時一年只寫了一個學習源碼整體架構系列~(點擊藍字了解我)
關注
若川視野
,回復"pdf" 領取優質前端書籍pdf,回復"1",可加群長期交流學習我的博客地址:https://lxchuan12.gitee.io?歡迎收藏
覺得文章不錯,可以點個
在看
呀^_^另外歡迎留言
交流~
精選前端好文,伴你不斷成長
我是若川歡迎關注!可點擊
小提醒:若川視野公眾號面試、源碼等文章合集在菜單欄中間
【源碼精選】
按鈕,歡迎點擊閱讀,也可以星標我的公眾號,便于查找