1. 前言
大家好,我是若川。好久以前我有寫過《面試官問系列》,旨在幫助讀者提升JS基礎知識,包含new、call、apply、this、繼承
相關知識。其中寫了 面試官問:this 指向 文章。在掘金等平臺收獲了還算不錯的反饋。
最近有小伙伴看我的 Vuex源碼 文章,提到有一處this
指向有點看不懂(好不容易終于有人看我的源碼文章了,感動的要流淚了^_^
)。于是我寫篇文章答疑解惑,簡單再說說 this
指向和尤大在 Vuex 源碼中
是怎么處理 this
指向丟失的。
2. 對象中的this指向
var?person?=?{name:?'若川',say:?function(text){console.log(this.name?+?',?'?+?text);}
}
console.log(person.name);
console.log(person.say('在寫文章'));?//?若川,?在寫文章
var?say?=?person.say;
say('在寫文章');?//?這里的this指向就丟失了,指向window了。(非嚴格模式)
3. 類中的this指向
3.1 ES5
//?ES5
var?Person?=?function(){this.name?=?'若川';
}
Person.prototype.say?=?function(text){console.log(this.name?+?',?'?+?text);
}
var?person?=?new?Person();
console.log(person.name);?//?若川
console.log(person.say('在寫文章'));
var?say?=?person.say;
say('在寫文章');?//?這里的this指向就丟失了,指向 window 了。
3.2 ES6
//?ES6
class?Person{construcor(name?=?'若川'){this.name?=?name;}say(text){console.log(`${this.name},?${text}`);}
}
const?person?=?new?Person();
person.say('在寫文章')
//?解構
const?{?say?}?=?person;
say('在寫文章');?//?報錯?this?,因為ES6?默認啟用嚴格模式,嚴格模式下指向?undefined
4. 尤大在Vuex源碼中是怎么處理的
先看代碼
class?Store{constructor(options?=?{}){this._actions?=?Object.create(null);//?bind?commit?and?dispatch?to?self//?給自己?綁定?commit?和?dispatchconst?store?=?thisconst?{?dispatch,?commit?}?=?this//?為何要這樣綁定??//?說明調用commit和dispach?的?this?不一定是?store?實例//?這是確保這兩個函數里的this是store實例this.dispatch?=?function?boundDispatch?(type,?payload)?{return?dispatch.call(store,?type,?payload)}this.commit?=?function?boundCommit?(type,?payload,?options)?{return?commit.call(store,?type,?payload,?options)}}dispatch(){console.log('dispatch',?this);}commit(){console.log('commit',?this);}
}
const?store?=?new?Store();
store.dispatch();?//?輸出結果 this 是什么呢?const?{?dispatch,?commit?}?=?store;
dispatch();?//?輸出結果 this 是什么呢?
commit();??//?輸出結果 this 是什么呢?

結論:非常巧妙的用了call
把dispatch
和commit
函數的this
指向強制綁定到store
實例對象上。如果不這么綁定就報錯了。
4.1 actions 解構 store
其實Vuex
源碼里就有上面解構const { dispatch, commit } = store;
的寫法。想想我們平時是如何寫actions
的。actions
中自定義函數的第一個參數其實就是 store
實例。
這時我們翻看下actions文檔
:https://vuex.vuejs.org/zh/guide/actions.html
const?store?=?new?Vuex.Store({state:?{count:?0},mutations:?{increment?(state)?{state.count++}},actions:?{increment?(context)?{context.commit('increment')}}
})
也可以用解構賦值的寫法。
actions:?{increment?({?commit?})?{commit('increment')}
}
有了Vuex
源碼構造函數里的call
綁定,這樣this
指向就被修正啦~不得不說祖師爺就是厲害。這一招,大家可以免費學走~
接著我們帶著問題,為啥上文中的context
就是store
實例,有dispatch
、commit
這些方法呢。繼續往下看。
4.2 為什么 actions 對象里的自定義函數 第一個參數就是 store 實例。
以下是簡單源碼,有縮減,感興趣的可以看我的文章 Vuex 源碼文章
class?Store{construcor(){//?初始化?根模塊//?并且也遞歸的注冊所有子模塊//?并且收集所有模塊的?getters?放在?this._wrappedGetters?里面installModule(this,?state,?[],?this._modules.root)}
}
接著我們看installModule
函數中的遍歷注冊 actions
實現
function?installModule?(store,?rootState,?path,?module,?hot)?{//?省略若干代碼//?循環遍歷注冊?actionmodule.forEachAction((action,?key)?=>?{const?type?=?action.root???key?:?namespace?+?keyconst?handler?=?action.handler?||?actionregisterAction(store,?type,?handler,?local)})
}
接著看注冊 actions
函數實現 registerAction
/**
*?注冊?mutation
*?@param?{Object}?store?對象
*?@param?{String}?type?類型
*?@param?{Function}?handler?用戶自定義的函數
*?@param?{Object}?local?local?對象
*/
function?registerAction?(store,?type,?handler,?local)?{const?entry?=?store._actions[type]?||?(store._actions[type]?=?[])//?payload?是actions函數的第二個參數entry.push(function?wrappedActionHandler?(payload)?{/***?也就是為什么用戶定義的actions中的函數第一個參數有*??{?dispatch,?commit,?getters,?state,?rootGetters,?rootState?}?的原因*?actions:?{*????checkout?({?commit,?state?},?products)?{*????????console.log(commit,?state);*????}*?}*/let?res?=?handler.call(store,?{dispatch:?local.dispatch,commit:?local.commit,getters:?local.getters,state:?local.state,rootGetters:?store.getters,rootState:?store.state},?payload)//?源碼有刪減
}
比較容易發現調用順序是 new Store() => installModule(this) => registerAction(store) => let res = handler.call(store)
。
其中handler
就是 用戶自定義的函數,也就是對應上文的例子increment
函數。store
實例對象一路往下傳遞,到handler
執行時,也是用了call
函數,強制綁定了第一個參數是store
實例對象。
actions:?{increment?({?commit?})?{commit('increment')}
}
這也就是為什么 actions
對象中的自定義函數的第一個參數是 store
對象實例了。
好啦,文章到這里就基本寫完啦~相對簡短一些。應該也比較好理解。
最后再總結下 this 指向
摘抄下面試官問:this 指向文章結尾。
如果要判斷一個運行中函數的 this
綁定, 就需要找到這個函數的直接調用位置。找到之后 就可以順序應用下面這四條規則來判斷 this
的綁定對象。
new
調用:綁定到新創建的對象,注意:顯示return
函數或對象,返回值不是新創建的對象,而是顯式返回的函數或對象。call
或者apply
( 或者bind
) 調用:嚴格模式下,綁定到指定的第一個參數。非嚴格模式下,null
和undefined
,指向全局對象(瀏覽器中是window
),其余值指向被new Object()
包裝的對象。對象上的函數調用:綁定到那個對象。
普通函數調用:在嚴格模式下綁定到
undefined
,否則綁定到全局對象。
ES6
中的箭頭函數:不會使用上文的四條標準的綁定規則, 而是根據當前的詞法作用域來決定this
, 具體來說, 箭頭函數會繼承外層函數,調用的 this 綁定( 無論 this 綁定到什么),沒有外層函數,則是綁定到全局對象(瀏覽器中是window
)。這其實和 ES6
之前代碼中的 self = this
機制一樣。
最近組建了一個江西人的前端交流群,如果你是江西人可以加我微信?ruochuan12?私信 江西?拉你進群。
推薦閱讀
我在阿里招前端,該怎么幫你(可進面試群)
我讀源碼的經歷
在字節做前端一年后,有啥收獲~
老姚淺談:怎么學JavaScript?
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》多篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,活躍在知乎@若川,掘金@若川。致力于分享前端開發經驗,愿景:幫助5年內前端人走向前列。
識別上方二維碼加我微信、長期交流學習
今日話題
略。歡迎分享、收藏、點贊、在看我的公眾號文章~