九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
干貨|圖解 Vue 響應(yīng)式原理
  • Vue 初始化
  • 模板渲染
  • 組件渲染

本文 Vue 源碼版本:2.6.11,為了便于理解,均有所刪減。

本文將從以下兩個(gè)方面進(jìn)行探索:

  • 從 Vue 初始化,到首次渲染生成 DOM 的流程。

  • 從 Vue 數(shù)據(jù)修改,到頁(yè)面更新 DOM 的流程。

Vue 初始化

先從最簡(jiǎn)單的一段 Vue 代碼開始:

<template>
<div>
{{ message }}
</div>
</template>
<script>
new Vue({
data() {
return {
message: 'hello world',
};
},
});
</script>

這段代碼很簡(jiǎn)單,最終會(huì)在頁(yè)面上打印一個(gè) hello world,它是如何實(shí)現(xiàn)的呢?

我們從源頭:new Vue 的地方開始分析。

// 執(zhí)行 new Vue 時(shí)會(huì)依次執(zhí)行以下方法
// 1. Vue.prototype._init(option)
// 2. initState(vm)
// 3. observe(vm._data)
// 4. new Observer(data)

// 5. 調(diào)用 walk 方法,遍歷 data 中的每一個(gè)屬性,監(jiān)聽數(shù)據(jù)的變化。
function walk(obj) {
  const keys = Object.keys(obj);
  for (let i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i]);
  }
}

// 6. 執(zhí)行 defineProperty 監(jiān)聽數(shù)據(jù)讀取和設(shè)置。
function defineReactive(obj, key, val) {
  // 為每個(gè)屬性創(chuàng)建 Dep(依賴搜集的容器,后文會(huì)講)
  const dep = new Dep();
  // 綁定 get、set
  Object.defineProperty(obj, key, {
    get() {
      const value = val;
      // 如果有 target 標(biāo)識(shí),則進(jìn)行依賴搜集
      if (Dep.target) {
        dep.depend();
      }
      return value;
    },
    set(newVal) {
      val = newVal;
      // 修改數(shù)據(jù)時(shí),通知頁(yè)面重新渲染
      dep.notify();
    },
  });
}

數(shù)據(jù)描述符綁定完成后,我們就能得到以下的流程圖:

圖中我們可以看到,Vue 初始化時(shí),進(jìn)行了數(shù)據(jù)的 get、set 綁定,并創(chuàng)建了一個(gè) Dep 對(duì)象。

對(duì)于數(shù)據(jù)的 get、set 綁定我們并不陌生,但是 Dep 對(duì)象什么呢?

Dep 對(duì)象用于依賴收集,它實(shí)現(xiàn)了一個(gè)發(fā)布訂閱模式,完成了數(shù)據(jù) Data 和渲染視圖 Watcher 的訂閱,我們一起來剖析一下。

class Dep {
  // 根據(jù) ts 類型提示,我們可以得出 Dep.target 是一個(gè) Watcher 類型。
  static target: ?Watcher;
  // subs 存放搜集到的 Watcher 對(duì)象集合
  subs: Array<Watcher>;
  constructor() {
    this.subs = [];
  }
  addSub(sub: Watcher) {
    // 搜集所有使用到這個(gè) data 的 Watcher 對(duì)象。
    this.subs.push(sub);
  }
  depend() {
    if (Dep.target) {
      // 搜集依賴,最終會(huì)調(diào)用上面的 addSub 方法
      Dep.target.addDep(this);
    }
  }
  notify() {
    const subs = this.subs.slice();
    for (let i = 0, l = subs.length; i < l; i++) {
      // 調(diào)用對(duì)應(yīng)的 Watcher,更新視圖
      subs[i].update();
    }
  }
}

根據(jù)對(duì) Dep 的源碼分析,我們得到了下面這張邏輯圖:

了解 Data 和 Dep 之后,我們來繼續(xù)揭開 Watcher 的面紗。

class Watcher {
  constructor(vm: Component, expOrFn: string | Function) {
    // 將 vm._render 方法賦值給 getter。
    // 這里的 expOrFn 其實(shí)就是 vm._render,后文會(huì)講到。
    this.getter = expOrFn;
    this.value = this.get();
  }
  get() {
    // 給 Dep.target 賦值為當(dāng)前 Watcher 對(duì)象
    Dep.target = this;
    // this.getter 其實(shí)就是 vm._render
    // vm._render 用來生成虛擬 dom、執(zhí)行 dom-diff、更新真實(shí) dom。
    const value = this.getter.call(this.vm, this.vm);
    return value;
  }
  addDep(dep: Dep) {
    // 將當(dāng)前的 Watcher 添加到 Dep 收集池中
    dep.addSub(this);
  }
  update() {
    // 開啟異步隊(duì)列,批量更新 Watcher
    queueWatcher(this);
  }
  run() {
    // 和初始化一樣,會(huì)調(diào)用 get 方法,更新視圖
    const value = this.get();
  }
}

