Vue 響應(yīng)式原理解析

vue框架 中最核心的就是 vue的響應(yīng)式 ,通過對vuedata數(shù)據(jù)的變更實現(xiàn)頁面效果的重新渲染。但在實際開發(fā)中經(jīng)常會有人發(fā)現(xiàn)明明更改了對應(yīng)數(shù)據(jù)的值,但是vue卻沒有重新渲染。明明說好了是響應(yīng)式的,但為什么有的數(shù)據(jù)可以通過響應(yīng)式實現(xiàn),而有的只能通過vue.set方法實現(xiàn)。這里就會有一個疑問:vue的響應(yīng)式到底是怎么回事,他的原理又是什么

這個文章主要就是針對這個疑問所對Vue 響應(yīng)式的一個解析。

前言

直白的描述下響應(yīng)式,就是對你數(shù)據(jù)的變化,vue會有一個響應(yīng),去完成某件事。

由于Vue 的核心庫只關(guān)注視圖層,所以這件事就是去重新渲染頁面。具體是局部渲染還是全部渲染,這個是基于vue的一個性能優(yōu)化,本篇文章暫時就不談了。

看到這里應(yīng)該明白了些,vue的響應(yīng)式原理是基于vue知道數(shù)據(jù)發(fā)生了改變。

所以針對上文提到的可以重新描述下問題:什么時候的賦值是才不是vue的盲區(qū)?

Vue數(shù)據(jù)劫持

數(shù)據(jù)劫持

數(shù)據(jù)劫持: vue.js 則是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在數(shù)據(jù)變動時發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)。

Object.defineProperty() 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現(xiàn)有屬性, 并返回這個對象。

如下面的代碼塊就是一個很簡單的Getter/Setter構(gòu)造器。
當(dāng)objparam被賦值的時候,執(zhí)行set對應(yīng)的方法;當(dāng)objparam被取值的時候,執(zhí)行g(shù)et對應(yīng)的方法。

    var obj = {};
    Object.defineProperty(obj,"param",{
        set :function SelfSetter(newVal) {
             console.log("被賦值")
             this._param = newVal
        },
        get :function SelfGetter () {
            console.log("取值")
            return this._param
        }
    })

  obj.param = 'newparam';  // 被賦值
  console.log(obj.param); // 取值  newparam

通過下方源碼可以看到,當(dāng)set的回調(diào)觸發(fā)之后將數(shù)據(jù)的變動推送給訂閱者。

// Vue.js v2.5.13 #964-1018
function defineReactive (
        obj,
        key,
        val,
        customSetter,
        shallow
    ) {
        var dep = new Dep();

        var property = Object.getOwnPropertyDescriptor(obj, key);
        if (property && property.configurable === false) {
            return
        }

        // cater for pre-defined getter/setters
        var getter = property && property.get;
        var setter = property && property.set;

        var childOb = !shallow && observe(val);
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: function reactiveGetter () {
                var value = getter ? getter.call(obj) : val;
                if (Dep.target) {
                    dep.depend();
                    if (childOb) {
                        childOb.dep.depend();
                        if (Array.isArray(value)) {
                            dependArray(value);
                        }
                    }
                }
                return value
            },
            set: function reactiveSetter (newVal) {
                var value = getter ? getter.call(obj) : val;
                /* eslint-disable no-self-compare */
                if (newVal === value || (newVal !== newVal && value !== value)) {
                    return
                }
                /* eslint-enable no-self-compare */
                if ("development" !== 'production' && customSetter) {
                    customSetter();
                }
                if (setter) {
                    setter.call(obj, newVal);
                } else {
                    val = newVal;
                }
                childOb = !shallow && observe(newVal);
                dep.notify();
            }
        });
    }

發(fā)布消息給訂閱者

在上述vue源碼中可以看到,當(dāng)set的回調(diào)觸發(fā)時,回去執(zhí)行dep.notify();發(fā)布消息給訂閱者。

迭代數(shù)據(jù)劫持

上述描述中僅僅只是對數(shù)據(jù)的一個屬性進行了劫持,vue中通過observe方法實現(xiàn)所有的數(shù)據(jù)屬性的劫持。

