Vue vue2源碼解析

入口文件

vue-2.6.11\src\platforms\web\entry-runtime-with-compiler.js

  1. 從vue原型中拿出$mount進(jìn)行覆蓋凶朗,

    // 17行
    const mount = Vue.prototype.$mount
    
  2. 解析模板相關(guān)選項(xiàng)

    // 18行
    Vue.prototype.$mount = function (
      el?: string | Element,// 宿主元素
      hydrating?: boolean
    ): Component {
      //獲取真實(shí)DOM
      el = el && query(el)
    // 34行
    // 先判斷有沒有render远荠,render函數(shù)優(yōu)先級最高
    // 優(yōu)先級:render > template > el
    if (!options.render) {
        let template = options.template
        // 再判斷有沒有template并解析
        if (template) {
          if (typeof template === 'string') {
            if (template.charAt(0) === '#') {
              template = idToTemplate(template)
              /* istanbul ignore if */
              if (process.env.NODE_ENV !== 'production' && !template) {
              }
            }
          } else if (template.nodeType) {
            template = template.innerHTML
          } else {
            return this
          }
        // 最后查看el選項(xiàng)
        } else if (el) {
          template = getOuterHTML(el)
        }
        // 處理模板的方式砸抛,編譯它亚铁,最終目標(biāo)是render
        if (template) {
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
            mark('compile')
          }
    
          const { render, staticRenderFns } = compileToFunctions(template, {
            outputSourceRange: process.env.NODE_ENV !== 'production',
            shouldDecodeNewlines,
            shouldDecodeNewlinesForHref,
            delimiters: options.delimiters,
            comments: options.comments
          }, this)
          // 重新復(fù)制給選項(xiàng)
          options.render = render
          options.staticRenderFns = staticRenderFns
    
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
            mark('compile end')
            measure(`vue ${this._name} compile`, 'compile', 'compile end')
          }
        }
      }
    

初始化掛載

vue-2.6.11\src\platforms\web\runtime\index.js

  1. 安裝平臺特有的補(bǔ)丁(patch)函數(shù):做初始化和更新蝇刀,為了實(shí)現(xiàn)跨平臺,平臺特有的操作徘溢。

    // 33行
    Vue.prototype.__patch__ = inBrowser ? patch : noop
    
  2. 實(shí)現(xiàn)$mount:初始化掛載吞琐。

    1. $mount('#app')=>mountComponent:render()=>vdom=>patch()=>dom=>appendChile()
    2. 初始化時執(zhí)行$mount('#app') ,在內(nèi)部調(diào)用mountComponent然爆,調(diào)用內(nèi)部的render函數(shù)顽分,核心目標(biāo)把當(dāng)前虛擬DOM樹傳給patch()函數(shù)變成真實(shí)DOM,真實(shí)DOM掛載到#app上施蜜。
    // 36行
    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
      el = el && inBrowser ? query(el) : undefined
      return mountComponent(this, el, hydrating)
    }
    

初始化全局API

vue-2.6.11\src\core\index.js

  1. 初始化全局API:Vue.use,component,directive,filter,mixin,set,extend,delete

    // 6行
    initGlobalAPI(Vue)
    

Vue的構(gòu)造函數(shù)

vue-2.6.11\src\core\instance\index.js

  1. 構(gòu)造函數(shù):new Vue(options)

    // 8行
    function Vue (options) {
      // 初始化
      this._init(options)
    }
    
  2. 聲明實(shí)例屬性和方法卒蘸,把構(gòu)造函數(shù)傳遞進(jìn)去,通過混入模式把原型加上_init方法缸沃。

    initMixin(Vue)
    // 熟悉的其他梳理屬性和方法,有下面這些混入
    stateMixin(Vue)
    eventsMixin(Vue)
    lifecycleMixin(Vue)
    renderMixin(Vue)
    

初始化