源碼中我們看到,Watcher 實(shí)現(xiàn)了渲染方法 _render 和 Dep 的關(guān)聯(lián), 初始化 Watcher 的時(shí)候,打上 Dep.target 標(biāo)識(shí),然后調(diào)用 get 方法進(jìn)行頁(yè)面渲染。加上上文的 Data,目前 Data、Dep、Watcher 三者的關(guān)系如下:

我們?cè)倮ù幌抡麄€(gè)流程:Vue 通過 defineProperty 完成了 Data 中所有數(shù)據(jù)的代理,當(dāng)數(shù)據(jù)觸發(fā) get 查詢時(shí),會(huì)將當(dāng)前的 Watcher 對(duì)象加入到依賴收集池 Dep 中,當(dāng)數(shù)據(jù) Data 變化時(shí),會(huì)觸發(fā) set 通知所有使用到這個(gè) Data 的 Watcher 對(duì)象去 update 視圖。

目前的整體流程如下:

上圖的流程中 Data 和 Dep 都是 Vue 初始化時(shí)創(chuàng)建的,但現(xiàn)在我們并不知道 Wacher 是從哪里創(chuàng)建的,帶著這個(gè)問題,我們接著往下探索。

模板渲染

上文中,我們分析了初始化 Vue 過程中處理數(shù)據(jù)的部分,接下來,我們分析一下數(shù)據(jù)渲染的部分。

其實(shí) new Vue 執(zhí)行到最后,會(huì)調(diào)用 mount 方法,將 Vue 實(shí)例渲染成 dom 。

// new Vue 執(zhí)行流程。
// 1. Vue.prototype._init(option)
// 2. vm.$mount(vm.$options.el)
// 3. render = compileToFunctions(template) ,編譯 Vue 中的 template 模板,生成 render 方法。
// 4. Vue.prototype.$mount 調(diào)用上面的 render 方法掛載 dom。
// 5. mountComponent

// 6. 創(chuàng)建 Watcher 實(shí)例
const updateComponent = () => {
  vm._update(vm._render());
};
// 結(jié)合上文,我們就能得出,updateComponent 就是傳入 Watcher 內(nèi)部的 getter 方法。
new Watcher(vm, updateComponent);

// 7. new Watcher 會(huì)執(zhí)行 Watcher.get 方法
// 8. Watcher.get 會(huì)執(zhí)行 this.getter.call(vm, vm) ,也就是執(zhí)行 updateComponent 方法
// 9. updateComponent 會(huì)執(zhí)行 vm._update(vm._render())

// 10. 調(diào)用 vm._render 生成虛擬 dom
Vue.prototype._render = function (): VNode {
  const vm: Component = this;
  const { render } = vm.$options;
  let vnode = render.call(vm._renderProxy, vm.$createElement);
  return vnode;
};
// 11. 調(diào)用 vm._update(vnode) 渲染虛擬 dom
Vue.prototype._update = function (vnode: VNode) {
  const vm: Component = this;
  if (!prevVnode) {
    // 初次渲染
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false);
  } else {
    // 更新
    vm.$el = vm.__patch__(prevVnode, vnode);
  }
};
// 12. vm.__patch__ 方法就是做的 dom diff 比較,然后更新 dom,這里就不展開了。

看完 Vue 模板渲染的過程,我們可以得到如下的流程圖:

到這里,我們就知道了 Watcher 其實(shí)是在 Vue 初始化的階段創(chuàng)建的,屬于生命周期中 beforeMount 的位置創(chuàng)建的,創(chuàng)建 Watcher 時(shí)會(huì)執(zhí)行 render 方法,最終將 Vue 代碼渲染成真實(shí)的 DOM。

我們?cè)賹⒅暗牧鞒陶弦幌拢湍艿玫揭韵碌牧鞒蹋?/p>

上圖分析了 Vue 初始化到渲染 DOM 的整個(gè)過程,最后我們?cè)俜治鲆幌拢?dāng)數(shù)據(jù)變化時(shí),Vue 又是怎么進(jìn)行更新的?

