<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從未看過源碼,到底該如何入手?分享一次完整的源碼閱讀過程

          共 48318字,需瀏覽 97分鐘

           ·

          2021-01-30 23:46

          a4f30acdfc1f17e0bbd30b38d39f117d.webp



          人生不止有技術(shù)
          ?鏈接每一位開發(fā)者,讓編程更有趣兒!關(guān)注


          前言

          我覺得每個(gè)人可能都有過看源碼的想法吧,也包括我。因?yàn)榭丛创a不光能使自己對這個(gè)庫更加熟悉,還能學(xué)習(xí)到作者強(qiáng)大的思想,久而久之,自己的水平和思想也會有明顯的提升的。

          但對于我來說,之前從來沒有閱讀過源碼,想閱讀源碼卻不敢邁出那一步,因?yàn)橐粋€(gè)成熟的庫有著太多的方法、邏輯,閱讀起來可能會比較困難,但人總要勇于嘗試的嘛,于是我就準(zhǔn)備把Vuex?的源碼?clone?下來,沒有別的原因,只是因?yàn)檫@個(gè)庫體積比較小,算上注釋,核心代碼只有1000行不到,我覺得非常適合第一次閱讀源碼的人拿來練手

          說干就干,我就先在?github?上給自己列了一個(gè)計(jì)劃表,預(yù)計(jì)?15?天看完源碼并完成總結(jié),然后每天記錄一下當(dāng)天的收獲

          690c0a7ad796eda5c5ff6282e1b15f73.webp

          不過最后的結(jié)果倒是出乎我的意料,閱讀源碼加上整理總結(jié)只用了8天左右的時(shí)間

          在閱讀源碼之前,我是先去看了一下?Vuex?的官方文檔,算是一種回顧、查漏補(bǔ)缺,我也非常建議這樣做,因?yàn)槟憧丛创a,你就會看到這個(gè)庫里面所有的內(nèi)容,那么你連這個(gè)庫都沒用明白呢,閱讀源碼的難度無形之中又增加了嘛!即先會熟練使用這個(gè)庫的各個(gè)方法(盡管你并不知道為何這么使用),再在閱讀源碼的過程中看到相應(yīng)的代碼時(shí)聯(lián)想到那個(gè)方法的使用,兩者相互結(jié)合,對于源碼的理解就變得容易許多了

          這里放上?Vuex?官方文檔的鏈接,如果有興趣跟著我的思路閱讀?Vuex?源碼的小伙伴可以先把文檔中提到的所有使用都熟悉一下

          ?

          ???「Vuex官方文檔」:https://vuex.vuejs.org/zh/

          ?
          ?

          文末有?「總結(jié)」?和?「問答環(huán)節(jié)」

          ?
          ?? 源碼解析

          對于源碼的所有注釋和理解我都收錄在我?github?的?Vuex-Analysis?倉庫里了,想要看更詳細(xì)的注釋的,可以?fork?下來參考一下(點(diǎn)擊文末的?「閱讀原文」?跳轉(zhuǎn)我的倉庫地址)

          接下來本文就按照我當(dāng)時(shí)閱讀源碼的思路,一步一步詳細(xì)地講解,希望大家耐心看完,謝謝啦~

          一、源碼目錄結(jié)構(gòu)分析

          整個(gè)?Vuex?的源碼文件非常多,我們直接看最主要的文件,即?src?文件夾中的內(nèi)容,結(jié)構(gòu)示例如下:

          ├──?src
          ????├──?module????//?與模塊相關(guān)的操作
          ????│???├──?module-collection.js???//?用于收集并注冊根模塊以及嵌套模塊
          ????│???└──?module.js???//?定義Module類,存儲模塊內(nèi)的一些信息,例如:?state...
          ????│
          ????├──?plugins???//?一些插件
          ????│???├──?devtool.js???//?開發(fā)調(diào)試插件
          ????│???└──?logger.js????//?
          ????│
          ????├──?helpers.js???????//?輔助函數(shù),例如:mapState、mapGetters、mapMutations...
          ????├──?index.cjs.js?????//?commonjs?打包入口
          ????├──?index.js?????????//?入口文件
          ????├──?index.mjs????????//?es6?module?打包入口
          ????├──?mixin.js?????????//?將vuex實(shí)例掛載到全局Vue的$store上
          ????├──?store.js?????????//?核心文件,定義了Store類
          ????└──?util.js??????????//?提供一些工具函數(shù),例如:?deepCopy、isPromise、isObject...

          二、源碼閱讀

          1. 查看工具函數(shù)

          首先我個(gè)人覺得肯定是要看一下?util.js?,這里面存放的是源碼中頻繁用到的工具函數(shù),所以我覺得要最先了解一下每個(gè)函數(shù)的作用是什么

          /**
          ?*?Get?the?first?item?that?pass?the?test
          ?*?by?second?argument?function
          ?*
          ?*?@param?{Array}?list
          ?*?@param?{Function}?f
          ?*?@return?{*}
          ?*/


          //?找到數(shù)組list中第一個(gè)符合要求的元素
          export?function?find?(list,?f)?{
          ??return?list.filter(f)[0]
          }

          /**
          ?*?深拷貝
          ?*?
          ?*?@param?{*}?obj
          ?*?@param?{Array}?cache
          ?*?@return?{*}
          ?*/
          export?function?deepCopy?(obj,?cache?=?[])?{
          ??//?just?return?if?obj?is?immutable?value
          ??if?(obj?===?null?||?typeof?obj?!==?'object')?{
          ????return?obj
          ??}

          ??//?if?obj?is?hit,?it?is?in?circular?structure
          ??const?hit?=?find(cache,?c?=>?c.original?===?obj)
          ??if?(hit)?{
          ????return?hit.copy
          ??}

          ??const?copy?=?Array.isArray(obj)???[]?:?{}
          ??//?put?the?copy?into?cache?at?first
          ??//?because?we?want?to?refer?it?in?recursive?deepCopy
          ??cache.push({
          ????original:?obj,
          ????copy
          ??})

          ??Object.keys(obj).forEach(key?=>?{
          ????copy[key]?=?deepCopy(obj[key],?cache)
          ??})

          ??return?copy
          }

          //?遍歷obj對象的每個(gè)屬性的值
          export?function?forEachValue?(obj,?fn)?{
          ??Object.keys(obj).forEach(key?=>?fn(obj[key],?key))
          }

          //?判斷是否為對象(排除null)
          export?function?isObject?(obj)?{
          ??return?obj?!==?null?&&?typeof?obj?===?'object'
          }

          //?判斷是否為Promise對象
          export?function?isPromise?(val)?{
          ??return?val?&&?typeof?val.then?===?'function'
          }

          //?斷言
          export?function?assert?(condition,?msg)?{
          ??if?(!condition)?throw?new?Error(`[vuex]?${msg}`)
          }

          //?保留原始參數(shù)的閉包函數(shù)
          export?function?partial?(fn,?arg)?{
          ??return?function?()?{
          ????return?fn(arg)
          ??}
          }

          每個(gè)函數(shù)的作用我都寫上了注釋,稍微閱讀一下應(yīng)該可以明白其作用

          2. 入口文件

          最主要的代碼都在?src?目錄下,所以以下提到的文件都是默認(rèn)?src?目錄下的文件

          首先,肯定從入口文件?index.js?開始看,但能發(fā)現(xiàn)的是,還有?index.cjs?和?index.mjs?,這兩者分別是?commonjs?和?es6 module?的打包入口,我們就不用管了

          import?{?Store,?install?}?from?'./store'
          import?{?mapState,?mapMutations,?mapGetters,?mapActions,?createNamespacedHelpers?}?from?'./helpers'
          import?createLogger?from?'./plugins/logger'

          export?default?{
          ??Store,
          ??install,
          ??version:?'__VERSION__',
          ??mapState,
          ??mapMutations,
          ??mapGetters,
          ??mapActions,
          ??createNamespacedHelpers,
          ??createLogger
          }

          export?{
          ??Store,
          ??install,
          ??mapState,
          ??mapMutations,
          ??mapGetters,
          ??mapActions,
          ??createNamespacedHelpers,
          ??createLogger
          }

          從入口文件中可以看到,主要導(dǎo)出了?Store?類 、install?方法以及一些輔助函數(shù)(mapState、mapMutations、mapGetters...)

          那么我們主要看的就是?vuex?的核心代碼,即?store.js?,可以看到?Store?類就出自于這個(gè)文件

          3. Store類的實(shí)現(xiàn)

          整個(gè)?Store?類的主要邏輯都在它的構(gòu)造函數(shù)?constructor?中,因此我們就從?constructor?中分步去捋邏輯、看代碼

          3.1 存放類的狀態(tài)

          首先是定義了一些實(shí)例狀態(tài),用于存放模塊、mutations?、actions?、getters?緩存等東西

          const?{
          ??plugins?=?[],
          ??strict?=?false
          }?=?options??????//?生成Store類的入?yún)?/span>

          this._committing?=?false????????//?表示提交的狀態(tài),當(dāng)通過mutations方法改變state時(shí),該狀態(tài)為true,state值改變完后,該狀態(tài)變?yōu)閒alse;?在嚴(yán)格模式下會監(jiān)聽state值的改變,當(dāng)改變時(shí),_committing為false時(shí),會發(fā)出警告,即表明state值的改變不是經(jīng)過mutations的

          this._actions?=?Object.create(null)??//?用于記錄所有存在的actions方法名稱(包括全局的和命名空間內(nèi)的,且允許重復(fù)定義)??????

          this._actionSubscribers?=?[]???????//?存放actions方法訂閱的回調(diào)函數(shù)

          this._mutations?=?Object.create(null)??//?用于記錄所有存在的的mutations方法名稱(包括全局的和命名空間內(nèi)的,且允許重復(fù)定義)

          this._wrappedGetters?=?Object.create(null)??//?收集所有模塊包裝后的的getters(包括全局的和命名空間內(nèi)的,但不允許重復(fù)定義)

          this._modules?=?new?ModuleCollection(options)??//?根據(jù)傳入的options配置,注冊各個(gè)模塊,此時(shí)只是注冊、建立好了各個(gè)模塊的關(guān)系,已經(jīng)定義了各個(gè)模塊的state狀態(tài),但getters、mutations等方法暫未注冊

          this._modulesNamespaceMap?=?Object.create(null)???//?存儲定義了命名空間的模塊

          this._subscribers?=?[]????//?存放mutations方法訂閱的回調(diào)

          this._watcherVM?=?new?Vue()??//?用于監(jiān)聽state、getters

          this._makeLocalGettersCache?=?Object.create(null)???//?getters的本地緩存

          關(guān)于各個(gè)變量狀態(tài)的作用都寫在這了,其中只有?this._modules = new ModuleCollection(option)?執(zhí)行了一些操作,其作用就是進(jìn)行「模塊遞歸收集」,根據(jù)?ModuleCollection?的來源,我們移步到?./module/module-collection.js?文件

          3.1.1 遞歸收集模塊

          在?Module-collection.js?文件中定義了?ModuleCollection?類,其作用就是通過遞歸遍歷?options?入?yún)?,將每個(gè)模塊都生成一個(gè)獨(dú)立的?Moudle

          這里先來熟悉一下?options?的結(jié)構(gòu),如下:

          import?Vuex?from?'vuex'

          const?options?=?{
          ??state:?{...},
          ??getters:?{...},
          ??mutations:?{...},
          ??actions:?{...},
          ??modules:?{
          ????ModuleA:?{
          ??????state:?{...},
          ??????...
          ??????modules:?{
          ????????ModuleA1:?{...}
          ??????}
          ????},
          ????ModuleB:?{
          ??????state:?{...},
          ??????...
          ??????modules:?{
          ????????ModuleB1:?{...}
          ??????}
          ????}
          ??}
          }

          const?store?=?new?Vuex.Store(options)

          export?default?store

          可以看到傳入的?options?整體可以看成一個(gè)根模塊?root?,然后?root?的?modules?中嵌套著另外兩個(gè)子模塊:ModuleA?和ModuleB?,而?ModuleA?和ModuleB?內(nèi)部也分別嵌套著一個(gè)子模塊,分別為?ModuleA1?、ModuleB1?。這樣就組成了一個(gè)模塊樹,因此?ModuleCollection?類的工作就是將保留原來的模塊關(guān)系,將每個(gè)模塊封裝到一個(gè)?Module?類中

          export?default?class?ModuleCollection?{
          ??constructor?(rawRootModule)?{
          ????//?遞歸注冊模塊
          ????this.register([],?rawRootModule,?false)
          ??}
          ??
          ??//?根據(jù)路徑順序,從根模塊開始遞歸獲取到我們準(zhǔn)備添加新的模塊的父模塊
          ??get?(path)?{
          ????return?path.reduce((module,?key)?=>?{
          ??????return?module.getChild(key)
          ????},?this.root)
          ??}
          ??
          ??//?遞歸注冊模塊
          ??register?(path,?rawModule,?runtime?=?true)?{
          ????if?(__DEV__)?{
          ??????assertRawModule(path,?rawModule)
          ????}
          ????
          ????const?newModule?=?new?Module(rawModule,?runtime)??//?初始化一個(gè)新的模塊
          ????if?(path.length?===?0)?{????//?當(dāng)前沒有別的模塊
          ??????this.root?=?newModule?????//?則此模塊為根模塊
          ????}?else?{????//?有多個(gè)模塊?????
          ??????const?parent?=?this.get(path.slice(0,?-1))???//?獲取到新模塊從屬的父模塊,所以是path.slice(0,?-1),最后一個(gè)元素就是我們要添加的子模塊的名稱
          ??????parent.addChild(path[path.length?-?1],?newModule)????//?在父模塊中添加新的子模塊
          ????}

          ????if?(rawModule.modules)?{?????//?如果有嵌套模塊
          ??????/**
          ???????*??1.?遍歷所有的子模塊,并進(jìn)行注冊;
          ???????*??2.?在path中存儲除了根模塊以外所有子模塊的名稱
          ???????*??*/
          ?
          ??????forEachValue(rawModule.modules,?(rawChildModule,?key)?=>?{
          ????????this.register(path.concat(key),?rawChildModule,?runtime)
          ??????})
          ????}
          ??}
          }

          「函數(shù)作用:」

          1. register(path, rawModule, runtime):注冊新的模塊,并根據(jù)模塊的嵌套關(guān)系,將新模塊添加作為對應(yīng)模塊的子模塊
          • path:表示模塊嵌套關(guān)系。當(dāng)前為根模塊時(shí),沒有任何嵌套關(guān)系,此時(shí)?path = []?; 當(dāng)前不是根模塊時(shí),存在嵌套關(guān)系,例如上述例子中的?ModuleA1?,它是?ModuleA的子模塊 ,而?ModuleA?又是根模塊的子模塊,此時(shí)?path = ['ModuleA', 'ModuleA1']
          • rawModule:表示模塊對象,此時(shí)是一個(gè)對象類型
          • runtime:表示程序運(yùn)行時(shí)
          1. get(path):根據(jù)傳入的?path?路徑,獲取到我們想要的?Module?類

          ModuleCollection?的構(gòu)造函數(shù)中調(diào)用了?register?函數(shù),前兩個(gè)參數(shù)分別為:[]?、rawRootModule?,此時(shí)肯定是從根模塊開始注冊的,所以?path?里無內(nèi)容,并且?rawRootModule?指向的是根模塊

          然后來看一下?register?函數(shù)里的邏輯。

          1. 首先將當(dāng)前要注冊的模塊生成一個(gè)?Module?,并將?rawModule?作為參數(shù),用于存放?Module?的信息

          2. 然后通過?if(path.length === 0)?判斷是否為根模塊,是的話就將?this.root指向?Module?; 否則就跳到第3步

          3. 判斷當(dāng)前模塊不是根模塊,就通過?get?函數(shù)找到當(dāng)前模塊的父模塊,然后調(diào)用父模塊中的?addChild?方法將當(dāng)前模塊添加到子模塊中

          4. 最后再判斷當(dāng)前模塊是否還有嵌套的模塊,有的話就重新回到第1步進(jìn)行遞歸操作 ; 否則不做任何處理

          按照上面的邏輯,就可以將所有的模塊遞歸收集并注冊好了,其中有一個(gè)?Module?類還沒有具體提到,所以這里移步到?./module/module.js

          import?{?forEachValue?}?from?'../util'

          //?定義了Vuex中的?Module?類,包含了state、mutations、getters、actions、modules
          export?default?class?Module?{
          ??constructor?(rawModule,?runtime)?{
          ????this.runtime?=?runtime
          ????
          ????this._children?=?Object.create(null)???//?創(chuàng)建一個(gè)空對象,用于存放當(dāng)前模塊的子模塊
          ????
          ????this._rawModule?=?rawModule?????????//?當(dāng)前模塊的一些信息,例如:state、mutations、getters、actions、modules
          ????const?rawState?=?rawModule.state????//?1.?函數(shù)類型?=>?返回一個(gè)obj對象;?2.?直接獲取到obj對象

          ????//?存儲當(dāng)前模塊的state狀態(tài)
          ????this.state?=?(typeof?rawState?===?'function'???rawState()?:?rawState)?||?{}???
          ??}

          ??//?判斷該模塊是否定義了namespaced,定義了則返回true;?否則返回false
          ??get?namespaced?()?{
          ????return?!!this._rawModule.namespaced
          ??}

          ??//?添加子模塊,名稱為key
          ??addChild?(key,?module)?{
          ????this._children[key]?=?module
          ??}

          ??//?移除名稱為key的子模塊
          ??removeChild?(key)?{
          ????delete?this._children[key]
          ??}

          ??//?獲取名稱為key的子模塊
          ??getChild?(key)?{
          ????return?this._children[key]
          ??}

          ??//?是否存在名稱為key的子模塊
          ??hasChild?(key)?{
          ????return?key?in?this._children
          ??}
          ?
          ??//?將當(dāng)前模塊的命名空間更新到指定模塊的命名空間中,并同時(shí)更新一下actions、mutations、getters的調(diào)用來源
          ??update?(rawModule)?{
          ????this._rawModule.namespaced?=?rawModule.namespaced
          ????if?(rawModule.actions)?{
          ??????this._rawModule.actions?=?rawModule.actions
          ????}
          ????if?(rawModule.mutations)?{
          ??????this._rawModule.mutations?=?rawModule.mutations
          ????}
          ????if?(rawModule.getters)?{
          ??????this._rawModule.getters?=?rawModule.getters
          ????}
          ??}

          ??//?遍歷當(dāng)前模塊的所有子模塊,并執(zhí)行回調(diào)操作
          ??forEachChild?(fn)?{
          ????forEachValue(this._children,?fn)
          ??}

          ??//?遍歷當(dāng)前模塊的所有g(shù)etters,并執(zhí)行回調(diào)操作
          ??forEachGetter?(fn)?{
          ????if?(this._rawModule.getters)?{
          ??????forEachValue(this._rawModule.getters,?fn)
          ????}
          ??}

          ??//?遍歷當(dāng)前模塊的所有actions,并執(zhí)行回調(diào)操作
          ??forEachAction?(fn)?{
          ????if?(this._rawModule.actions)?{
          ??????forEachValue(this._rawModule.actions,?fn)
          ????}
          ??}

          ??//?遍歷當(dāng)前模塊的所有mutations,并執(zhí)行回調(diào)操作
          ??forEachMutation?(fn)?{
          ????if?(this._rawModule.mutations)?{
          ??????forEachValue(this._rawModule.mutations,?fn)
          ????}
          ??}
          }

          來看一下剛才模塊收集時(shí),創(chuàng)建的?Module?類內(nèi)部做了什么事情,同樣的從?constructor?中開始看

          this._children?是一個(gè)對象值,用于存放該模塊嵌套的其它?Module?類 ;

          this._rawModule?就是用于存放該模塊內(nèi)部的一些信息,例如:state、mutations?、actions?、getters?、moudles?;

          this.state?對應(yīng)的就是?this._rawModule?中的?state?;

          這是整個(gè)構(gòu)造函數(shù)中執(zhí)行的操作,我們可以看到,在生成一個(gè)?Module?類的時(shí)候,其只定義了?state?屬性,而?mutations?、getters?、actions?、modules?都是沒有被定義的,即例如現(xiàn)在是無法通過?Module.mutations?獲取到該模塊所有的?mutations?方法,那么這些方法都是在何時(shí)被定義的呢?自然是等模塊全部都收集完畢以后才進(jìn)行的操作,因?yàn)?vuex?中的嵌套模塊可能會存在命名空間?namespaced

          3.2 注冊模塊

          到此為止,各個(gè)模塊的類都創(chuàng)建好了,那么繼續(xù)回到?./src/store.js?的?constructor?構(gòu)造函數(shù)中

          //?將?dispatch?和?commit?方法綁定到?Store?的實(shí)例上,避免后續(xù)使用dispatch或commit時(shí)改變了this指向
          const?store?=?this
          const?{?dispatch,?commit?}?=?this
          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)
          }

          //?判斷store是否未嚴(yán)格模式。true:?所有的state都必須經(jīng)過mutations來改變
          this.strict?=?strict

          //?將根模塊的state賦值給state變量
          const?state?=?this._modules.root.state

          這段代碼首先對?Store?實(shí)例上的?dispatch?和?commit?方法進(jìn)行了一層包裝,即通過?call?將這兩個(gè)方法的作用對象指向當(dāng)前的?Store?實(shí)例,這樣就能防止后續(xù)我們操作時(shí),出現(xiàn)?this.$store.dispatch.call(obj, 1)?類似的情況而報(bào)錯(cuò)

          this.strict?是用于判斷是否是嚴(yán)格模式。因?yàn)?vuex?中,建議所有的?state?變量的變化都必須經(jīng)過?mutations?方法,因?yàn)檫@樣才能被?devtool?所記錄下來,所以在嚴(yán)格模式下,未經(jīng)過?mutations?而直接改變了?state?的值,開發(fā)環(huán)境下會發(fā)出警告??

          const state = this._modules.root.state??獲取的是根模塊的?state?,用于后續(xù)的一些操作

          一切都準(zhǔn)備就緒了,下面就開始為每個(gè)模塊注冊信息了

          //?從根模塊開始,遞歸完善各個(gè)模塊的信息
          installModule(this,?state,?[],?this._modules.root)

          調(diào)用了?installModule?方法,并將?store?實(shí)例對象 、state?屬性 、路徑 、根模塊對象依次作為參數(shù)進(jìn)行傳遞

          //?注冊完善各個(gè)模塊內(nèi)的信息
          function?installModule?(store,?rootState,?path,?module,?hot)?{
          ??const?isRoot?=?!path.length??//?是否為根模塊
          ??const?namespace?=?store._modules.getNamespace(path)??//?獲取當(dāng)前模塊的命名空間,格式為:second/?或 second/third/

          ??//?如果當(dāng)前模塊設(shè)置了namespaced?或?繼承了父模塊的namespaced,則在modulesNamespaceMap中存儲一下當(dāng)前模塊
          ??if?(module.namespaced)?{
          ????if?(store._modulesNamespaceMap[namespace]?&&?__DEV__)?{
          ??????console.error(`[vuex]?duplicate?namespace?${namespace}?for?the?namespaced?module?${path.join('/')}`)
          ????}
          ????store._modulesNamespaceMap[namespace]?=?module
          ??}

          ??//?如果不是根模塊,將當(dāng)前模塊的state注冊到其父模塊的state上
          ??if?(!isRoot?&&?!hot)?{
          ????const?parentState?=?getNestedState(rootState,?path.slice(0,?-1))?//?獲取父模塊的state
          ????const?moduleName?=?path[path.length?-?1]???//?當(dāng)前模塊的名稱
          ????store._withCommit(()?=>?{
          ??????if?(__DEV__)?{
          ????????if?(moduleName?in?parentState)?{
          ??????????console.warn(
          ????????????`[vuex]?state?field?"${moduleName}"?was?overridden?by?a?module?with?the?same?name?at?"${path.join('.')}"`
          ??????????)
          ????????}
          ??????}
          ??????//?將當(dāng)前模塊的state注冊在父模塊的state上,并且是響應(yīng)式的
          ??????Vue.set(parentState,?moduleName,?module.state)
          ????})
          ??}

          ??//?設(shè)置當(dāng)前模塊的上下文context
          ??const?local?=?module.context?=?makeLocalContext(store,?namespace,?path)

          ??//?注冊模塊的所有mutations
          ??module.forEachMutation((mutation,?key)?=>?{
          ????const?namespacedType?=?namespace?+?key?????//?例如:first/second/mutations1
          ????registerMutation(store,?namespacedType,?mutation,?local)
          ??})

          ??//?注冊模塊的所有actions
          ??module.forEachAction((action,?key)?=>?{
          ????/**
          ?????* actions有兩種寫法:
          ?????*?
          ?????*?actions:?{
          ?????*????AsyncAdd?(context,?payload)?{...},???//?第一種寫法
          ?????*????AsyncDelete:?{???????????????????????//?第二種寫法
          ?????*??????root:?true,
          ?????*??????handler:?(context,?payload)?{...}
          ?????*????}?
          ?????*?}
          ?????*/

          ????const?type?=?action.root???key?:?namespace?+?key???//?判斷是否需要在命名空間里注冊一個(gè)全局的action
          ????const?handler?=?action.handler?||?action??????????//?獲取actions對應(yīng)的函數(shù)
          ????registerAction(store,?type,?handler,?local)???
          ??})

          ??//?注冊模塊的所有g(shù)etters
          ??module.forEachGetter((getter,?key)?=>?{
          ????const?namespacedType?=?namespace?+?key
          ????registerGetter(store,?namespacedType,?getter,?local)
          ??})

          ??//?遞歸注冊子模塊
          ??module.forEachChild((child,?key)?=>?{
          ????installModule(store,?rootState,?path.concat(key),?child,?hot)
          ??})
          }

          const namespace = store._modules.getNamespace(path)?是將路徑?path作為參數(shù), 調(diào)用?ModuleCollection?類實(shí)例上的?getNamespace?方法來獲取當(dāng)前注冊對象的命名空間的

          /**
          *?根據(jù)模塊是否有命名空間來設(shè)定一個(gè)路徑名稱
          *?例如:A為父模塊,B為子模塊,C為子孫模塊
          *?1.?若B模塊命名空間為second,C模塊未設(shè)定命名空間時(shí);?C模塊繼承了B模塊的命名空間,為?second/
          *?2.?若B模塊未設(shè)定命名空間,?B模塊命名空間為third;?則此時(shí)B模塊繼承的是A模塊的命名空間,而C模塊的命名空間路徑為?third/
          */

          getNamespace?(path)?{
          ??let?module?=?this.root
          ??return?path.reduce((namespace,?key)?=>?{
          ????module?=?module.getChild(key)???//?獲取子模塊
          ????return?namespace?+?(module.namespaced???key?+?'/'?:?'')
          ??},?'')
          }

          從這可以看出,未指定命名空間的模塊會繼承父模塊的命名空間

          ??//?如果當(dāng)前模塊設(shè)置了namespaced?或?繼承了父模塊的namespaced,則在modulesNamespaceMap中存儲一下當(dāng)前模塊
          if?(module.namespaced)?{
          ??if?(store._modulesNamespaceMap[namespace]?&&?__DEV__)?{
          ????console.error(`[vuex]?duplicate?namespace?${namespace}?for?the?namespaced?module?${path.join('/')}`)
          ??}
          ??store._modulesNamespaceMap[namespace]?=?module
          }

          這段代碼是將所有存在命名空間的模塊記錄在?store._modulesNamespaceMap?中,便于之后的輔助函數(shù)可以調(diào)用(這里還未提到輔助函數(shù),可以先不管,到時(shí)候回頭來看)

          3.2.1 注冊模塊的state
          //?如果不是根模塊,將當(dāng)前模塊的state注冊到其父模塊的state上
          if?(!isRoot?&&?!hot)?{
          ??const?parentState?=?getNestedState(rootState,?path.slice(0,?-1))?//?獲取父模塊的state
          ??const?moduleName?=?path[path.length?-?1]???//?當(dāng)前模塊的名稱
          ??store._withCommit(()?=>?{
          ????if?(__DEV__)?{
          ??????if?(moduleName?in?parentState)?{
          ????????console.warn(
          ??????????`[vuex]?state?field?"${moduleName}"?was?overridden?by?a?module?with?the?same?name?at?"${path.join('.')}"`
          ????????)
          ??????}
          ????}
          ????//?將當(dāng)前模塊的state注冊在父模塊的state上,并且是響應(yīng)式的
          ????Vue.set(parentState,?moduleName,?module.state)
          ??})
          }

          這段代碼主要是將非根模塊的?state?掛載到父模塊的?state?上

          const parentState = getNestedState(rootState, path.slice(0, -1))根據(jù)當(dāng)前的模塊路徑,從根模塊的?state?開始找,最終找到當(dāng)前模塊的父模塊的state,可以看一下?getNestedState?方法內(nèi)部的具體實(shí)現(xiàn)

          //?獲取到嵌套的模塊中的state
          function?getNestedState?(state,?path)?{
          ??return?path.reduce((state,?key)?=>?state[key],?state)
          }

          const moduleName = path[path.length - 1]?從路徑?path?中將當(dāng)前模塊的名稱提取出來

          store._withCommit(()?=>?{
          ??if?(__DEV__)?{
          ????if?(moduleName?in?parentState)?{
          ??????console.warn(
          ????????`[vuex]?state?field?"${moduleName}"?was?overridden?by?a?module?with?the?same?name?at?"${path.join('.')}"`
          ??????)
          ????}
          ??}
          ??//?將當(dāng)前模塊的state注冊在父模塊的state上,并且是響應(yīng)式的
          ??Vue.set(parentState,?moduleName,?module.state)
          })

          這段代碼中最主要的部分就是?Vue.set(parentState, moduleName, module.state)?,作用就是調(diào)用了?Vue?的?set?方法將當(dāng)前模塊的?state?響應(yīng)式地添加到了父模塊的?state?上,這是因?yàn)樵谥笪覀儠吹?state?會被放到一個(gè)新的?Vue?實(shí)例的?data?中,所以這里不得不使用?Vue?的?set?方法來響應(yīng)式地添加

          同樣的,從這段代碼中我們也可以知道了為什么平時(shí)在獲取子模塊上?state?的屬性時(shí),是通過?this.$store.state.ModuleA.name?這樣的形式來獲取的了

          3.2.2 生成模塊調(diào)用上下文
          //?設(shè)置當(dāng)前模塊的上下文context
          const?local?=?module.context?=?makeLocalContext(store,?namespace,?path)

          這行代碼也可以說是非常核心的一段代碼了,它根據(jù)命名空間為每個(gè)模塊創(chuàng)建了一個(gè)屬于該模塊調(diào)用的上下文,并將該上下文賦值了給了該模塊的?context?屬性

          接下來看一下這個(gè)上下文是如何創(chuàng)建的吧

          //?若設(shè)置了命名空間則創(chuàng)建一個(gè)本地的commit、dispatch方法,否則將使用全局的store
          function?makeLocalContext?(store,?namespace,?path)?{
          ??const?noNamespace?=?namespace?===?''??

          ??const?local?=?{
          ????dispatch:?noNamespace???store.dispatch?:?(_type,?_payload,?_options)?=>?{
          ??????const?args?=?unifyObjectStyle(_type,?_payload,?_options)
          ??????const?{?payload,?options?}?=?args
          ??????let?{?type?}?=?args

          ??????if?(!options?||?!options.root)?{??//?若傳入了第三個(gè)參數(shù)設(shè)置了root:true,則派發(fā)的是全局上對應(yīng)的的actions方法
          ????????type?=?namespace?+?type
          ????????if?(__DEV__?&&?!store._actions[type])?{
          ??????????console.error(`[vuex]?unknown?local?action?type:?${args.type},?global?type:?${type}`)
          ??????????return
          ????????}
          ??????}

          ??????return?store.dispatch(type,?payload)
          ????},

          ????commit:?noNamespace???store.commit?:?(_type,?_payload,?_options)?=>?{
          ??????const?args?=?unifyObjectStyle(_type,?_payload,?_options)
          ??????const?{?payload,?options?}?=?args
          ??????let?{?type?}?=?args

          ??????if?(!options?||?!options.root)?{???//?若傳入了第三個(gè)參數(shù)設(shè)置了root:true,則派發(fā)的是全局上對應(yīng)的的mutations方法
          ????????type?=?namespace?+?type
          ????????if?(__DEV__?&&?!store._mutations[type])?{
          ??????????console.error(`[vuex]?unknown?local?mutation?type:?${args.type},?global?type:?${type}`)
          ??????????return
          ????????}
          ??????}

          ??????store.commit(type,?payload,?options)
          ????}
          ??}

          ??/**
          ???*?若沒有設(shè)定命名空間,則直接讀取store.getters(store.getters已經(jīng)掛載到vue實(shí)例的computed上了);
          ???*?若設(shè)定了命名空間,則從本地緩存_makeLocalGettersCache中讀取getters
          ???*/

          ??Object.defineProperties(local,?{
          ????getters:?{
          ??????get:?noNamespace
          ??????????()?=>?store.getters????
          ????????:?()?=>?makeLocalGetters(store,?namespace)
          ????},
          ????state:?{
          ??????get:?()?=>?getNestedState(store.state,?path)
          ????}
          ??})

          ??return?local
          }

          local?這個(gè)變量存儲的就是一個(gè)模塊的上下文。

          先來看其第一個(gè)屬性?dispatch?,當(dāng)該模塊沒有設(shè)置命名空間時(shí),調(diào)用該上下文的?dispatch?方法時(shí)會直接調(diào)用?sotre.dispatch?,即調(diào)用了根模塊的?dispatch?方法 ; 而存在命名空間時(shí),會先判斷相應(yīng)的命名空間,以此來決定調(diào)用哪個(gè)?dispatch?方法

          if (!options || !options.root)?是判斷調(diào)用?dispatch?方法時(shí)有沒有傳入第三個(gè)參數(shù)?{root: true}?,若有則表示調(diào)用全局根模塊上對應(yīng)的的?dispatch?方法

          那么同樣的,local?中的?commit?屬性就類似于?dispatch?,這里就不多說了

          然后最后通過?Object.defineProperties?方法對?local?的?getters?屬性和state?屬性設(shè)置了一層獲取代理,等后續(xù)對其訪問時(shí),才會進(jìn)行處理。例如,訪問?getters?屬性時(shí),先判斷是否存在命名空間,若沒有,則直接返回?store.getters?; 否則的話,根據(jù)命名空間創(chuàng)建一個(gè)本地的?getters?緩存,根據(jù)這個(gè)緩存來獲取對應(yīng)的?getters?,來看一下代碼

          //?創(chuàng)建本地的getters緩存
          function?makeLocalGetters?(store,?namespace)?{
          ??//?若緩存中沒有指定的getters,則創(chuàng)建一個(gè)新的getters緩存到__makeLocalGettersCache中
          ??if?(!store._makeLocalGettersCache[namespace])?{
          ????const?gettersProxy?=?{}
          ????const?splitPos?=?namespace.length
          ????Object.keys(store.getters).forEach(type?=>?{
          ??????//?如果store.getters中沒有與namespace匹配的getters,則不進(jìn)行任何操作
          ??????if?(type.slice(0,?splitPos)?!==?namespace)?return

          ??????//?獲取本地getters名稱
          ??????const?localType?=?type.slice(splitPos)

          ??????//?對getters添加一層代理
          ??????Object.defineProperty(gettersProxy,?localType,?{
          ????????get:?()?=>?store.getters[type],
          ????????enumerable:?true
          ??????})
          ????})
          ????//?把代理過的getters緩存到本地
          ????store._makeLocalGettersCache[namespace]?=?gettersProxy
          ??}

          ??return?store._makeLocalGettersCache[namespace]
          }

          當(dāng)存在命名空間時(shí)訪問?local.getters?,首先會去?store._makeLocalGettersCache?查找是否有對應(yīng)的?getters?緩存,若沒有,則創(chuàng)建一個(gè)?gettersProxy?,在store.getters?上找到對應(yīng)的?getters?,然后用?Object.defineProperty?對gettersProxy?做一層處理,即當(dāng)訪問?local.getters.func?時(shí),相當(dāng)于訪問了?store.getters['first/func']?,這樣做一層緩存,下一次訪問該?getters?時(shí),就不會重新遍歷?store.getters?了 ; 若有緩存,則直接從緩存中獲取

          上下文已經(jīng)創(chuàng)建好了,接下來就是注冊?mutations?、actions?、getters?了

          3.2.3 注冊模塊的mutations
          //?注冊模塊的所有mutations
          module.forEachMutation((mutation,?key)?=>?{
          ??const?namespacedType?=?namespace?+?key?????//?例如:first/second/mutations1
          ??registerMutation(store,?namespacedType,?mutation,?local)
          })

          這里遍歷了模塊的所有?mutations?方法,通過命名空間 +?mutations?方法名的形式生成了?namespacedType

          然后跳到?registerMutations?方法看看具體是如何注冊的

          //?注冊mutations方法
          function?registerMutation?(store,?type,?handler,?local)?{
          ??const?entry?=?store._mutations[type]?||?(store._mutations[type]?=?[])??//?通過store._mutations?記錄所有注冊的mutations
          ??entry.push(function?wrappedMutationHandler?(payload)?{
          ????handler.call(store,?local.state,?payload)
          ??})
          }

          首先根據(jù)我們傳入的?type?也就是上面的?namespacedType?去store._mutations?尋找是否有入口?entry?,若有則直接獲取 ; 否則就創(chuàng)建一個(gè)空數(shù)組用于存儲?mutations?方法

          在獲取到?entry?以后,將當(dāng)前的?mutations?方法添加到?entry?末尾進(jìn)行存儲。其中?mutations?接收的參數(shù)有兩個(gè),即 上下文中的?state?和 我們傳入的參數(shù)?payload

          從這段代碼我們可以看出,整個(gè)?store?實(shí)例的所有?mutations?方法都是存儲在?store._mutations?中的,并且是以鍵值對的形式存放的,例如:

          store._mutations?=?{
          ??'mutations1':?[function?handler()?{...}],
          ??'ModuleA/mutations2':?[function?handler()?{...},?function?handler()?{...}],
          ??'ModuleA/ModuleB/mutations2':?[function?handler()?{...}]
          }

          其中「鍵」是由命名空間和?mutations?方法名組成的,「值」是一個(gè)數(shù)組,存放著所有該鍵對應(yīng)的?mutations?方法

          為什么是用數(shù)組存放呢?因?yàn)樵谏厦嬲f過,假設(shè)父模塊ModuleA?里有一個(gè)叫?func?的mutations?方法,那么其在?store._mutations?中就是這個(gè)樣子的

          store._mutations?=?{
          ??'ModuleA/func':?[function?handler()?{...}]
          }

          若子模塊沒有設(shè)置命名空間,那么他是會繼承父模塊的命名空間的,此時(shí)子模塊里也有一個(gè)叫func?的?mutations?方法,那么在獲取?entry?時(shí),獲取到的是?store._mutations['ModuleA/func']?,但此時(shí)這個(gè)?entry?中已經(jīng)有一個(gè)?mutations?方法了,那么為了保證之前的方法不被替換,就選擇添加到數(shù)組的末尾,此時(shí)應(yīng)該就可以猜測到了,后續(xù)如果調(diào)用該?mutations?方法,會先獲取到相應(yīng)的數(shù)組,然后遍歷依次執(zhí)行

          得出個(gè)「結(jié)論」mutations?方法是可以重名的

          3.2.4 注冊模塊的actions
          //?注冊模塊的所有actions
          module.forEachAction((action,?key)?=>?{
          ??const?type?=?action.root???key?:?namespace?+?key???//?判斷是否需要在命名空間里注冊一個(gè)全局的action
          ??const?handler?=?action.handler?||?action??????????//?獲取actions對應(yīng)的函數(shù)
          ??registerAction(store,?type,?handler,?local)???
          })

          遍歷模塊的所有?actions?方法,其中對于?type?和?handler?的處理主要是為了兼容兩種寫法:

          //?第一種寫法:
          actions:?{
          ??func(context,?payload)?{
          ????//?省略業(yè)務(wù)代碼...
          ??}
          }

          //?第二種寫法:
          actions:?{
          ??func:?{
          ????root:?true,
          ????handler(context,?payload)?{
          ??????//?省略業(yè)務(wù)代碼...
          ????}
          ??}
          }

          當(dāng)采用第二種寫法,并且?root = true?時(shí),就會將該?actions?方法注冊到全局上,即前面不加上任何的命名空間前綴

          再來看看?registerAction?方法里具體實(shí)現(xiàn)了什么

          //?注冊actions方法,接收兩個(gè)參數(shù):context(包含了上下文中的dispatch方法、commit方法、getters方法、state)、傳入的參數(shù)payload
          function?registerAction?(store,?type,?handler,?local)?{
          ??const?entry?=?store._actions[type]?||?(store._actions[type]?=?[])???//?通過store._actions?記錄所有注冊的actions
          ??entry.push(function?wrappedActionHandler?(payload)?{
          ????let?res?=?handler.call(store,?{
          ??????dispatch:?local.dispatch,
          ??????commit:?local.commit,
          ??????getters:?local.getters,
          ??????state:?local.state,
          ??????rootGetters:?store.getters,
          ??????rootState:?store.state
          ????},?payload)
          ????//?若返回值不是一個(gè)promise對象,則包裝一層promise,并將返回值作為then的參數(shù)
          ????if?(!isPromise(res))?{
          ??????res?=?Promise.resolve(res)
          ????}
          ????if?(store._devtoolHook)?{
          ??????return?res.catch(err?=>?{
          ????????store._devtoolHook.emit('vuex:error',?err)
          ????????throw?err
          ??????})
          ????}?else?{
          ??????return?res
          ????}
          ??})
          }

          與?mutations?類似,先從?store._actions?獲取入口?entry?,然后將當(dāng)前的actions?進(jìn)行包裝處理后添加到?entry?的末尾。actions?方法接收兩個(gè)參數(shù),即context?和我們傳入的參數(shù)?payload?,其中?context?是一個(gè)對象,里面包含了dispatch?、commit?、getters?、state?、rootGetters?、rootState?,前4個(gè)都是在當(dāng)前模塊的上下文中調(diào)用的,后2個(gè)是在全局上調(diào)用的

          最后對于?actions?的返回值還做了一層處理,因?yàn)?actions?規(guī)定是處理異步任務(wù)的,所以我們肯定希望其值是一個(gè)?promise?對象,這樣方便后續(xù)的操作。所以這里對?actions?方法的返回值做了一個(gè)判斷,如果本身就是?promise?對象,那么就直接返回 ;若不是,則包裝一層?promise?對象,并將返回值?res?作為參數(shù)返回給?.then

          同樣的,actions?方法也是可以重名的

          3.2.5 注冊模塊的getters
          //?注冊模塊的所有g(shù)etters
          module.forEachGetter((getter,?key)?=>?{
          ??const?namespacedType?=?namespace?+?key
          ??registerGetter(store,?namespacedType,?getter,?local)
          })

          與上面的類似,這里就不多說了,直接跳到?registerGetters?方法

          //?注冊getters
          function?registerGetter?(store,?type,?rawGetter,?local)?{
          ??if?(store._wrappedGetters[type])?{???//?若記錄過getters了,則不再重復(fù)記錄
          ????if?(__DEV__)?{
          ??????console.error(`[vuex]?duplicate?getter?key:?${type}`)
          ????}
          ????return
          ??}
          ??//?在store._wrappedGetters中記錄getters
          ??store._wrappedGetters[type]?=?function?wrappedGetter?(store)?{
          ????return?rawGetter(
          ??????local.state,?//?local?state
          ??????local.getters,?//?local?getters
          ??????store.state,?//?root?state
          ??????store.getters?//?root?getters
          ????)
          ??}
          }

          這里發(fā)現(xiàn)?getters?并不像?mutations?和?actions?一樣去獲取一個(gè)?entry?,而是直接查看?store._wrappedGetters[type]?是否有對應(yīng)的?getters?,若有,則不再重復(fù)記錄 ; 否則將?getters?包裝一下存在?sotre._wrappedGetters?中,其中經(jīng)過包裝后的?getters?接收4個(gè)參數(shù),即?state?、getters?、rootState?、rootGetters?,前2個(gè)分別表示當(dāng)前上下文中的?state?和?getters?,后2個(gè)分別表示根模塊的?state?和?getters

          所以我們在使用?Vuex?時(shí),調(diào)用子模塊的?getters?時(shí)是這樣的:

          const?store?=?Vuex.Store({
          ??state:?{
          ????a:?1,
          ????b:?2
          ??},
          ??getters:?{
          ????addA(state)?{
          ??????return?state.a?+?1
          ????}
          ??},
          ??modules:?{
          ????//?子模塊A
          ????ModuleA:?{
          ??????state:?{
          ????????c:?3
          ??????},
          ??????getters:?{
          ???????sum(state,?getters,?rootState,?rootGetters)?{
          ??????????console.log(state.c)???//?3
          ??????????console.log(getters.addC)??//?4
          ??????????console.log(rootState.b)??//?2
          ??????????console.log(rootGetters.addA)??//?2
          ????????},
          ????????addC(state)?{
          ??????????return?state.c?+?1
          ????????}
          ??????}
          ????}
          ??}
          })

          最后我們再次得出一個(gè)結(jié)論,getters?是不能重名的,并且前一個(gè)命名的不會被后一個(gè)命名的所覆蓋

          3.2.6 遞歸注冊子模塊
          //?遞歸注冊子模塊
          module.forEachChild((child,?key)?=>?{
          ??installModule(store,?rootState,?path.concat(key),?child,?hot)
          })

          然后就是判斷當(dāng)前的模塊里有沒有嵌套的子模塊了,有的話就將子模塊的名稱添加到?path末尾,然后把相應(yīng)的參數(shù)傳入?installModule?方法,重新走一遍本文中?3.2?里所有的流程

          3.3 注冊vm

          上面已經(jīng)將模塊的注冊完畢了,看一下?constructor?中下一行代碼是什么:

          resetStoreVM(this,?state)

          跳到相應(yīng)的方法中去看一下:

          //?初始化vm
          function?resetStoreVM?(store,?state,?hot)?{
          ??const?oldVm?=?store._vm

          ??store.getters?=?{}????//?在實(shí)例store上設(shè)置getters對象
          ??
          ??store._makeLocalGettersCache?=?Object.create(null)??//?清空本地緩存
          ??const?wrappedGetters?=?store._wrappedGetters
          ??const?computed?=?{}
          ??//?遍歷getters,將每一個(gè)getter注冊到store.getters,訪問對應(yīng)getter時(shí)會去vm上訪問對應(yīng)的computed
          ??forEachValue(wrappedGetters,?(fn,?key)?=>?{
          ????computed[key]?=?partial(fn,?store)
          ????Object.defineProperty(store.getters,?key,?{
          ??????get:?()?=>?store._vm[key],
          ??????enumerable:?true?//?for?local?getters
          ????})
          ??})

          ??const?silent?=?Vue.config.silent
          ??Vue.config.silent?=?true
          ??//?使用Vue實(shí)例來存儲Vuex的state狀態(tài)樹,并利用computed去緩存getters返回的值
          ??store._vm?=?new?Vue({
          ????data:?{
          ??????$$state:?state
          ????},
          ????computed
          ??})
          ??Vue.config.silent?=?silent

          ??//?啟用嚴(yán)格模式的監(jiān)聽警告
          ??if?(store.strict)?{
          ????enableStrictMode(store)
          ??}

          ??//?若存在舊的vm,?銷毀舊的vm
          ??if?(oldVm)?{
          ????if?(hot)?{
          ??????//?解除對舊的vm對state的引用
          ??????store._withCommit(()?=>?{
          ????????oldVm._data.$$state?=?null
          ??????})
          ????}
          ????Vue.nextTick(()?=>?oldVm.$destroy())
          ??}
          }

          這個(gè)方法里主要做的就是生成一個(gè)?Vue?的實(shí)例?_vm?,然后將?store._makeLocalGettersCache?里的?getters?以及?store.state?交給一個(gè)?_vm?托管,即將?store.state?賦值給?_vm.data.$$state?,將?store._makeLocalGettersCache?通過轉(zhuǎn)化后賦值給?_vm.computed?,這樣一來,state?就實(shí)現(xiàn)了響應(yīng)式,getters?實(shí)現(xiàn)了類似?computed?的功能

          因?yàn)樯闪诵碌?_vm?,所以最后通過?oldVm.$destory()?將舊的?_vm?給銷毀掉了

          值得注意的是,其將?sotre.getters?的操作放在了這個(gè)方法里,是因?yàn)槲覀兒罄m(xù)訪問某個(gè)?getters?時(shí),訪問的其實(shí)是?_vm.computed?中的內(nèi)容。因此,通過?Object.defineProperty?對?store.getters?進(jìn)行了處理

          3.4 訪問 state 、mutations 、actions

          到此為止,已經(jīng)實(shí)現(xiàn)了可以通過?store.getter.某個(gè)getters?來使用?getters?,那么如何訪問?state?、mutations?、actions?呢?

          3.4.1 訪問 state

          通過搜索,在?Store?類中定義了一個(gè)?get?函數(shù),用于處理?store.state?的操作:

          get?state?()?{
          ??return?this._vm._data.$$state
          }

          可以很清楚地看到,當(dāng)我們訪問?store.state?時(shí),就是去訪問?store._vm.data.$$state?,與剛才介紹?_vm?時(shí)說的一樣

          3.4.2 訪問 mutations

          其實(shí)?mutations?的訪問在一開始就觸及到了,只不過當(dāng)時(shí)只是提了一嘴,因?yàn)楫?dāng)時(shí)直接來看可能不會太明白

          const?store?=?this
          const?{?dispatch,?commit?}?=?this
          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)
          }

          在?Store?中,對?store.commit?和?store.dispatch?方法做了一層處理,將該方法的調(diào)用指向了?store?,先來看看?commit?方法的具體實(shí)現(xiàn)

          commit?(_type,?_payload,?_options)?{
          ??//?check?object-style?commit
          ??const?{
          ????type,
          ????payload,
          ????options
          ??}?=?unifyObjectStyle(_type,?_payload,?_options)

          ??const?mutation?=?{?type,?payload?}
          ??const?entry?=?this._mutations[type]????//?查找_mutations上是否有對應(yīng)的方法
          ??//?查找不到則不執(zhí)行任何操作
          ??if?(!entry)?{
          ????if?(__DEV__)?{
          ??????console.error(`[vuex]?unknown?mutation?type:?${type}`)
          ????}
          ????return
          ??}

          ??//?若有相應(yīng)的方法,則執(zhí)行
          ??this._withCommit(()?=>?{
          ????entry.forEach(function?commitIterator?(handler)?{
          ??????handler(payload)
          ????})
          ??})

          ??this._subscribers
          ????.slice()?//?shallow?copy?to?prevent?iterator?invalidation?if?subscriber?synchronously?calls?unsubscribe
          ????.forEach(sub?=>?sub(mutation,?this.state))

          ??if?(
          ????__DEV__?&&
          ????options?&&?options.silent
          ??)?{
          ????console.warn(
          ??????`[vuex]?mutation?type:?${type}.?Silent?option?has?been?removed.?`?+
          ??????'Use?the?filter?functionality?in?the?vue-devtools'
          ????)
          ??}
          }

          首先通過?unifyObjectStyle?方法對傳入的參數(shù)進(jìn)行了處理,來看一下這個(gè)方法是干什么的

          function?unifyObjectStyle?(type,?payload,?options)?{
          ??if?(isObject(type)?&&?type.type)?{
          ????options?=?payload
          ????payload?=?type
          ????type?=?type.type
          ??}

          ??if?(__DEV__)?{
          ????assert(typeof?type?===?'string',?`expects?string?as?the?type,?but?found?${typeof?type}.`)
          ??}

          ??return?{?type,?payload,?options?}
          }

          使用過?Vuex?的應(yīng)該都知道,commit?有兩種提交方式:

          //?第一種提交方式
          this.$store.commit('func',?1)

          //?第二種提交方式
          this.$store.commit({
          ??type:?'func',
          ??num:?1
          })

          其先對第一個(gè)參數(shù)進(jìn)行判斷是否為對象,是的話就當(dāng)作對象提交風(fēng)格處理,否則的話就直接返回

          在處理完參數(shù)以后,根據(jù)?type?從?store._mutations?上獲取到?entry?,前面分析過了,mutations?方法是以數(shù)組形式存儲的,所以可能有多個(gè)方法。然后在?_withCommit?方法中遍歷?entry?依次執(zhí)行?mutations?方法,這是因?yàn)?Vuex?規(guī)定?state?的改變都要通過?mutations?方法,store._committing?這個(gè)屬性就是用來判斷當(dāng)前是否處于調(diào)用?mutations?方法的,當(dāng)?state?值改變時(shí),會先去判斷?store._committing?是否為?true?,若不為?true?,則表示?state?的值改變沒有經(jīng)過?mutations?方法,于是會打印警告?? 信息

          而?this._subscribers?這段代碼我暫時(shí)還不清楚是干什么的,通過詞義,目測應(yīng)該是一個(gè)存放訂閱的東西吧,就先放著不管了,等后續(xù)回來再看

          3.4.3 訪問 actions
          dispatch?(_type,?_payload)?{
          ??//?check?object-style?dispatch
          ??const?{
          ????type,
          ????payload
          ??}?=?unifyObjectStyle(_type,?_payload)

          ??const?action?=?{?type,?payload?}
          ??const?entry?=?this._actions[type]??//?查找_actions上是否有對應(yīng)的方法
          ??//?查找不到則不執(zhí)行任何操作
          ??if?(!entry)?{
          ????if?(__DEV__)?{
          ??????console.error(`[vuex]?unknown?action?type:?${type}`)
          ????}
          ????return
          ??}

          ??try?{
          ????this._actionSubscribers
          ??????.slice()?//?shallow?copy?to?prevent?iterator?invalidation?if?subscriber?synchronously?calls?unsubscribe
          ??????.filter(sub?=>?sub.before)
          ??????.forEach(sub?=>?sub.before(action,?this.state))
          ??}?catch?(e)?{
          ????if?(__DEV__)?{
          ??????console.warn(`[vuex]?error?in?before?action?subscribers:?`)
          ??????console.error(e)
          ????}
          ??}

          ??const?result?=?entry.length?>?1
          ????Promise.all(entry.map(handler?=>?handler(payload)))
          ??:?entry[0](payload)

          ??return?new?Promise((resolve,?reject)?=>?{
          ????result.then(res?=>?{
          ??????try?{
          ????????this._actionSubscribers
          ??????????.filter(sub?=>?sub.after)
          ??????????.forEach(sub?=>?sub.after(action,?this.state))
          ??????}?catch?(e)?{
          ????????if?(__DEV__)?{
          ??????????console.warn(`[vuex]?error?in?after?action?subscribers:?`)
          ??????????console.error(e)
          ????????}
          ??????}
          ??????resolve(res)
          ????},?error?=>?{
          ??????try?{
          ????????this._actionSubscribers
          ??????????.filter(sub?=>?sub.error)
          ??????????.forEach(sub?=>?sub.error(action,?this.state,?error))
          ??????}?catch?(e)?{
          ????????if?(__DEV__)?{
          ??????????console.warn(`[vuex]?error?in?error?action?subscribers:?`)
          ??????????console.error(e)
          ????????}
          ??????}
          ??????reject(error)
          ????})
          ??})
          }

          前半部分與?commit?方法類似,就不多說了

          代碼中又出現(xiàn)了?this._actionSubscribers?,與?commit?中的也類似,可能這里是存放?actions?的訂閱者的東西,所以這些都先不看了

          其中變量?result?,先判斷?entry?的長度,若大于1,則表示有多個(gè)異步方法,所以用Promise.all?進(jìn)行包裹 ; 否則直接執(zhí)行?entry[0]

          最后創(chuàng)建并返回了一個(gè)新的?promise?,內(nèi)部判斷了?result?的狀態(tài),成功則執(zhí)行?resolve?,失敗則執(zhí)行?reject

          到此為止,我們已經(jīng)實(shí)現(xiàn)了?store.state?、store.getters?、store.commit?、store.dispatch?的調(diào)用了

          3.5 插件的調(diào)用

          繼續(xù)看?constructor?中的代碼(這段代碼也是整個(gè)?Store?類的構(gòu)造函數(shù)中最后的一小段代碼了)

          //?依次調(diào)用傳入的插件
          plugins.forEach(plugin?=>?plugin(this))

          const?useDevtools?=?options.devtools?!==?undefined???options.devtools?:?Vue.config.devtools
          //?使用vue的開發(fā)插件
          if?(useDevtools)?{
          ??devtoolPlugin(this)
          }

          首先就是遍歷創(chuàng)建?Store?類時(shí)傳入的參數(shù)?Plugins?,依次調(diào)用傳入的插件函數(shù)(當(dāng)然一般我們都沒有傳入,所以?Plugins?默認(rèn)是空數(shù)組)

          然后就是調(diào)用?devtoolPlugin?方法啦,根據(jù)導(dǎo)入的路徑我們?nèi)サ较鄳?yīng)的文件

          //?文件路徑:./plugins/devtool.js
          const?target?=?typeof?window?!==?'undefined'
          ????window
          ??:?typeof?global?!==?'undefined'
          ??????global
          ????:?{}
          const?devtoolHook?=?target.__VUE_DEVTOOLS_GLOBAL_HOOK__

          export?default?function?devtoolPlugin?(store)?{
          ??if?(!devtoolHook)?return

          ??store._devtoolHook?=?devtoolHook

          ??devtoolHook.emit('vuex:init',?store)

          ??devtoolHook.on('vuex:travel-to-state',?targetState?=>?{
          ????store.replaceState(targetState)
          ??})

          ??store.subscribe((mutation,?state)?=>?{
          ????devtoolHook.emit('vuex:mutation',?mutation,?state)
          ??},?{?prepend:?true?})

          ??store.subscribeAction((action,?state)?=>?{
          ????devtoolHook.emit('vuex:action',?action,?state)
          ??},?{?prepend:?true?})
          }

          看了半天,搜索了半天,都沒有找到哪個(gè)文件里有?__VUE_DEVTOOLS_GLOBAL_HOOK__,應(yīng)該是?dev-tools?插件里定義的,為了保證?Vuex?的源碼閱讀進(jìn)度,就先舍棄閱讀dev-tools?插件的內(nèi)容了

          3.6 其它方法

          整個(gè)?Store?實(shí)例生成的全過程差不多就是這樣了,另外還會發(fā)現(xiàn),其實(shí)有很多方法都沒有被用到,但是卻被定義出來了,這里可以稍微列舉幾個(gè)簡單地看一下

          3.6.1 更新 state
          //?在store._committing?=?true?的狀態(tài)下更新一下state
          replaceState?(state)?{
          ??this._withCommit(()?=>?{
          ????this._vm._data.$$state?=?state
          ??})
          }

          一目了然,這是提供了一種直接修改?state?的方法,并且不會打印警告信息

          3.6.2 注冊、卸載模塊
          //?注冊模塊
          registerModule?(path,?rawModule,?options?=?{})?{
          ??if?(typeof?path?===?'string')?path?=?[path]

          ??if?(__DEV__)?{
          ????assert(Array.isArray(path),?`module?path?must?be?a?string?or?an?Array.`)
          ????assert(path.length?>?0,?'cannot?register?the?root?module?by?using?registerModule.')
          ??}

          ??this._modules.register(path,?rawModule)
          ??installModule(this,?this.state,?path,?this._modules.get(path),?options.preserveState)
          ??//?reset?store?to?update?getters...
          ??resetStoreVM(this,?this.state)
          }

          //?卸載模塊
          unregisterModule?(path)?{
          ??if?(typeof?path?===?'string')?path?=?[path]

          ??if?(__DEV__)?{
          ????assert(Array.isArray(path),?`module?path?must?be?a?string?or?an?Array.`)
          ??}

          ??this._modules.unregister(path)
          ??this._withCommit(()?=>?{
          ????const?parentState?=?getNestedState(this.state,?path.slice(0,?-1))
          ????Vue.delete(parentState,?path[path.length?-?1])
          ??})
          ??resetStore(this)
          }
          3.6.3 重置 store 實(shí)例
          //?重置store,即注冊模塊、生成vm等操作
          function?resetStore?(store,?hot)?{
          ??store._actions?=?Object.create(null)
          ??store._mutations?=?Object.create(null)
          ??store._wrappedGetters?=?Object.create(null)
          ??store._modulesNamespaceMap?=?Object.create(null)
          ??const?state?=?store.state
          ??//?init?all?modules
          ??installModule(store,?state,?[],?store._modules.root,?true)
          ??//?reset?vm
          ??resetStoreVM(store,?state,?hot)
          }

          將所有的狀態(tài)都清空,然后重新執(zhí)行一邊?installModule?和?resetStoreVM?,這一般在模塊結(jié)構(gòu)變化以后調(diào)用,例如某個(gè)模塊被卸載

          4. install 注冊

          Store?類的所有實(shí)現(xiàn)都了解完了,再來看一下入口文件里還有什么,突然發(fā)現(xiàn)忘記看一下非常重要的?install?方法了,根據(jù)?install?方法的導(dǎo)入路徑找到相應(yīng)的函數(shù):

          //?提供install方法
          export?function?install?(_Vue)?{
          ??if?(Vue?&&?_Vue?===?Vue)?{
          ????if?(__DEV__)?{
          ??????console.error(
          ????????'[vuex]?already?installed.?Vue.use(Vuex)?should?be?called?only?once.'
          ??????)
          ????}
          ????return
          ??}
          ??Vue?=?_Vue
          ??applyMixin(Vue)
          }

          當(dāng)我們調(diào)用?Vue.use(vuex)?時(shí),調(diào)用這個(gè)方法,先判斷?vuex?是否已被注冊,若已被注冊,則不執(zhí)行任何操作 ; 若沒有被注冊,則調(diào)用?applyMixin?方法,現(xiàn)在移步到?./mixin.js?文件:

          export?default?function?(Vue)?{
          ??const?version?=?Number(Vue.version.split('.')[0])

          ??//?2.x版本直接通過全局混入Vue.mixin的方式掛載store
          ??if?(version?>=?2)?{
          ????Vue.mixin({?beforeCreate:?vuexInit?})
          ??}?else?{
          ????//?兼容1.x版本
          ????const?_init?=?Vue.prototype._init
          ????Vue.prototype._init?=?function?(options?=?{})?{
          ??????options.init?=?options.init
          ??????????[vuexInit].concat(options.init)
          ????????:?vuexInit
          ??????_init.call(this,?options)
          ????}
          ??}

          ??//?將vuex混入到$options中
          ??function?vuexInit?()?{
          ????//?獲取當(dāng)前組件的?$options
          ????const?options?=?this.$options
          ????//?若當(dāng)前組件的$options上已存在store,則將$options.store賦值給this.$store(一般是用于根組件的)
          ????if?(options.store)?{
          ??????this.$store?=?typeof?options.store?===?'function'
          ??????????options.store()
          ????????:?options.store
          ????}?
          ????//?當(dāng)前組件的$options上沒有store,則獲取父組件上的$store,即$options.parent.$store,并將其賦值給this.$store(一般用于子組件)
          ????else?if?(options.parent?&&?options.parent.$store)?{
          ??????this.$store?=?options.parent.$store
          ????}
          ??}
          }

          applyMixin?方法先判斷了?Vue?的版本號,主要做的是一個(gè)向下兼容?Vue 1.x?的版本,這里我對?Vue 1.x?的版本不太熟悉,所以就直接看?Vue 2.x?版本的處理方式吧

          通過?Vue.minxin?方法做了一個(gè)全局的混入,在每個(gè)組件?beforeCreate?生命周期時(shí)會調(diào)用?vuexInit?方法,該方法處理得非常巧妙,首先獲取當(dāng)前組件的?$options,判斷當(dāng)前組件的?$options?上是否有?sotre?,若有則將?store?賦值給當(dāng)前組件,即?this.$store?,這個(gè)一般是判斷根組件的,因?yàn)橹挥性诔跏蓟?Vue?實(shí)例的時(shí)候我們才手動(dòng)傳入了?store?; 若?$options?上沒有?store?,則代表當(dāng)前不是根組件,所以我們就去父組件上獲取,并賦值給當(dāng)前組件,即當(dāng)前組件也可以通過?this.$store?訪問到?store?實(shí)例了

          這里不得不感嘆,這個(gè)處理方式太棒了。

          5. 輔助函數(shù)

          store實(shí)例生成并且也?install?到?Vue?上了,看一下入口文件中只剩下輔助函數(shù)了,它們有?mapState?、mapGetters?、mapMutations?、mapActions?、createNamespacedHelpers?,進(jìn)到相應(yīng)的文件?./helpers.js?中看一下

          import?{?isObject?}?from?'./util.js'

          export?const?mapState?=?normalizeNamespace((namespace,?states)?=>?{
          ??const?res?=?{}
          ??if?(__DEV__?&&?!isValidMap(states))?{
          ????console.error('[vuex]?mapState:?mapper?parameter?must?be?either?an?Array?or?an?Object')
          ??}
          ??normalizeMap(states).forEach(({?key,?val?})?=>?{
          ????res[key]?=?function?mappedState?()?{
          ??????let?state?=?this.$store.state
          ??????let?getters?=?this.$store.getters
          ??????if?(namespace)?{
          ????????const?module?=?getModuleByNamespace(this.$store,?'mapState',?namespace)
          ????????if?(!module)?{
          ??????????return
          ????????}
          ????????state?=?module.context.state
          ????????getters?=?module.context.getters
          ??????}
          ??????return?typeof?val?===?'function'
          ??????????val.call(this,?state,?getters)
          ????????:?state[val]
          ????}
          ????//?mark?vuex?getter?for?devtools
          ????res[key].vuex?=?true
          ??})
          ??return?res
          })


          export?const?mapMutations?=?normalizeNamespace((namespace,?mutations)?=>?{
          ??const?res?=?{}
          ??if?(__DEV__?&&?!isValidMap(mutations))?{
          ????console.error('[vuex]?mapMutations:?mapper?parameter?must?be?either?an?Array?or?an?Object')
          ??}
          ??normalizeMap(mutations).forEach(({?key,?val?})?=>?{
          ????res[key]?=?function?mappedMutation?(...args)?{
          ??????//?Get?the?commit?method?from?store
          ??????let?commit?=?this.$store.commit
          ??????if?(namespace)?{
          ????????const?module?=?getModuleByNamespace(this.$store,?'mapMutations',?namespace)
          ????????if?(!module)?{
          ??????????return
          ????????}
          ????????commit?=?module.context.commit
          ??????}
          ??????return?typeof?val?===?'function'
          ??????????val.apply(this,?[commit].concat(args))
          ????????:?commit.apply(this.$store,?[val].concat(args))
          ????}
          ??})
          ??return?res
          })


          export?const?mapGetters?=?normalizeNamespace((namespace,?getters)?=>?{
          ??const?res?=?{}
          ??if?(__DEV__?&&?!isValidMap(getters))?{
          ????console.error('[vuex]?mapGetters:?mapper?parameter?must?be?either?an?Array?or?an?Object')
          ??}
          ??normalizeMap(getters).forEach(({?key,?val?})?=>?{
          ????//?The?namespace?has?been?mutated?by?normalizeNamespace
          ????val?=?namespace?+?val
          ????res[key]?=?function?mappedGetter?()?{
          ??????if?(namespace?&&?!getModuleByNamespace(this.$store,?'mapGetters',?namespace))?{
          ????????return
          ??????}
          ??????if?(__DEV__?&&?!(val?in?this.$store.getters))?{
          ????????console.error(`[vuex]?unknown?getter:?${val}`)
          ????????return
          ??????}
          ??????return?this.$store.getters[val]
          ????}
          ????//?mark?vuex?getter?for?devtools
          ????res[key].vuex?=?true
          ??})
          ??return?res
          })


          export?const?mapActions?=?normalizeNamespace((namespace,?actions)?=>?{
          ??const?res?=?{}
          ??if?(__DEV__?&&?!isValidMap(actions))?{
          ????console.error('[vuex]?mapActions:?mapper?parameter?must?be?either?an?Array?or?an?Object')
          ??}
          ??normalizeMap(actions).forEach(({?key,?val?})?=>?{
          ????res[key]?=?function?mappedAction?(...args)?{
          ??????//?get?dispatch?function?from?store
          ??????let?dispatch?=?this.$store.dispatch
          ??????if?(namespace)?{
          ????????const?module?=?getModuleByNamespace(this.$store,?'mapActions',?namespace)
          ????????if?(!module)?{
          ??????????return
          ????????}
          ????????dispatch?=?module.context.dispatch
          ??????}
          ??????return?typeof?val?===?'function'
          ??????????val.apply(this,?[dispatch].concat(args))
          ????????:?dispatch.apply(this.$store,?[val].concat(args))
          ????}
          ??})
          ??return?res
          })

          /**
          ?*?Rebinding?namespace?param?for?mapXXX?function?in?special?scoped,?and?return?them?by?simple?object
          ?*?@param?{String}?namespace
          ?*?@return?{Object}
          ?*/

          export?const?createNamespacedHelpers?=?(namespace)?=>?({
          ??mapState:?mapState.bind(null,?namespace),
          ??mapGetters:?mapGetters.bind(null,?namespace),
          ??mapMutations:?mapMutations.bind(null,?namespace),
          ??mapActions:?mapActions.bind(null,?namespace)
          })


          function?normalizeMap?(map)?{
          ??if?(!isValidMap(map))?{
          ????return?[]
          ??}
          ??return?Array.isArray(map)
          ??????map.map(key?=>?({?key,?val:?key?}))
          ????:?Object.keys(map).map(key?=>?({?key,?val:?map[key]?}))
          }

          function?isValidMap?(map)?{
          ??return?Array.isArray(map)?||?isObject(map)
          }

          function?normalizeNamespace?(fn)?{
          ??return?(namespace,?map)?=>?{
          ????if?(typeof?namespace?!==?'string')?{
          ??????map?=?namespace
          ??????namespace?=?''
          ????}?
          ????else?if?(namespace.charAt(namespace.length?-?1)?!==?'/')?{
          ??????namespace?+=?'/'
          ????}
          ????return?fn(namespace,?map)
          ??}
          }

          function?getModuleByNamespace?(store,?helper,?namespace)?{
          ??const?module?=?store._modulesNamespaceMap[namespace]
          ??if?(__DEV__?&&?!module)?{
          ????console.error(`[vuex]?module?namespace?not?found?in?${helper}():?${namespace}`)
          ??}
          ??return?module
          }

          整個(gè)文件里東西非常多,但我們很明確地知道,我們主要看的就是那幾個(gè)輔助函數(shù),觀察發(fā)現(xiàn),每個(gè)輔助函數(shù)都會先調(diào)用?normalizeNamespace?函數(shù)進(jìn)行處理,那么我們就先看看這個(gè)函數(shù)做了什么:

          function?normalizeNamespace?(fn)?{
          ??return?(namespace,?map)?=>?{
          ????if?(typeof?namespace?!==?'string')?{
          ??????map?=?namespace
          ??????namespace?=?''
          ????}?
          ????else?if?(namespace.charAt(namespace.length?-?1)?!==?'/')?{
          ??????namespace?+=?'/'
          ????}
          ????return?fn(namespace,?map)
          ??}
          }

          根據(jù)函數(shù)名的字面意思知道這應(yīng)該是根據(jù)不同的調(diào)用方法,標(biāo)準(zhǔn)化命名空間的。

          首先返回一個(gè)函數(shù),接收兩個(gè)參數(shù),即?namespace?和?map?,這也是我們調(diào)用輔助函數(shù)時(shí)可以傳入的兩個(gè)參數(shù) ;

          然后判斷?namespace?是否為字符串形式,若不是字符串,則表示是普通的調(diào)用方式,例如:

          mapMutations(['first/second/foo',?'first/second/bar'])

          mapMutations({
          ???foo:?'first/second/foo',
          ???bar:?'first/second/bar',
          })

          這種情況,就直接將第一個(gè)參數(shù)?namespace?賦值給映射變量?map?,而?namespace設(shè)為空

          若是字符串的話,則表示調(diào)用的是帶命名空間的綁定函數(shù)的,例如:

          mapState('first/second',?['foo',?'bar'])

          mapState('first/second',?{
          ??foo:?'foo',
          ??bar:?'bar',
          })

          處理好這兩種不同的調(diào)用方式以后,調(diào)用一下?fn?,并將?namespace?和?map?作為參數(shù)

          那么就先從?mapState?開始看吧

          5.1 mapState
          export?const?mapState?=?normalizeNamespace((namespace,?states)?=>?{
          ??const?res?=?{}
          ??if?(__DEV__?&&?!isValidMap(states))?{
          ????console.error('[vuex]?mapState:?mapper?parameter?must?be?either?an?Array?or?an?Object')
          ??}
          ??normalizeMap(states).forEach(({?key,?val?})?=>?{
          ????res[key]?=?function?mappedState?()?{
          ??????let?state?=?this.$store.state
          ??????let?getters?=?this.$store.getters
          ??????if?(namespace)?{
          ????????const?module?=?getModuleByNamespace(this.$store,?'mapState',?namespace)
          ????????if?(!module)?{
          ??????????return
          ????????}
          ????????state?=?module.context.state
          ????????getters?=?module.context.getters
          ??????}
          ??????return?typeof?val?===?'function'
          ??????????val.call(this,?state,?getters)
          ????????:?state[val]
          ????}
          ????//?mark?vuex?getter?for?devtools
          ????res[key].vuex?=?true
          ??})
          ??return?res
          })

          這里的?namespace?是一個(gè)字符串,states?是我們剛才處理好的映射變量?map

          首先創(chuàng)建一個(gè)空對象?res?,這是我們最后處理好要返回的變量 ;

          然后通過?isValidMap?方法判斷?map?是否符合要求,即是否是數(shù)組或?qū)ο?;

          再然后調(diào)用了?normalizeMap?方法處理了變量?states?,從字面意義上來看,這是用來標(biāo)準(zhǔn)化該變量的,因?yàn)楫吘褂锌赡苁菙?shù)組又有可能是對象嘛,所以要統(tǒng)一一下。來看一下normalizeMap?方法的實(shí)現(xiàn):

          function?normalizeMap?(map)?{
          ??if?(!isValidMap(map))?{
          ????return?[]
          ??}
          ??return?Array.isArray(map)
          ??????map.map(key?=>?({?key,?val:?key?}))
          ????:?Object.keys(map).map(key?=>?({?key,?val:?map[key]?}))
          }

          首先仍然要先判斷?map?是否合法,若不合法,則返回空數(shù)組,避免后續(xù)的代碼報(bào)錯(cuò) ;

          然后判斷?map?是否為數(shù)組,若是數(shù)組,則遍歷?map?進(jìn)行處理:

          將?[1,?2,?3]?變成?[{key:?1,?val:?1},?{key:?2,?val:?2},?{key:?3,?val:?3}]

          若?map?不是數(shù)組,則一定為對象,那么同樣也要把其處理成跟上面一樣的格式:

          將?{a:?1,?b:?2,?c:?3}?變成?[{key:?a,?val:?1},?{key:?b,?val:?2},?{key:?c,?val:?3}]

          處理好了以后就直接返回,在得到標(biāo)準(zhǔn)化以后的?map?后要對其進(jìn)行?forEach?遍歷,將遍歷到的每一個(gè)對象經(jīng)過處理后存放在?res?中,即?res[key] = function mappedState() {...}?,來看一下這個(gè)?mappedState?里做了什么處理

          首先獲取一下根模塊上的?state?和?getters

          //?獲取根模塊的?state?、getters
          let?state?=?this.$store.state
          let?getters?=?this.$store.getters

          然后判斷是否存在命名空間,即?namespace?是否為空,若為空,則不做任何處理 ; 否則調(diào)用?getModuleByNamespace?方法獲取到?namespace?對應(yīng)的模塊?module

          function?getModuleByNamespace?(store,?helper,?namespace)?{
          ??const?module?=?store._modulesNamespaceMap[namespace]
          ??if?(__DEV__?&&?!module)?{
          ????console.error(`[vuex]?module?namespace?not?found?in?${helper}():?${namespace}`)
          ??}
          ??return?module
          }

          可以看到?store._modulesNamespaceMap?終于派上了用場,在生成?Store?實(shí)例注冊所有模塊的時(shí)候,將帶有命名空間的模塊都存儲在了該變量上,原來是在這里用上了

          然后將剛才聲明的變量?state?和?getters?替換成?module?對應(yīng)上下文中的?state?和?getters

          if?(namespace)?{
          ??//?獲取命名空間namespace對應(yīng)的模塊
          ??const?module?=?getModuleByNamespace(this.$store,?'mapState',?namespace)
          ??if?(!module)?{
          ????return
          ??}
          ??//?將?state?、getters?變成該模塊上下文中的?state?、getters
          ??state?=?module.context.state
          ??getters?=?module.context.getters
          }

          這個(gè)?context?也是非常的巧妙,在注冊模塊的時(shí)候,獲取到該模塊的上下文的同時(shí),還將其存儲了一下,即:

          const?local?=?module.context?=?makeLocalContext(store,?namespace,?path)

          之前看到的時(shí)候不知道有啥用,但在這里看到后,覺得真的非常得贊 ??

          確定好了?state?和?getters?的值,最后就可以返回值了

          return?typeof?val?===?'function'
          ????val.call(this,?state,?getters)
          ?:?state[val]

          這里還做了一層處理是因?yàn)橐幚韮煞N不同的方式,例如:

          mapState({
          ??foo:?state?=>?state.foo,
          ??bar:?'bar'
          })

          在這里我又發(fā)現(xiàn)了一個(gè)官方文檔里沒有提及的,就是以函數(shù)形式返回的時(shí)候,還能接收第二個(gè)參數(shù)?getters?,即:foo: (state, getters) => state.foo + getters.bar

          5.2 mapMutations
          export?const?mapMutations?=?normalizeNamespace((namespace,?mutations)?=>?{
          ??const?res?=?{}
          ??if?(__DEV__?&&?!isValidMap(mutations))?{
          ????console.error('[vuex]?mapMutations:?mapper?parameter?must?be?either?an?Array?or?an?Object')
          ??}
          ??normalizeMap(mutations).forEach(({?key,?val?})?=>?{
          ????res[key]?=?function?mappedMutation?(...args)?{
          ??????//?Get?the?commit?method?from?store
          ??????let?commit?=?this.$store.commit
          ??????if?(namespace)?{
          ????????const?module?=?getModuleByNamespace(this.$store,?'mapMutations',?namespace)
          ????????if?(!module)?{
          ??????????return
          ????????}
          ????????commit?=?module.context.commit
          ??????}
          ??????return?typeof?val?===?'function'
          ??????????val.apply(this,?[commit].concat(args))
          ????????:?commit.apply(this.$store,?[val].concat(args))
          ????}
          ??})
          ??return?res
          })

          mapMutations?與?mapState?的實(shí)現(xiàn)大體相似,主要的不同就在下面這段代碼:

          return?typeof?val?===?'function'
          ????val.apply(this,?[commit].concat(args))
          ?:?commit.apply(this.$store,?[val].concat(args))

          這里也是像?mapState?一樣處理了函數(shù)的調(diào)用類型和普通的調(diào)用類型,例如:

          mapMutations({
          ??foo:?(commit,?num)?=>?{
          ????commit('foo',?num)
          ??},
          ??bar:?'bar'
          })

          當(dāng)是函數(shù)的調(diào)用類型時(shí),則將?commit?作為第一個(gè)參數(shù),并把額外的參數(shù)一并傳入,所以才有的?val.apply(this, [commit].concat(args))?這段代碼 ;

          當(dāng)是普通的調(diào)用類型時(shí),則直接執(zhí)行?commit?,其中?val?對應(yīng)的就是該命名空間下需要調(diào)用的?mutations?方法名,然后再接收額外的參數(shù),即?commit.apply(this.$store, [val].concat(args))

          5.3 mapGetters
          export?const?mapGetters?=?normalizeNamespace((namespace,?getters)?=>?{
          ??const?res?=?{}
          ??if?(__DEV__?&&?!isValidMap(getters))?{
          ????console.error('[vuex]?mapGetters:?mapper?parameter?must?be?either?an?Array?or?an?Object')
          ??}
          ??normalizeMap(getters).forEach(({?key,?val?})?=>?{
          ????//?The?namespace?has?been?mutated?by?normalizeNamespace
          ????val?=?namespace?+?val
          ????res[key]?=?function?mappedGetter?()?{
          ??????if?(namespace?&&?!getModuleByNamespace(this.$store,?'mapGetters',?namespace))?{
          ????????return
          ??????}
          ??????if?(__DEV__?&&?!(val?in?this.$store.getters))?{
          ????????console.error(`[vuex]?unknown?getter:?${val}`)
          ????????return
          ??????}
          ??????return?this.$store.getters[val]
          ????}
          ????//?mark?vuex?getter?for?devtools
          ????res[key].vuex?=?true
          ??})
          ??return?res
          })

          這個(gè)也沒什么好說的了,拿到命名空間?namespace?,直接拼接上?val?通過?this.$store.getters[val]?進(jìn)行訪問。簡單舉個(gè)例子:

          第一種情況

          //?第一種
          mapGetters(['first/foo'])

          這種情況下?namespace?被處理成了空字符串,map?被處理成了?['first/foo'],遍歷?map?,此時(shí)?val = 'first/foo'?,那么?val = namespace + val處理后?val?仍然等于?first/foo?,所以最后就相當(dāng)于調(diào)用?this.$store.getters['first/foo']

          再來看第二種情況

          //?第二種
          mapGetters('first',?['foo'])

          這種情況下?namespace?被處理成了?first/,map?被處理成了?['foo']?,遍歷map?,此時(shí)?val = 'foo'?,那么?val = namespace + val?處理后?val?就等于?first/foo?,所以最后仍然是相當(dāng)于調(diào)用?this.$store.getters['first/foo']

          5.4 mapActions
          export?const?mapActions?=?normalizeNamespace((namespace,?actions)?=>?{
          ??const?res?=?{}
          ??if?(__DEV__?&&?!isValidMap(actions))?{
          ????console.error('[vuex]?mapActions:?mapper?parameter?must?be?either?an?Array?or?an?Object')
          ??}
          ??normalizeMap(actions).forEach(({?key,?val?})?=>?{
          ????res[key]?=?function?mappedAction?(...args)?{
          ??????//?get?dispatch?function?from?store
          ??????let?dispatch?=?this.$store.dispatch
          ??????if?(namespace)?{
          ????????const?module?=?getModuleByNamespace(this.$store,?'mapActions',?namespace)
          ????????if?(!module)?{
          ??????????return
          ????????}
          ????????dispatch?=?module.context.dispatch
          ??????}
          ??????return?typeof?val?===?'function'
          ??????????val.apply(this,?[dispatch].concat(args))
          ????????:?dispatch.apply(this.$store,?[val].concat(args))
          ????}
          ??})
          ??return?res
          })

          簡單看了一下,處理流程跟?mapMutations?幾乎一模一樣,就不多說了

          5.5 createNamespacedHelpers
          export?const?createNamespacedHelpers?=?(namespace)?=>?({
          ??mapState:?mapState.bind(null,?namespace),
          ??mapGetters:?mapGetters.bind(null,?namespace),
          ??mapMutations:?mapMutations.bind(null,?namespace),
          ??mapActions:?mapActions.bind(null,?namespace)
          })

          該方法是根據(jù)傳入的命名空間?namespace?創(chuàng)建一組輔助函數(shù)。巧妙之處就是先通過?bind?函數(shù)把第一個(gè)參數(shù)先傳入

          import?{?createNamespacedHelpers?}?from?'vuex'

          const?{?mapState,?mapActions?}?=?createNamespacedHelpers('first/second')

          export?default?{
          ??computed:?{
          ????...mapState({
          ??????a:?'a',??//?相當(dāng)于?first/second/a
          ??????b:?'b',??//?相當(dāng)于?first/second/b
          ????})
          ??},
          ??methods:?{
          ????...mapActions([
          ??????'foo',??????//?相當(dāng)于?first/second/foo
          ??????'bar',??????//?相當(dāng)于?first/second/bar
          ????])
          ??}
          }
          ?? 心得體會

          首先,我一直有一個(gè)閱讀源碼的想法,但卻因?yàn)槟芰τ邢捱t遲沒有行動(dòng),之后在一次與大佬的交流中,我發(fā)現(xiàn)了自己的不足,沒有深入學(xué)習(xí),即只停留在「會用」的階段,卻沒有做到知其然知其所以然。說實(shí)話,這樣真的很難受,每次用某個(gè)庫時(shí),出現(xiàn)了某個(gè)問題只會先看考慮是否自己調(diào)用的方式有問題,然后上搜索引擎找答案,長期這樣自己也很難有進(jìn)步。

          所以,因?yàn)橐韵氯c(diǎn)原因,我準(zhǔn)備靠自己好好看一下?Vuex?源碼:

          1. Vuex?的核心源碼比較少,對于像我一樣第一次閱讀源碼的人比較友好
          2. 深入學(xué)習(xí)了常用的庫以后,在使用的時(shí)候遇到問題,可以快速地找到問題根源
          3. 不能只停留在成熟的庫的表面,要學(xué)習(xí)它們的思想、技術(shù),這樣有助于自己的成長

          剛開始不知道自己能花多久時(shí)間看完?Vuex?的核心源碼,我初步給自己定了?15?天的期限,預(yù)計(jì)每天至少看?2?小時(shí)。于是我把?Vuex?的源碼?fork?并?clone?了下來,第一天簡單地找了一下核心代碼的位置,然后非常粗略地看了一下源碼里的大致流程。同時(shí),我去?Vuex?官方文檔里重新仔仔細(xì)細(xì)地回顧了一下所有的核心使用方法

          接下來的時(shí)間我就按照我本文的閱讀順序進(jìn)行源碼的閱讀

          這里總結(jié)幾點(diǎn)閱讀源碼的「心得體會」吧:

          1. 對于這個(gè)庫的使用一定要十分熟練,即明白各種方法的使用,強(qiáng)烈建議把官方文檔吃透(「重點(diǎn)」
          2. 找到核心代碼的位置,從入口文件開始,一步步看
          3. 多看源碼中的英文注釋,看不懂的可以用翻譯,這些注釋基本上能幫你理解這段代碼的作用
          4. 遇到看不懂的地方可以先打個(gè)備注,因?yàn)樗赡芘c后面的某些代碼有所聯(lián)系,等之后回頭來看之前看不懂的代碼時(shí),就會明白了
          5. 閱讀源碼的過程中,看到某些變量或函數(shù)時(shí),先看命名,因?yàn)檫@些命名的字面意思基本上就代表了它的作用,然后要學(xué)會聯(lián)想到這個(gè)正常的調(diào)用是什么樣的,這樣更便于理解
          6. 多多利用編譯器的搜索功能。因?yàn)橛袝r(shí)你看到的函數(shù)或變量可能在別的地方也有用到,為了方便尋找,可以利用好編譯器的搜索功能(包括當(dāng)前「本地搜索」「全局搜索」
          0467c2a4cb92c2e08ff5a4f99587c9b8.webp本地搜索cdb379c792567e12c5ec98f08b448cc7.webp全局搜索?? 問答環(huán)節(jié)

          這里放上幾個(gè)群友對于這次閱讀源碼問我的問題:

          「Q1:」?你是怎么看源碼的?有看別人的視頻或者別人的文章嗎?

          「A1:」?沒有看別人的視頻或者文章,就當(dāng)時(shí)自己思考了一下該如何看源碼,列了一個(gè)步驟,就這樣摸索著看完了,覺得還挺有意思的

          「Q2:」?光自己看能看懂嗎?

          「A2:」?說實(shí)話確實(shí)有些地方挺難看懂的,但結(jié)合著源碼自帶的英文注釋,基本上能把大致的思路理清,然后看不懂的地方就先做上記號并暫時(shí)略過,等到看了更多的代碼了以后,回過頭來就發(fā)現(xiàn)似乎看懂了些。最后要說的就是,源碼真不是一遍就能看懂的,真的是要反反復(fù)復(fù)多看幾遍,才能理解其中的原理

          「Q3:」?看完源碼后,你能自己手寫出來嗎?

          「A3:」?emmmm...這可能有點(diǎn)難度,但是我覺得手寫一些核心代碼,實(shí)現(xiàn)一個(gè)簡陋的?Vuex?還是可以做到的吧,而且我覺得很有必要自己再去手寫一下核心代碼,因?yàn)檫@又是一次對源碼的鞏固,并且我也已經(jīng)開始在寫一個(gè)簡陋版的?Vuex?了,放在倉庫的?myVuex?文件夾下

          ?? 最后

          若本文對于?Vuex?源碼閱讀有任何錯(cuò)誤的地方,歡迎大家給我提意見,一定虛心聽取你們的指正,

          Vuex?源碼閱讀倉庫可以點(diǎn)擊文末的?「閱讀原文」?查看,若覺得不錯(cuò)的,也可以點(diǎn)個(gè)??「star」??? 支持一下我。

          最后,也可以關(guān)注我的公眾號:「前端印象」,或是添加我的微信(Lpyexplore333)私底下進(jìn)行交流

          這篇文章我真的很用心了,你們?nèi)绦牟唤o點(diǎn)個(gè)贊 ?? 和 在看 嘛~



          「歡迎各位大佬關(guān)注我,掃二維碼即可」


          熱文導(dǎo)讀



          瀏覽 45
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    91在线无码精品秘 入口九色十 | 欧洲精品在线观看 | 天天操人人摸视频ⅩⅩⅩA√ | 国内9l 自拍九色啦视频 | 我看操逼 |