<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>

          從 vue 源碼看問題 —— vue 初始化都做了什么事?

          共 6629字,需瀏覽 14分鐘

           ·

          2021-12-22 17:06

          點擊上方?前端瓶子君,關(guān)注公眾號

          回復(fù)算法,加入前端編程面試算法每日一題群

          前言

          最近想要對 Vue2 源碼進行學(xué)習(xí),主要目的就是為了后面在學(xué)習(xí) Vue3 源碼時,可以有一個更好的對比和理解,所以這個系列暫時不會涉及到 Vue3 的內(nèi)容,但是 Vue3 的核心模塊和 Vue2 是一致的,只是在實現(xiàn)上改變了方式、進行了優(yōu)化等。

          準(zhǔn)備工作

          再開始閱讀源碼之前,有些事還是必須要做的,那就是拉取 源倉庫代碼[2] 或者直接下載 ZIP 格式文件,處理好之后就需要打開神器 VScode .

          打開編輯器之后,你需要做的就是:

          • 安裝依賴 install —— yarn 或者 npm
          • 找到 package.json 文件并找到里面的 scripts 部分,然后往 dev 命令中加入 --sourcemap 配置參數(shù),或者新建建一個 dev:sourcemap 命令,其內(nèi)容就是比 dev 命令多了個 --sourcemap 配置,其實主要就是為了生成 vue.js.map 文件方便后面調(diào)試
          • 執(zhí)行 npm run dev:sourcemap 命令,執(zhí)行成功以后就會在 dist 目錄下生成 vue.js.map 文件
          image.png
          • 然后就可以在 example 目錄下,寫一些自己想要測試的例子,然后通過 debug 的形式找到對應(yīng)內(nèi)容在代碼中的位置
          image.png

          深入源碼

          Vue 初始化的代碼位置

          既然要深入了解 Vue 初始化的內(nèi)容,那么我們就得先找到 Vue 初始化是在哪里進行的,那么查找的方式有兩種:

          • 通過 package.json 文件來進行查找 —— 在 script 腳本命令配置中有 "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",其中的 scripts/config.jsTARGET:web-full-dev 就為指明了文件里的具體配置,然后可以在逐層查找對應(yīng)的入口文件
          • 通過 debug 模式進行查找 —— 首先在 example 目錄下新建一個目錄,可以是任何名字,本文討論的是初始化的內(nèi)容,這里就將其命名為 init,在 init 目錄下新建一個 html 文件,在里面引入 dist 目錄下的 vue.js, 因為生成的 map 文件是 vue.js.map,否則 debug 時不方便查找對應(yīng)的文件位置

          這里選擇方式二,畢竟方式一通過 rollup 配置查找還是過于繁瑣,于是通過 debug 可以快速確定 Vue 進行初始化的文件位置.

          this._init(options) 初始化

          src>core>index.js 文件中,可以清晰的看到,在我們進行 new Vue() 時,調(diào)用的其實就是 this._init() 方法,而這個方法又是在 initMixin(Vue) 中進行定義的

          initMixin(Vue) 方法

          src>core>init.js 文件中,可以看到在 initMixin() 方法中最核心的就是處理組件配置項的部分,這一部分又分為 子組件根組件 的配置,又分別對應(yīng) initInternalComponent() 方法 和 mergeOptions()方法 + resolveConstructorOptions()方法.

          同時,在 Vue.prototype._init 函數(shù)中還存在下面的這些方法的調(diào)用,后面會依次對每個方法進行解讀:

          子組件 —— initInternalComponent() 方法

          這個方法做的事就是創(chuàng)建 $options 對象,然后對組件選項進行打平做性能優(yōu)化,因為組件有很多的配置,其中也會存在各種嵌套的配置,在訪問時免不了要通過原型鏈進行動態(tài)查找,會影響執(zhí)行效率.

          • 根據(jù) vm 的構(gòu)造函數(shù)創(chuàng)建新的配置對象,即平時訪問的 $options 對象
          • 把當(dāng)前組件配置項打平然后賦值到 $options 對象,避免了原型鏈的動態(tài)查找
          • 如果當(dāng)前組件配置項中存在 render 選項,就把它添加到 $options 對象上

          根組件 —— resolveConstructorOptions() 方法

          src>core>init.js 文件中,resolveConstructorOptions() 方法其實最主要的事情就是從構(gòu)造函數(shù)上解析配置對象,具體如下:

          • 如果構(gòu)造函數(shù)的 super 屬性存在,證明還有基類,此時需要遞歸進行對配置選項解析
          • 將構(gòu)造函數(shù)的基類配置項進行緩存,然后比對當(dāng)前配置項配置項進行對比,如果不一致,則表明基類的配置項已發(fā)生更改
          • 找出被更改的配置項和 extent 選項進行合并,并賦值給 $options

          根組件 —— mergeOptions() 方法

          src>core>options.js 文件中,mergeOptions() 方法主要做的事情就是:

          • 對配置選項進行標(biāo)準(zhǔn)化
          • 對傳入的原始配置對象進行合并
          • 返回新的配置對象

          根組件合并配置項,就是將全局配置項合并到根組件局部配置項中,比如將全局注冊的 Vue.componet(options) 全局配置合并到根組件 new Vue(options) 上得到類似:

          ???new?Vue({
          ?????el:?xxx,
          ?????data:?{?xxx?},
          ?????componets:{
          ???????localComponents,
          ???????globalComponents,
          ?????}
          ???})
          復(fù)制代碼

          initLifecycle() 方法

          src>core>init.js 文件中調(diào)用 initLifecycle()方法,方法的具體定義位置為src>core>lifecycle.js.

          可能很多人會誤以為該方法是初始化生命周期鉤子函數(shù)的(因為其方法名),其實這個方法主要是對組件關(guān)系屬性進行初始化,比如:$root、$parent、$children、$refs 等.

          initEvents() 方法

          src>core>init.js 文件中調(diào)用 initEvents()方法,方法的具體定義位置為src>core>events.js.

          主要作用就是初始化自定義事件,但是針對這個方法目前先有個簡單的了解即可,因為里面涉及到的處理比較多,所以本文中暫時不對其進行展開,但是后面涉及到 this 實例上的方法時在具體分析.

          問題:存在一個組件并且綁定了事件,如 ,那么我們可以在 com 組件中通過 this.$emit('myClick') 的方式去觸發(fā),那是誰監(jiān)聽了這個事件呢?

          回答:也許你會認(rèn)為是 comp 的父組件去監(jiān)聽的,但其實是 com 組件自己監(jiān)聽的,因為 @myclick="clickHandle" 會被編譯為 this.$on('myClick', function clickHandle(){ })this.$emit('myClick') 的形式,而其中的 this 指向的就是 組件本身.

          initRender() 方法

          src>core>init.js 文件中調(diào)用 initRender()方法,方法的具體定義位置為src>core>render.js.

          這里具體的內(nèi)容也不再展開,后面涉及到 render 部分在進行深入解讀,其實主要就做了三件事:

          • 初始化插槽,如:vm.$slots、vm.$scopedSlots
          • 定義 _c 方法,即 createElement 方法,也就是 h 函數(shù)
          • $attrs$listeners 屬性進行響應(yīng)式處理

          callHook(vm: Component, hook: string) 方法

          src>core>init.js 文件中有調(diào)用 callHook(vm, 'beforeCreate')callHook(vm, 'created'),這就是平時我們在組件配置項中定義的生命周期函數(shù)被調(diào)用的形式.

          callHook 方法的具體定義位置為src>core>lifecycle.js 中.

          initInjections() 方法

          src>core>init.js 文件中調(diào)用 initInjections()方法,方法的具體定義位置為src>core>inject.js.

          provide/inject 的相關(guān)介紹和使用方法,可以點此 vue 文檔[3] 查看.

          initInjections() 方法

          主要做的就是獲取解析之后的 inject 選項 result,然后把 result 的每一項都代理到 vm 實例上,也可以理解為是進行響應(yīng)式處理,在組件中實現(xiàn) this.key 的形式直接進行訪問

          resolveInject() 方法

          • resolveInject(inject: any, vm: Component) 方法中的 inject 選項到這之前就已經(jīng)進行了標(biāo)準(zhǔn)化處理,所以這里的 inject 選項的格式一定為:

          inject = { key: { from: xxx, default: xxx } } 復(fù)制代碼

          -?遍歷?`inject`?配置的所有?`key`,查找到對應(yīng)?`provide`?中的值
          -?從?`inject`?配置中獲取?`from`?屬性值
          -?循環(huán)在祖代組件中查找?`provide`?選項,如果找到了,就獲取對應(yīng)的值,保存到?`result`?中;如果沒有找到,就繼續(xù)向上查找,一直到根組件
          -?如果到了根組件還是沒有找到?`inject`?中的?`key`?在對應(yīng)祖代組件上?`provide`?的值,那么就檢查?`inject`?中是否設(shè)置了默認(rèn)值,如果設(shè)置了默認(rèn)值,就將其賦值為默認(rèn)值

          ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/59089834a10447dfa4612c79617c12a1~tplv-k3u1fbpfcp-watermark.awebp?)

          ##?initState\(\)?方法

          在?`src>core>init.js`?文件中調(diào)用?`initState()`方法,方法的具體定義位置為`src>core>state.js`.

          它是響應(yīng)式原理的核心,主要處理?`props、data、methods、watch、computed`?等,?因為這一塊屬于響應(yīng)式的內(nèi)容,因此,不在本文里面進行深入探討,后面針對響應(yīng)式的內(nèi)容會進行解讀.

          ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f10f080f8a044286bd9570cf34e16f81~tplv-k3u1fbpfcp-watermark.awebp?)

          ##?initProvide\(\)?方法

          在?`src>core>init.js`?文件中調(diào)用?`initProvide()`方法,方法的具體定義位置為`src>core>inject.js`.

          這里面做的事情很簡單:

          -?從?`$options`?上獲取?`provide`?選項
          -?`provide`?選項存在時,判斷它是不是函數(shù),如果是函數(shù)就調(diào)用然后獲取配置對象,如果不是函數(shù)就直接使用?`provide`?選項
          -?在?`vm`?實例上掛載?`_provide`?屬性,值就為上面的?`provide`?的具體內(nèi)容

          ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/74720713cd2941138aae31c93b896c79~tplv-k3u1fbpfcp-watermark.awebp?)

          #?總結(jié)

          上面根據(jù)?`Vue.prototype._init`?初始化函數(shù)中使用的每個方法進行了分析,在這里需要稍微總結(jié)一下,順便配合一些問題進行理解。

          ##?合并組件配置

          ###?子組件

          子組件進行合并配置項時,主要是通過打平配置項,減少原型鏈動態(tài)查找,達到性能優(yōu)化的目的.

          ###?根組件

          根組件合并配置,就是將全局配置項合并到根組件局部配置項中.

          根組件合并會發(fā)生在三個地方:

          -?就是初始化時的這種情況
          -?`Vue.component(name,?Comp)`?時,將合并?`Vue?內(nèi)置全局組件`?和?`用戶注冊的全局組件`,最終都會合并到跟組件上配置上的?`components`?選項中
          -?`{?components:{xxx}?}`?局部注冊,執(zhí)行編譯器生成?`render`?函數(shù)時,?會合并全局配置對象到組件局部配置對象上

          ##?組件關(guān)系屬性的初始化

          如需要初始化?`$root、$parent、$children、$refs`?等.

          ##?初始化自定義事件

          ###?問題:當(dāng)在組件上使用自定義事件時,父組件和子組件誰負(fù)責(zé)監(jiān)聽這個事件?

          ```js
          //?這是一個偽代碼?

          ??"clickHandle"?/>

          復(fù)制代碼

          其實 @myClick="clickHandle" 會被編譯為 this.$emit('myClick')this.$on('myClick', function clickHandle(){}) 的形式,而這個 this 就是組件實例,即誰需要觸發(fā)事件,誰就需要監(jiān)聽事件.

          初始化插槽 & 定義 _c 方法

          • 初始化插槽,如:vm.$slots、vm.$scopedSlots
          • 定義 _c 方法,即 createElement 方法,也就是 h 函數(shù)

          通過 callHook 執(zhí)行 beforeCreate 和 created 生命周期函數(shù)

          問題:beforeCreate 中能獲取能訪問什么內(nèi)容,data 可以訪問嗎?

          從源碼中很容易看出來,在 beforeCreate 之前只初始化了 組件關(guān)系屬性、自定義事件、插槽_c 方法,所以關(guān)于可以訪問的就是這些內(nèi)容.

          因為 data、props、methods 等都沒有進行初始化,所以就都不能進行訪問,當(dāng)然如果你在 beforeCreate 中通過異步的方式訪問,比如 setTimeout 其實是可以的,最早能訪問數(shù)據(jù)的地方其實就是 created 當(dāng)中了.

          初始化 inject 選項

          • 根據(jù) inject 選項從祖代組件配置項中找到對應(yīng)的 provide 選項,從而獲取對應(yīng) key 中的值,得到 result[key] = val 形式的結(jié)果
          • 如果找到根組件上還不存在,就判斷是否有 default 選項,有就設(shè)置默認(rèn)值
          • 把得到的 result 結(jié)果進行響應(yīng)式處理,代理的 vm 實例上

          初始化 state 數(shù)據(jù)

          響應(yīng)式原理的核心,主要處理 props、data、methods、watch、computed

          處理 provide 選項

          vm.$options 配置對象上獲取 provide 選項,provide 選項存在時,判斷 provide 是不是函數(shù),是函數(shù)就調(diào)用獲取返回配置項,否則就直接使用 provide 選項

          $mount 掛載

          根據(jù) $options 中是否存在 el 選項,決定是否自動調(diào)用 $mount 方法進入掛載階段,沒有則需要用戶手動調(diào)用 $mount 進行掛載.

          最后

          看源碼的過程中,一定要學(xué)會放棄某些內(nèi)容解讀,找準(zhǔn)本次你要了解和學(xué)習(xí)的主線內(nèi)容,否則就容易在閱讀源碼時產(chǎn)生各種支線問題,從而阻塞了主線的內(nèi)容,對應(yīng)的模塊在學(xué)習(xí)對應(yīng)內(nèi)容時深入解讀就好,不要一次性解讀所有的內(nèi)容。

          關(guān)于本文

          來源:MA

          https://juejin.cn/post/7038058903799595022


          最后

          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認(rèn)真的解答喲!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
          ?》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持


          瀏覽 70
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  天天爽夜夜爽AA片免费 | 黑人大吊AV | 亚洲国产一区二区在线 | 视频网站国产日本 | 高清操逼大片 |