vue-2.6.11\src\core\instance\init.js

  1. 原型掛載_init實(shí)現(xiàn)原型方法翘单,在任何實(shí)例都可使用vm._init

    Vue.prototype._init = function (options?: Object)
    
  2. 合并選項(xiàng)

    // 30行
    // 合并選項(xiàng):new Vue傳入的時用戶配置貌亭,需要和系統(tǒng)配置合并
    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
       )
    }
    
  3. 初始化操作

    // 52行
    // 生命周期初始化 組件實(shí)例相關(guān)的屬性初始化,$parent,$root,$children,$refs
    initLifecycle(vm)
    // 事件初始化 監(jiān)聽自定義事件
    initEvents(vm)
    // 解析插槽剧腻,$slots,$scopeSlots,$createElement()
    initRender(vm)
    callHook(vm, 'beforeCreate')// beforeCreate生命周期
    
    // 組件狀態(tài)相關(guān)的數(shù)據(jù)操作
    // inject/provide 注入祖輩傳遞下來的數(shù)據(jù)。
    initInjections(vm) // 
    // 把自己內(nèi)部的狀態(tài)進(jìn)行響應(yīng)式的處理蕊温,`props`,`methods`,`data`,`computed`,`watch`
    initState(vm)
    // 提供給后代,用來隔代傳遞參數(shù)
    initProvide(vm) 
    callHook(vm, 'created')// created生命周期
    
  4. initEvents(vm)

    1. 為什么要從父組件找Listeners凉翻?
      1. 子組件上有事件選項(xiàng)制轰,但是在父組件內(nèi)聲明的。
      2. 派發(fā)和監(jiān)聽者都是子組件本身(this.$on(),this.$emit());
      3. 事件的真正監(jiān)聽者時子組件自己调俘。
      4. updateComponentListeners從父組件身上拿出事件給子組件。
    export function initEvents (vm: Component) {
      vm._events = Object.create(null)
      vm._hasHookEvent = false
      // 找到父組件的選項(xiàng)
      const listeners = vm.$options._parentListeners
      if (listeners) {
        updateComponentListeners(vm, listeners)
      }
    }
    
  5. initRender(vm)

    // 34行
    // 這里的$createElement就是render(h)
    vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
    
  6. 如果設(shè)置了el骇钦,自動執(zhí)行$mount()

    if (vm.$options.el) {
       vm.$mount(vm.$options.el)
    }
    