// Vue.js v2.5.13 #934-959
function observe (value, asRootData) {
        if (!isObject(value) || value instanceof VNode) {
            return
        }
        var ob;
        if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
            ob = value.__ob__;
        } else if (
            observerState.shouldConvert &&
            !isServerRendering() &&
            (Array.isArray(value) || isPlainObject(value)) &&
            Object.isExtensible(value) &&
            !value._isVue
        ) {
            ob = new Observer(value);
        }
        if (asRootData && ob) {
            ob.vmCount++;
        }
        return ob
    }

數(shù)據(jù)劫持盲區(qū)

在日常開發(fā)時候發(fā)現(xiàn)的賦值之后沒有渲染頁面,也就是說該屬性沒有被劫持,當(dāng)變化時候沒有去發(fā)布消息給訂閱者,屬于盲區(qū)。

簡述

  • vue在實例化的時候會將data數(shù)據(jù)中的屬性都做數(shù)據(jù)劫持。
  • 如果是對象,也會迭代本身屬性將全部屬性都實現(xiàn)數(shù)據(jù)劫持。
  • 當(dāng)賦值的時候,如果是newVal是對象,也會去迭代newVal的屬性實現(xiàn)全部屬性的數(shù)據(jù)劫持

那在什么情況下會有盲區(qū):如果對一個對象添加一個新的屬性 如obj本身沒有newparam屬性,現(xiàn)在 obj.newparam = 1,因為本身obj.newparam的屬性不是Getter/Setter,所以在賦值后不會去發(fā)布消息給訂閱者。這就是所謂的盲區(qū)。而且無論以后obj.newparam如何變化都不會發(fā)布消息(實際就是vue根本不知道這個屬性發(fā)生了變化)。

但是有一個有趣的現(xiàn)象:就是雖然obj.newparam不會發(fā)布消息,但是如果別的發(fā)布者觸發(fā)的時候,頁面局部渲染時如果包括obj.newparam的值,渲染效果也是會顯示obj.newparam的最新值。這是由于頁面更新時是直接讀取的obj.newparam的值。

數(shù)組Array的特殊性

可能又有同學(xué)會發(fā)現(xiàn),Array對象是沒有辦法通過上述方法實現(xiàn)數(shù)據(jù)劫持的。

那么數(shù)組是如何實現(xiàn)的:vue中實現(xiàn)的方法實際是對數(shù)組的屬性重寫,重寫過后的方法不僅能實現(xiàn)原有的功能,還能發(fā)布消息給訂閱者。

  // Vue.js v2.5.13 #818-851
  var arrayProto = Array.prototype;
    var arrayMethods = Object.create(arrayProto);[
        'push',
        'pop',
        'shift',
        'unshift',
        'splice',
        'sort',
        'reverse'
    ].forEach(function (method) {
        // cache original method
        var original = arrayProto[method];
        def(arrayMethods, method, function mutator () {
            var args = [], len = arguments.length;
            while ( len-- ) args[ len ] = arguments[ len ];

            var result = original.apply(this, args);
            var ob = this.__ob__;
            var inserted;
            switch (method) {
                case 'push':
                case 'unshift':
                    inserted = args;
                    break
                case 'splice':
                    inserted = args.slice(2);
                    break
            }
            if (inserted) { ob.observeArray(inserted); }
            // notify change
            ob.dep.notify();
            return result
        });
    });

當(dāng)然Array也有特殊現(xiàn)象:如果要更新 Array 某個索引對應(yīng)的值得時候,要用Vue.set方式實現(xiàn)

Vue.set

一直在說Vue.set是對數(shù)據(jù)進行攔截,那么Vue.set究竟是什么

閱讀源碼就能發(fā)現(xiàn),實際就是數(shù)據(jù)劫持處理,并發(fā)布一次消息

// #1025-1050
function set (target, key, val) {
        if (Array.isArray(target) && isValidArrayIndex(key)) {
            target.length = Math.max(target.length, key);
            target.splice(key, 1, val);
            return val
        }
        if (key in target && !(key in Object.prototype)) {
            target[key] = val;
            return val
        }
        var ob = (target).__ob__;
        if (target._isVue || (ob && ob.vmCount)) {
            "development" !== 'production' && warn(
                'Avoid adding reactive properties to a Vue instance or its root $data ' +
                'at runtime - declare it upfront in the data option.'
            );
            return val
        }
        if (!ob) {
            target[key] = val;
            return val
        }
        defineReactive(ob.value, key, val);
        ob.dep.notify();
        return val
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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