面对 this 指向丢失,尤雨溪在 Vuex 源码中是怎么处理的
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?默認啟用嚴格模式,嚴格模式下指向?undefined4. 尤大在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?
·················?若川簡介?·················
你好,我是若川,畢業于江西高?!,F在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》多篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,活躍在知乎@若川,掘金@若川。致力于分享前端開發經驗,愿景:幫助5年內前端人走向前列。
識別上方二維碼加我微信、長期交流學習
今日話題
略。歡迎分享、收藏、點贊、在看我的公眾號文章~
總結
以上是生活随笔為你收集整理的面对 this 指向丢失,尤雨溪在 Vuex 源码中是怎么处理的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Visual C++】游戏开发笔记二十
- 下一篇: 前端学习(3181):ant-desig