1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        從 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)化等。

        準備工作

        再開始閱讀源碼之前,有些事還是必須要做的,那就是拉取 源倉庫代碼[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 對象
        • 把當前組件配置項打平然后賦值到 $options 對象,避免了原型鏈的動態(tài)查找
        • 如果當前組件配置項中存在 render 選項,就把它添加到 $options 對象上

        根組件 —— resolveConstructorOptions() 方法

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

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

        根組件 —— mergeOptions() 方法

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

        • 對配置選項進行標準化
        • 對傳入的原始配置對象進行合并
        • 返回新的配置對象

        根組件合并配置項,就是將全局配置項合并到根組件局部配置項中,比如將全局注冊的 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)聽了這個事件呢?

        回答:也許你會認為是 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)進行了標準化處理,所以這里的 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è)置了默認值,如果設(shè)置了默認值,就將其賦值為默認值

        ![](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`?等.

        ##?初始化自定義事件

        ###?問題:當在組件上使用自定義事件時,父組件和子組件誰負責(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 等都沒有進行初始化,所以就都不能進行訪問,當然如果你在 beforeCreate 中通過異步的方式訪問,比如 setTimeout 其實是可以的,最早能訪問數(shù)據(jù)的地方其實就是 created 當中了.

        初始化 inject 選項

        • 根據(jù) inject 選項從祖代組件配置項中找到對應(yīng)的 provide 選項,從而獲取對應(yīng) key 中的值,得到 result[key] = val 形式的結(jié)果
        • 如果找到根組件上還不存在,就判斷是否有 default 選項,有就設(shè)置默認值
        • 把得到的 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)容解讀,找準本次你要了解和學(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ù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認真的解答喲!
        回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
        回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
        如果這篇文章對你有幫助,在看」是最大的支持
        ?》》面試官也在看的算法資料《《
        “在看和轉(zhuǎn)發(fā)”就是最大的支持


        瀏覽 70
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            a一级毛片直接观看 | 岛国AV免费看 | 国产一区二区精品丝袜 | 六月婷婷综合网 | R四虎18 | 午夜精品一区二区三区在线视频 | 鲤鱼乡全肉整夜不拔bl双珏记 | 日本美女激情 | 成人自拍偷拍视频 | 麻豆tv在线入口 |