面試官很忙,但我不單純是蹭熱點,今天聊的主題絕對是面試中命中率很高的知識點。我在複習javascript函數這塊知識時,注意到一個有意思的點,就是構造函數顯式return,並由此引起了一波頭腦風暴......javascript
咱們知道,若是不作特殊處理,new構造函數時會發生下面這幾步。前端
__proto__
屬性指向構造函數的
prototype
屬性
this
指向這個新對象
this
給新對象添加新的成員屬性或方法。
下面咱們來驗證下:java
function Test() {
console.log(JSON.stringify(this)); console.log(this.__proto__.constructor === Test); this.name = 'jack'; this.age = 18; console.log(JSON.stringify(this)); } var a = new Test(); // Chrome控制檯會輸出如下內容 // {} // true // {"name":"jack","age":18} 複製代碼
這徹底符合咱們的認知,沒毛病。web
那麼在認識到new實例化過程的幾個關鍵步驟後,咱們也能解答一道面試中常見的題目:如何實現一個new?面試
實現一個new也就意味着不能用new關鍵字,那麼要完成這麼一系列步驟,固然是經過函數實現了。app
// func是構造函數,...args是須要傳給構造函數的參數
function myNew(func, ...args) { // 建立一個空對象,而且指定原型爲func.prototype var obj = Object.create(func.prototype); // new構造函數時要執行函數,同時指定this func.call(obj, ...args); // 最後return這個對象 return obj; } 複製代碼
以這四個關鍵步驟做爲指導思想,咱們很快就寫出了代碼實現。從這一點我也能體會到思路的重要性,別當工具人,代碼纔是工具!編輯器
從實現邏輯上來看沒什麼問題,咱們來驗證下。ide
function Test(name, age) {
this.name = name; this.age = age; } myNew(Test, '小明', 18); // Chrome控制檯會輸出如下內容 // Test {name: "小明", age: 18} 複製代碼
所謂顯式return,就是在構造函數中主動return一個對象,這裏說的對象不只包括Object
,也包含Array
,Date
等對象哦。函數
咱們能夠試一試:工具
function Test() {
this.name = 'jack'; this.age = 18; return { content: '我有freestyle' } } new Test(); // Chrome控制檯會輸出如下內容 // {content: "我有freestyle"} 複製代碼
那麼return一個普通類型數據有沒有用呢?好比字符串,數字?試試便知。
function Test() {
this.name = 'jack'; this.age = 18; return '我有freestyle' } new Test(); // Chrome控制檯會輸出如下內容 // Test {name: "jack", age: 18} 複製代碼
能夠看到,當咱們return一個普通類型數據時,不會影響結果,依然會返回new出來的這個新對象。
咱們也應該知道,new構造函數就是爲了建立對象,你return一個字符串之類的普通類型數據是沒有任何意義的,因此咱們的關注點應該是return一個特殊的對象。請接着往下看。
所謂「無new實例化」,就是指不經過new關鍵字實例化對象(固然,這裏說的不經過new,只是調用層面的,底層仍是用了new)。這一點咱們使用jQuery的時候已經體驗過了。
// 實例化了一個jQuery對象,可是沒有用到new
var ele = jQuery('<div>freestyle</div>'); 複製代碼
那麼這種黑科技是怎麼實現的呢?
前面已經提到了,咱們能夠在構造函數中經過顯式return來返回一個自定義的對象,那麼這裏就有發揮的空間了。咱們經過一個簡單的例子來感覺下:
function Shadow() {
this.name = 'jack'; this.age = 18; } function jQuery() { return new Shadow(); } var obj1 = jQuery(); console.log(obj1) // Chrome控制檯會輸出如下內容 // Shadow {name: "jack", age: 18} 複製代碼
jQuery()
用了移花接木的障眼法完成了對象實例化,一手隱藏的new Shadow()
讓咱們誤覺得不用new
直接調用函數也能建立實例。
咱們再來試下new jQuery()
,會發現,「臥槽,怎麼跟jQuery()
執行結果如出一轍!」
var obj2 = new jQuery();
console.log(obj2) // Chrome控制檯會輸出如下內容 // Shadow {name: "jack", age: 18} 複製代碼
這是由於new構造函數顯式return了new Shadow()
,這樣返回的結果也就是new Shadow()
實例化出來的對象,而不使用new
直接調用jQuery()
,只是把jQuery()
當成一個普通的函數執行,其結果不言而喻是new Shadow()
實例化出來的對象。
因此,這裏new jQuery()
和jQuery()
是等價的。
雖然jQuery已經用得愈來愈少,可是其設計思路很是值得咱們學習。那麼jQuery到底妙在哪裏?能夠說是不少,鏈式操做,插件體系這些特點都是咱們耳熟能詳的。不扯太多了,就讓咱們來簡單分析下jQuery實例化的過程。
我這裏拿到了jQuery v1.12.4版本的代碼,大概1W行,很舒服。
翻啊翻啊,翻到了第71行,看到了這麼一串代碼:
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context ); } 複製代碼
這不就是咱們熟悉的移花接木技術嗎?jQuery.fn.init
彷佛就是上面例子中的Shadow
。看着有點像了,可是仍是要好好研究下。
jQuery.fn是jQuery.prototype的別名,是爲了代碼簡潔的考慮。這一點參考源碼第91行就能夠知道。
jQuery.fn = jQuery.prototype = {
// ...... 複製代碼
咱們知道,若是僅僅經過new jQuery.fn.init(selector, context)
是存在一個問題的,問題就是獲得的實例不是jQuery
的實例,而是jQuery.fn.init
的實例。那麼如何處理這個問題呢?
咱們翻到源碼2866行,能夠看到:
init = jQuery.fn.init = function( selector, context, root ) {
// 建立實例的具體邏輯 } 複製代碼
具體init
方法怎麼建立一個jQuery對象,作了哪些判斷邏輯,這些都不是本文關注的重點。咱們須要關注的是,jQuery是如何保證明例化的對象的原型指向是正確的?否則實例化的對象如何使用jQuery.prototype
上面掛載的諸多方法呢,好比this.show()
、this.hide()
?
緊接着翻到2982行,我有了答案:
init.prototype = jQuery.fn;
複製代碼
妙啊,這一手修改原型指向的操做,完美解決了這個問題。這樣一來,new init()
獲得的實例天然也是jQuery
的實例。
jQuery.prototype.init.prototype === jQuery.prototype; // true
var a = $('<div>123</div>') a instanceof jQuery // true a instanceof jQuery.fn.init // true 複製代碼
這樣一來,咱們能夠獲得一個基本的設計思路:
function myModule(params) {
return new myModule.fn.init(params); } myModule.fn = myModule.prototype = { constructor: myModule } myModule.fn.init = function(params) { // 能夠對實例對象進行各類操做 } myModule.fn.init.prototype = myModule.prototype; 複製代碼
在這個基礎上,咱們能夠擴展靜態方法和原型方法,這個myModule模塊就變得愈來愈豐富。
妙啊,一個構造函數,讓我陷入了思考......扶我起來,我還能學!
本文使用 mdnice 靈動藍主題 排版