其實(shí),在上圖也能看出,在 Data 變化時(shí),會(huì)調(diào)用 Dep.notify 方法,隨即調(diào)用 Watcher 內(nèi)部的 update 方法,此方法會(huì)將所有使用到這個(gè) Data 的 Watcher 加入一個(gè)隊(duì)列,并開啟一個(gè)異步隊(duì)列進(jìn)行更新,最終執(zhí)行 _render 方法完成頁(yè)面更新。

整體的流程如下:

好了,探索到這里,Vue 的響應(yīng)式原理,已經(jīng)被我們分析透徹了,如果你還沒有明白,不妨再細(xì)品一下上圖。

組件渲染

本來探索到上面的流程圖就結(jié)束了,但好奇的我又想到了一個(gè)問題 ??。

Vue 組件又是怎么渲染的呢?

帶著這個(gè)問題,我繼續(xù)查閱了源碼。

// 從模板編譯開始,當(dāng)發(fā)現(xiàn)一個(gè)自定義組件時(shí),會(huì)執(zhí)行以下函數(shù)
// 1. compileToFunctions(template)
// 2. compile(template, options);
// 3. const ast = parse(template.trim(), options)
// 4. const code = generate(ast, options)
// 5. createElement

// 6. createComponent
export function createComponent(
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  // $options._base 其實(shí)就是全局 Vue 構(gòu)造函數(shù),在初始化時(shí) initGlobalAPI 中定義的:Vue.options._base = Vue
  const baseCtor = context.$options._base;
  // Ctor 就是 Vue 組件中 <script> 標(biāo)簽下 export 出的對(duì)象
  if (isObject(Ctor)) {
    // 將組件中 export 出的對(duì)象,繼承自 Vue,得到一個(gè)構(gòu)造函數(shù)
    // 相當(dāng)于 Vue.extend(YourComponent)
    Ctor = baseCtor.extend(Ctor);
  }
  const vnode = new VNode(`vue-component-${Ctor.cid}xxx`);
  return vnode;
}

// 7. 實(shí)現(xiàn)組件繼承 Vue,并調(diào)用 Vue._init 方法,進(jìn)行初始化
Vue.extend = function (extendOptions: Object): Function {
  const Super = this;
  const Sub = function VueComponent(options) {
    // 調(diào)用 Vue.prototype._init,之后的流程就和首次加載保持一致
    this._init(options);
  };
  // 原型繼承,相當(dāng)于:Component extends Vue
  Sub.prototype = Object.create(Super.prototype);
  Sub.prototype.constructor = Sub;
  return Sub;
};

看完組件渲染的源碼后,結(jié)合上文,重新整理了一張流程圖,圖中的藍(lán)色部分就是渲染組件的過程。

好了,現(xiàn)在是真的結(jié)束了,最終的流程圖就是上面的這一張圖。

問個(gè)問題,現(xiàn)在你理解 Vue 響應(yīng)式原理了嗎?

如果仍覺得不好理解,我這里還準(zhǔn)備了一張帶標(biāo)注的簡(jiǎn)圖 ??

思考與總結(jié)

本文從源碼的角度,介紹了 Vue 響應(yīng)式原理,來簡(jiǎn)單回顧一下吧。

  1. 從 new Vue 開始,首先通過 get、set 監(jiān)聽 Data 中的數(shù)據(jù)變化,同時(shí)創(chuàng)建 Dep 用來搜集使用該 Data 的 Watcher。
  2. 編譯模板,創(chuàng)建 Watcher,并將 Dep.target 標(biāo)識(shí)為當(dāng)前 Watcher。
  3. 編譯模板時(shí),如果使用到了 Data 中的數(shù)據(jù),就會(huì)觸發(fā) Data 的 get 方法,然后調(diào)用 Dep.addSub 將 Watcher 搜集起來。
  4. 數(shù)據(jù)更新時(shí),會(huì)觸發(fā) Data 的 set 方法,然后調(diào)用 Dep.notify 通知所有使用到該 Data 的 Watcher 去更新 DOM。
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Vue2源碼分析
Vue2 源碼閱讀(三) 雙向綁定原理
前端進(jìn)階-手寫Vue2.0源碼(三)|技術(shù)點(diǎn)評(píng)
面試官:聊聊對(duì)Vue.js框架的理解
深入剖析:Vue核心之虛擬DOM
<keep-alive> 緩存及其緩存優(yōu)化原理
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服