Vue執(zhí)行流程

  1. 進(jìn)入src\core\instance\index.js文件內(nèi)的Vue函數(shù)窥翩,執(zhí)行this._init(options)寇蚊。

  2. 再進(jìn)入src\core\instance\init.js文件內(nèi)的initMixin函數(shù)糊闽。

    1. 32行此時的options還是用戶設(shè)置的初始化選項(xiàng)右犹,data盼忌,el等谦纱。

    2. 先判斷是否為isComponent,若不是執(zhí)行mergeOptions將用戶設(shè)置和系統(tǒng)設(shè)置進(jìn)行合并祠乃。

      1. 合并完成后vm.$options是合并后的結(jié)果,可使用全局組件等{components{KeepAlive,Transition,TransitionGroup},data,el,filters}
    3. 執(zhí)行initProxy進(jìn)行代理設(shè)置嘱支。

    4. 依次執(zhí)行初始化操作,

      1. initLifecycle(vm)馍盟,vm新增屬性$children,$parent,$refs但是值為空八毯,$rootVue根實(shí)例。

      2. initEvents(vm)泊交,vm新增屬性_events事件監(jiān)聽

      3. callHook(vm, 'beforeCreate')派發(fā)生命周期廓俭,所以此時不可以操作數(shù)據(jù)。

      4. initState(vm)雹熬,數(shù)據(jù)的代理和響應(yīng)式發(fā)生在這里,vm新增數(shù)據(jù)相應(yīng)式烈菌,新增_data(響應(yīng)式有_ob_),$data(非響應(yīng)式)

        1. 進(jìn)入文件src\core\instance\state.js,執(zhí)行initState函數(shù)捂襟,如果有重名按處理順序,誰先占用誰使用宠漩。

          1. if (opts.props) initProps(vm, opts.props)處理props扒吁。

          2. if (opts.methods) initMethods(vm, opts.methods)處理methods魁索。

          3. if (opts.data) { initData(vm) } else {observe(vm._data = {}, true) }處理data刽虹。

            1. 如果有data執(zhí)行initData(vm)
              1. 執(zhí)行initData函數(shù)当辐,判斷是函數(shù)還是對象再做相應(yīng)處理瀑构。
              2. 校驗(yàn)命名沖突世吨,根據(jù)propsmethods罢浇。
              3. 最后執(zhí)行observe(data, true)遞歸響應(yīng)式處理。
            2. 如果沒有data執(zhí)行observe(vm._data = {}, true)胞锰。
              1. 進(jìn)入文件core\observer\index.js
              2. 獲取Ob實(shí)例,是否作為根數(shù)據(jù)凌那。
                1. 如果有_ob_直接返回,ob = value.__ob__
                2. 如果沒有則創(chuàng)建励稳,ob = new Observer(value)
                  1. 初始化時先給data的值創(chuàng)建一個ob麦锯,結(jié)果對象就有幾個ob鹅巍。
                  2. 創(chuàng)建dep實(shí)例this.dep = new Dep():對象也需要dep,對象如果動態(tài)增減屬性敛苇。
                  3. Observer區(qū)分對象還是數(shù)組。
                  4. 判斷類型来涨,指定ob實(shí)例。
                    1. 如果時數(shù)組執(zhí)行數(shù)組響應(yīng)式this.observeArray(value)卧抗。
                      1. 進(jìn)入文件src\core\observer\array.js
                      2. 默認(rèn)的7個方法不會通知更新浦马,將原有7個方法攔截修改晶默。
                      3. 獲取數(shù)組原型,const arrayProto = Array.prototype坞靶。
                      4. 克隆一份新原型,export const arrayMethods = Object.create(arrayProto)尿这。
                      5. 7個變更方法需要覆蓋,const methodsToPatch = [ 'push', 'pop', 'shift','unshift', 'splice', 'sort', 'reverse']叨橱。因?yàn)檫@7個會改變數(shù)組,其他的返回新數(shù)組栖博。
                      6. 遍歷7個方法,每次拿出一個方法保存原始方法const original = arrayProto[method]開始覆蓋。
                      7. 執(zhí)行默認(rèn)方法const result = original.apply(this, args)公你。
                      8. 對新加入的元素進(jìn)行響應(yīng)式if (inserted) ob.observeArray(inserted)
                      9. 變更通知const ob = this.__ob__
                      10. ob內(nèi)部有dep税肪,讓dep通知更新ob.dep.notify()锻梳。
                      2. 如果是對象執(zhí)行對象響應(yīng)式this.walk(value),執(zhí)行defineReactive(obj, keys[i])神汹。
                      1. 每個key對應(yīng)一個depconst dep = new Dep()
                      2. 依賴收集dep.depend()桃漾,vue2中一個組件是一個Watcher敦迄。
                      1. dep:n=>wtacher:1罚屋,多對一撕彤。
                      2. 通過diff算法比對變化。
                      3. 進(jìn)入文件src\core\observer\dep.js
                      1. depend函數(shù)內(nèi)執(zhí)行的Dep.target.addDep(this)猛拴,watcher.addDep()羹铅。
                      2. 進(jìn)入文件src\core\observer\watcher.jsaddDep函數(shù)。
                      1. 相互添加引用的過程愉昆。
                      2. watcher添加dep职员,this.newDepIds.add(id)哥蔚,this.newDeps.push(dep)
                      3. dep添加watcher深夯,dep.addSub(this)
                      3. 用戶手動創(chuàng)建Watcher翩肌,this.$watch(key,cb)隶糕。
                      1. dep:n=>wtacher:n灾常,多對多。
                      4. 子ob也要做依賴收集工作childOb.dep.depend()
          4. if (opts.computed) initComputed(vm, opts.computed)處理computed螟蒸。

          5. if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) }處理watch

    5. 執(zhí)行掛載過程vm.$mount(vm.$options.el)

  3. 進(jìn)入文件src\platforms\web\runtime\index.js執(zhí)行Vue.prototype.$mount掛載函數(shù)。

    1. 執(zhí)行mountComponent(this, el, hydrating)將虛擬DOM轉(zhuǎn)換成真實(shí)DOM猴誊。
  4. 進(jìn)入文件src\core\instance\lifecycle.js

    1. 執(zhí)行mountComponent函數(shù)內(nèi)部畏吓,掛載callHook(vm, 'beforeMount')生命周期鉤子包吝。
    2. 聲明updateComponent組件更新函數(shù)函數(shù)未執(zhí)行储矩。
      1. vm._render()渲染函數(shù)。
      2. vm._update()更新函數(shù)绝编。
    3. new Watcher內(nèi)部會執(zhí)行updateComponent,這次才真正執(zhí)行updateComponent,執(zhí)行函數(shù)內(nèi)部的vm._render()渲染函數(shù)。
      1. 進(jìn)入文件src\core\instance\render.js田绑,執(zhí)行Vue.prototype._render,渲染函數(shù)的目的是得到虛擬DOM吆你。
        1. vnode = render.call(vm._renderProxy, vm.$createElement)獲得虛擬DOM咸包。
    4. 再執(zhí)行lifecycle.js文件內(nèi)執(zhí)行vm._update()更新函數(shù)柠衅。
      1. 判斷是否有虛擬DOM。
        1. 若沒有則通過vm._patch_創(chuàng)建挤忙,虛擬DOM將變成真實(shí)DOM。
        2. 若有則通過vm._patch_更新。
