Vue響應(yīng)式原理(2.6版本)

前言:此文章主要是分析Vue的響應(yīng)式原理,從new Vue開始,看源碼究竟發(fā)生了什么,其中會(huì)忽略掉大量邊界處理以及“不重要”的代碼,最后再驗(yàn)證一下,建議可以自己clone源碼,然后跟著文中的步驟跟著看,文中對(duì)代碼不會(huì)有太多講解,源碼本身的命名結(jié)構(gòu)都是很清晰的(在去除邊界處理代碼之后),主要是記錄過(guò)程,幫助以后自己回憶,如果你是對(duì)響應(yīng)式原理完全不懂的,建議可以先看看Introduction - Advanced Components | Vue Mastery或者Vue 3 Reactivity - Vue 3 Reactivity | Vue Mastery

看源碼!

  1. src/core/index.js中聲明了Vue函數(shù)
function Vue(options) { this._init(options); }
// 設(shè)置Vue.prototype._init
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue
  1. 接著來(lái)到src/core/instance/init.js,在initMixin中我們可以找到Vue中_init函數(shù)的聲明,它主要是初始化了Vue組件的聲明周期、事件、Render,然后觸發(fā)了beforeCreate的鉤子,再接著初始化Injections、Provide,再觸發(fā)了created的鉤子,最后掛載組件
export function initMixin (Vue: Class<Component>) {
    Vue.prototype._init = function(options?: Object) {
        const vm: Component = this;
        // ...Normalizing options ...
        vm._self = vm;
        initLifecycle(vm);
        initEvents(vm);
        initRender(vm);
        callHook(vm, 'beforeCreate');
        initInjections(vm);
        initState(vm);
        initProvide(vm);
        callHook(vm, 'created');
        // ...
        vm.$mount(vm,$options.el);
    }
}
  1. 這其中我們需要關(guān)注的就是initState,來(lái)到src/core/instance/state.js
export function initState (vm: Component) {
    vm._watchers = [];
    const opts = vm.$options;
    if (opts.props) initProps(vm, opts.props)
    if (opts.methods) initMethods(vm, opts.methods);
    if (opts.data) {
        initData(vm);
    } else {
        observe(vm._data = {}, true /* asRootData */);
    }
    if (opts.computed) initComputed(vm, opts.computed);
    if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch);
    }
}

可以看到,在initState中主要是初始化Props、Methods、Data、Computed(計(jì)算屬性)和Watch(偵聽器)

  1. 我們要關(guān)注的是initData,initData還是在state.js中
function initData (vm: Component) {
    let data = vm.$options.data;
    // ...
    observe(data, true /* asRootData */);
}
  1. 緊接著是observe(src/core/observer/index.js)
/**
* 嘗試為value創(chuàng)建一個(gè)observer實(shí)例,并返回這個(gè)observer
*/
export function observe(value: any, asRootData: ?boolean): Observer | void {
    // ...
    ob = new Observer(value);
    return ob;
}
  1. 繼續(xù)在src/core/observer/index.js找到Observer的Class聲明
export class Observer {
    value: any;
    dep: Dep;

    constructor (value: any) {
        this.value = value;
        // ...
        if (Array.isArray(value)) {
            // ...
            this.observeArray(value);
        } else {
            this.walk(value);
        }
    }

    walk (obj: Object) {
        const keys = Object.keys(obj);
        for (let i = 0; i< keys.length; i++) {
            defineReactive(obj, keys[i], obj[keys[i]]);
        }
    }

    observeArray (items: Array<any>) {
        for (let i = 0, l = items.length; i < l; i++) {
            observe(items[I]);
        }
    }
}

export function defineReactive (...) {
    const dep = new Dep();
    // ...
    let childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
        get: function reactiveGetter () { dep.depend(); },
        set: function reactiveSetter (newVal) { dep.notify(); }
    });
    //  ...
}
  1. 下面要看看Dep是如何定義的了,來(lái)到src/core/observer/dep.js
export default class Dep {
    static target: ?Watcher;
    subs: Array<Watcher>;
    constructor () {
        this.subs = [];
    }

    addSub (sub: Watcher) {
        this.subs.push(sub);
    }

    depend () {
        if (Dep.tartget) {
            Dep.target.addDep(this);
        }
    }

    notify () {
        const subs = this.subs.slice();
        for (let i = 0, l = subs.length; i < l; i++) {
            subs[i].update();
        }
    }
}
Dep.target = null
const targetStack = []

export function pushTarget (_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}
  1. 最后就是Watcher,讓我們把目光轉(zhuǎn)移到src/core/observer/watcher.js
import Dep, { pushTarget, popTarget } from './dep'

export deafult class Watcher {
    vm: Component;
    newDepIds: ISet;
  getter: Function;
    value: any;

    constructor (vm: Component, expOrFn: string | Function ...) {
        // ...
        this.vm = vm;
        this.getter = expOrFn;
        // ...
        this.value = this.get();
    }

    get () {
        pushTarget(this);
        const vm = this.vm;
        let value = this.getter.call(vm, vm);
        // ...
        popTarget();
        return value;
    }
    
    update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }
}

梳理一下

總的來(lái)理一遍:
首先,new Vue會(huì)調(diào)用initState,為data創(chuàng)建一個(gè)Observer類的實(shí)例,同時(shí)遞歸為data中的Object類型的property創(chuàng)建一個(gè)Observer類的實(shí)例(如果這些property中還有Object類型的屬性,則繼續(xù)遞歸創(chuàng)建Observer),并為每一個(gè)Observer都創(chuàng)建了一個(gè)Dep類的實(shí)例;
然后使用了Object.defineProperty來(lái)攔截并重寫property的get和set,在defineProperty中為每個(gè)property都創(chuàng)建了一個(gè)Dep類的實(shí)例,再在get和set中分別調(diào)用了dep.depend()和dep.notify()用于將當(dāng)前的Watcher添加到dep的訂閱列表中和觸發(fā)訂閱列表中Wacther的updat方法;而Watcher才是真正記錄了使用響應(yīng)式數(shù)據(jù)的函數(shù)的類

驗(yàn)證一下

來(lái)看一個(gè)簡(jiǎn)單的例子:

<template>
  <div>
    <h1>{{ obj.name }}</h1>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      obj1: {
        name: "test",
        obj2: {
          test: 'test'
        },
      },
    };
  },
  mounted() {
    console.log(this.obj);
  },
};
</script>

來(lái)看看控制臺(tái)的打?。?/p>

  1. 首先,data中的obj是有一個(gè)ob指向它的Observer的(data也有,但并沒(méi)有直接有一個(gè)ob屬性指向它的Observer)

  2. 然后我們也能看到obj中的obj也是有一個(gè)ob屬性的,同樣指向它的Observer,同時(shí)也能看到每一個(gè)Observer都對(duì)應(yīng)的有一個(gè)Dep

  3. 接著就是確認(rèn)get和set中的dep,下面是get name和set name中的dep,可以通過(guò)id確認(rèn)這兩個(gè)dep是同一個(gè)dep,均為defineProperty時(shí)閉包中的dep




    至此響應(yīng)式原理分析和驗(yàn)證告一段落,最后放一張圖幫助理解


    14F77938-0913-4378-99E6-04B75BE27E5B.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容