你們好,我是若川。歡迎加我微信ruochuan12
,關注個人公衆號若川視野,長期交流學習。很久之前我有寫過《面試官問系列》,旨在幫助讀者提高JS基礎知識,包含new、call、apply、this、繼承
相關知識。其中寫了 面試官問:this 指向 文章。在掘金等平臺收穫了還算不錯的反饋。html
最近有小夥伴看個人 Vuex源碼 文章,提到有一處this
指向有點看不懂(好不容易終於有人看個人源碼文章了,感動的要流淚了^_^
)。因而我寫篇文章答疑解惑,簡單再說說 this
指向和尤大在 Vuex 源碼中
是怎麼處理 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了。(非嚴格模式)
複製代碼
// 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 了。
複製代碼
// 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
複製代碼
先看代碼vue
class Store{
constructor(options = {}){
this._actions = Object.create(null);
// bind commit and dispatch to self
// 給本身 綁定 commit 和 dispatch
const store = this
const { 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
實例對象上。若是不這麼綁定就報錯了。git
其實Vuex
源碼裏就有上面解構const { dispatch, commit } = store;
的寫法。想一想咱們平時是如何寫actions
的。actions
中自定義函數的第一個參數其實就是 store
實例。github
這時咱們翻看下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')
}
}
})
複製代碼
也能夠用解構賦值的寫法。vuex
actions: {
increment ({ commit }) {
commit('increment')
}
}
複製代碼
有了Vuex
源碼構造函數裏的call
綁定,這樣this
指向就被修正啦~不得不說祖師爺就是厲害。這一招,你們能夠免費學走~segmentfault
接着咱們帶着問題,爲啥上文中的context
就是store
實例,有dispatch
、commit
這些方法呢。繼續往下看。瀏覽器
如下是簡單源碼,有縮減,感興趣的能夠看個人文章 Vuex 源碼文章微信
class Store{
construcor(){
// 初始化 根模塊
// 而且也遞歸的註冊全部子模塊
// 而且收集全部模塊的 getters 放在 this._wrappedGetters 裏面
installModule(this, state, [], this._modules.root)
}
}
複製代碼
接着咱們看installModule
函數中的遍歷註冊 actions
實現
function installModule (store, rootState, path, module, hot) {
// 省略若干代碼
// 循環遍歷註冊 action
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(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
的綁定對象。
new
調用:綁定到新建立的對象,注意:顯示return
函數或對象,返回值不是新建立的對象,而是顯式返回的函數或對象。call
或者 apply
( 或者 bind
) 調用:嚴格模式下,綁定到指定的第一個參數。非嚴格模式下,null
和undefined
,指向全局對象(瀏覽器中是window
),其他值指向被new Object()
包裝的對象。undefined
,不然綁定到全局對象。ES6
中的箭頭函數:不會使用上文的四條標準的綁定規則, 而是根據當前的詞法做用域來決定this
, 具體來講, 箭頭函數會繼承外層函數,調用的 this 綁定( 不管 this 綁定到什麼),沒有外層函數,則是綁定到全局對象(瀏覽器中是window
)。 這其實和 ES6
以前代碼中的 self = this
機制同樣。
做者:常以若川爲名混跡於江湖。前端路上 | PPT愛好者 | 所知甚少,惟善學。
公衆號若川視野
若川的博客
segmentfault
前端視野專欄,開通了前端視野專欄,歡迎關注~
掘金專欄,歡迎關注~
知乎前端視野專欄,開通了前端視野專欄,歡迎關注~
github blog,求個star
^_^~