vue執(zhí)行流程

new Vue({})發(fā)生了什么?

  1. 選項(xiàng)的合并纵苛,用戶選項(xiàng)和系統(tǒng)選項(xiàng)的合并宪躯。
  2. 組件實(shí)例相關(guān)的屬性初始化,$parent,$root,$children,$refs
  3. 監(jiān)聽自定義事件。
  4. 解析插槽,$slots,$scopeSlots,$createElement()
  5. 注入祖輩傳遞下來的數(shù)據(jù)。
  6. 把自己內(nèi)部的狀態(tài)進(jìn)行響應(yīng)式的處理柳击,props,methods,data,computed,watch
  7. 提供給后代盲再,用來隔代傳遞參數(shù)

Vue數(shù)據(jù)響應(yīng)式

  1. Vue中利用了JS語言特性
    Object.defineProperty()西设,通過定義對象屬性getter/setter攔截對屬性的訪問。
    具體實(shí)現(xiàn)是在Vue初始化時洲胖,會調(diào)用initState济榨,它會初始化data,props等绿映,這里著重關(guān)注data初始
    化擒滑。
  2. Vue1最大問題watcher過多,項(xiàng)目大到一定程度容易崩潰叉弦,所以導(dǎo)致Vue1性能很差丐一。
  3. Vue2的解決方法,把watcher粒度降低淹冰,一個組件一個watcher库车。
  4. 如果有數(shù)據(jù)發(fā)生變化,通過Diff算法把兩次計(jì)算的結(jié)果比較樱拴,才能知道那里變化并進(jìn)行更新柠衍。
  5. Watcher和屬性key之間的關(guān)系是1對N,正好跟Vue1的一對N是相反晶乔。
  6. 當(dāng)用戶自己編寫表達(dá)式this.$watch('foo',function(){})時珍坊,會產(chǎn)生新的watcher實(shí)例,此時'foo'又跟多個watcer產(chǎn)生關(guān)系正罢,N對N阵漏。
  7. 在依賴收集過程中相互添加關(guān)系,watcher.addDep()
  8. 一個對象一個Observer履怯,內(nèi)部有個大管家dep回还。
    1. 大管家負(fù)責(zé)對象新增或刪除屬性的更新通知。
  9. 一個key一個小管家dep叹洲。
    1. 小關(guān)節(jié)負(fù)責(zé)對應(yīng)key的值變化的更新通知柠硕。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市疹味,隨后出現(xiàn)的幾起案子仅叫,更是在濱河造成了極大的恐慌,老刑警劉巖糙捺,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異笙隙,居然都是意外死亡洪灯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門竟痰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來签钩,“玉大人,你說我怎么就攤上這事坏快∏﹂荩” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵莽鸿,是天一觀的道長昧旨。 經(jīng)常有香客問我,道長祥得,這世上最難降的妖魔是什么兔沃? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮级及,結(jié)果婚禮上乒疏,老公的妹妹穿的比我還像新娘。我一直安慰自己饮焦,他們只是感情好怕吴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著县踢,像睡著了一般转绷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上殿雪,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天暇咆,我揣著相機(jī)與錄音,去河邊找鬼。 笑死爸业,一個胖子當(dāng)著我的面吹牛其骄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扯旷,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拯爽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钧忽?” 一聲冷哼從身側(cè)響起毯炮,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耸黑,沒想到半個月后桃煎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡大刊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年为迈,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缺菌。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡葫辐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伴郁,到底是詐尸還是另有隱情耿战,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布焊傅,位于F島的核電站剂陡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏租冠。R本人自食惡果不足惜鹏倘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望顽爹。 院中可真熱鬧纤泵,春花似錦、人聲如沸镜粤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肉渴。三九已至公荧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間同规,已是汗流浹背循狰。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工窟社, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绪钥。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓灿里,卻偏偏與公主長得像,于是被迫代替她去往敵國和親程腹。 傳聞我的和親對象是個殘疾皇子匣吊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容