成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

vue - 響應(yīng)式原理梳理(一)

weknow619 / 1827人閱讀

摘要:問題為什么修改即可觸發(fā)更新和的關(guān)聯(lián)關(guān)系官方介紹的官網(wǎng)文檔,對響應(yīng)式屬性的原理有一個(gè)介紹。因此本文在源碼層面,對響應(yīng)式原理進(jìn)行梳理,對關(guān)鍵步驟進(jìn)行解析。

描述

?我們通過一個(gè)簡單的 Vue應(yīng)用 來演示 Vue的響應(yīng)式屬性

html:
    
{{message}}
js: let vm = new Vue({ el: "#app", data: { message: "123" } })

?在應(yīng)用中,message 屬性即為 響應(yīng)式屬性。

?我們通過 vm.message, vm.$data.message, 可訪問 響應(yīng)式屬性 message。

?當(dāng)我們通過修改 vm.message(vm.message = "456"), 修改后的數(shù)據(jù)會 更新到UI界面中。

問題

為什么修改 vm.message, 即可觸發(fā) UI更新

vm.messagedata.message 的關(guān)聯(lián)關(guān)系;

官方介紹

? vue的官網(wǎng)文檔,對響應(yīng)式屬性的原理有一個(gè)介紹。

把一個(gè)普通的 JavaScript 對象傳給 Vue 實(shí)例的 data 選項(xiàng),Vue 將遍歷此對象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉(zhuǎn)為 getter/setter。    

每個(gè)組件實(shí)例都有相應(yīng)的 watcher 實(shí)例對象,它會在組件渲染的過程中把屬性記錄為依賴,之后當(dāng)依賴項(xiàng)的 setter 被調(diào)用時(shí),會通知 watcher 重新計(jì)算,從而致使它關(guān)聯(lián)的組件得以更新。

?官方文檔

? 以上介紹,只是對響應(yīng)式原理進(jìn)行了簡單描述,并沒有深入細(xì)節(jié)。因此本文在源碼層面,對響應(yīng)式原理進(jìn)行梳理,對關(guān)鍵步驟進(jìn)行解析。

? 響應(yīng)式原理涉及到的關(guān)鍵步驟如下:

構(gòu)建vue實(shí)例;

vue實(shí)例data屬性初始化,構(gòu)建響應(yīng)式屬性;

將vue實(shí)例對應(yīng)的template編譯為render函數(shù)

構(gòu)建vue實(shí)例的watcher對象;

執(zhí)行render函數(shù),構(gòu)建VNode節(jié)點(diǎn)樹,同時(shí)建立響應(yīng)式屬性和watcher對象的依賴關(guān)系;

將VNode節(jié)點(diǎn)渲染為dom節(jié)點(diǎn)樹;

修改響應(yīng)式屬性,觸發(fā)watcher的更新,重新執(zhí)行render函數(shù),生成新的VNode節(jié)點(diǎn)樹;

對比新舊Vnode,重新渲染dom節(jié)點(diǎn)樹;

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

?Vue.js 給我們提供了一個(gè) 全局構(gòu)造函數(shù) Vue

?通過 new Vue(options) 生成一個(gè) vue實(shí)例,從而可以構(gòu)建一個(gè) Vue應(yīng)用

?其中,options 為構(gòu)造vue實(shí)例的配置項(xiàng),即為 { data, methods, computed, filter ... }

    /*
      options:
      {
        data: {...},
        methods: {...},
        computed: {...},
        watch: {...}
        ...
      }
    */
    function Vue (options) {
      if (process.env.NODE_ENV !== "production" &&
        !(this instanceof Vue)
      ) {
        warn("Vue is a constructor and should be called with the `new` keyword")
      }
      
      // 根據(jù)options, 初始化vue實(shí)例
      this._init(options)
    }
    
    export default Vue;

? vue實(shí)例 構(gòu)造完畢之后,執(zhí)行實(shí)例私有方法 _init(), 開始初始化。

? 在一個(gè) vue應(yīng)用 中,存在兩種類型的 vue實(shí)例根vue實(shí)例組件vue實(shí)例。

? 根vue實(shí)例,由構(gòu)造函數(shù) Vue 生成。

? 組件vue實(shí)例,由組件構(gòu)造函數(shù) VueComponent 生成,組件構(gòu)造函數(shù) 繼承 自構(gòu)造函數(shù) Vue。

    // 全局方法extend, 會返回一個(gè)組件構(gòu)造函數(shù)。
    Vue.extend = function(options) {
        ...
        
        // 組件構(gòu)造函數(shù),用于創(chuàng)建組件
        var Sub = function VueComponent(options) {
            this._init(options);
        };
        // 子類的prototype繼承自Vue的prototype
        // 相當(dāng)于Sub實(shí)例可以使用Vue實(shí)例的方法
        Sub.prototype = Object.create(Vue.prototype);
        
        ...
        
        return Sub;
    }

? 通過一個(gè) 根vue實(shí)例 和多個(gè) 組件vue實(shí)例,構(gòu)成了整個(gè) Vue應(yīng)用。

Vue.prototype._init

? 在_init方法中,vue實(shí)例會執(zhí)行一系列初始化操作。

? 在初始化過程中, 我們通過全局方法 initState 來初始化vue實(shí)例的 data、propsmethods、computed、watch 屬性。

     Vue.prototype._init = function(options) {
        var vm = this;
        
        ...  // 其他初始化過程, 包括建立子vue實(shí)例和父vue實(shí)例的對應(yīng)關(guān)系、給vue實(shí)例添加自定義事件、執(zhí)行beforeCreated回調(diào)函數(shù)等
        
        // 初始化props屬性、data屬性、methods屬性、computed屬性、watch屬性
        initState(vm);
        
        ... // 其他初始化過程,比如執(zhí)行created回調(diào)函數(shù)
        
        // vue實(shí)例初始化完成以后,掛載vue實(shí)例,將模板渲染成html
        if(vm.$options.el) {
                vm.$mount(vm.$options.el);
        }
    };

    function initState (vm: Component) {
      vm._watchers = [];
      // new Vue(options) 中的 options
      const opts = vm.$options; 
      
      // 將props配置項(xiàng)中屬性轉(zhuǎn)化為vue實(shí)例的響應(yīng)式屬性
      if (opts.props) initProps(vm, opts.props); 
      
      // 將 methods配置項(xiàng)中的方法添加到 vue實(shí)例對象中
      if (opts.methods) initMethods(vm, opts.methods);
      
      // 將data配置項(xiàng)中的屬性轉(zhuǎn)化為vue實(shí)例的響應(yīng)式屬性
      if (opts.data) {
        initData(vm)
      } else {
        observe(vm._data = {}, true /* asRootData */)
      }
      ...
    }

? 其中,initData 方法會將 data配置項(xiàng) 中的屬性全部轉(zhuǎn)化為 vue實(shí)例響應(yīng)式屬性。

initData

initData 方法的主要過程:

根據(jù)data配置項(xiàng),創(chuàng)建vue實(shí)例的私有屬性: _data。

通過 observe 方法,將 _data 對象中的屬性轉(zhuǎn)化為 響應(yīng)式屬性。

通過全局方法proxy, 建立 vue實(shí)例_data 的關(guān)聯(lián)關(guān)系。

    function initData(vm) {
        // 獲取data配置項(xiàng)對象
        var data = vm.$options.data;
        // 組件實(shí)例的data配置項(xiàng)是一個(gè)函數(shù)
        data = vm._data = typeof data === "function"? getData(data, vm): data || {};
        
        // 獲取data配置項(xiàng)的屬性值
        var keys = Object.keys(data);
        // 獲取props配置項(xiàng)的屬性值
        var props = vm.$options.props;
        // 獲取methods配置項(xiàng)的屬性值;
        var methods = vm.$options.methods;
        var i = keys.length;
        
        while(i--) {
            var key = keys[i];
            {
                // methods配置項(xiàng)和data配置項(xiàng)中的屬性不能同名
                if(methods && hasOwn(methods, key)) {
                    warn(
                        ("method "" + key + "" has already been defined as a data property."),
                        vm
                    );
                }
            }
            // props配置項(xiàng)和data配置項(xiàng)中的屬性不能同名
            if(props && hasOwn(props, key)) {
                "development" !== "production" && warn(
                    "The data property "" + key + "" is already declared as a prop. " +
                    "Use prop default value instead.",
                    vm
                );
            } else if(!isReserved(key)) { // 如果屬性不是$,_ 開頭(vue的保留屬性)
                // 建立 vue實(shí)例 和 _data 的關(guān)聯(lián)關(guān)系性
                proxy(vm, "_data", key);
            }
        }
        // 觀察data對象, 將對象屬性全部轉(zhuǎn)化為響應(yīng)式屬性
        observe(data, true /* asRootData */);
    }
observe

? 全局方法 observe 的作用是用來觀察一個(gè)對象,將_data對象的屬性全部轉(zhuǎn)化為 響應(yīng)式屬性。

    // observe(_data, true)
    function observe(value, asRootData) {
        if(!isObject(value)) {
            return
        }
        var ob;
       ...
       // 
       ob = new Observer(value);
       
       ...
       
       return ob;
    }


    var Observer = function Observer(value) {
        ...
        
        if(Array.isArray(value)) {
            // 如果value是數(shù)組,對數(shù)組每一個(gè)元素執(zhí)行observe方法
            this.observeArray(value);
        } else {
            // 如果value是對象, 遍歷對象的每一個(gè)屬性, 將屬性轉(zhuǎn)化為響應(yīng)式屬性
            this.walk(value);
        }
    };
    
    // 如果要觀察的對象時(shí)數(shù)組, 遍歷數(shù)組,然后調(diào)用observe方法將對象的屬性轉(zhuǎn)化為響應(yīng)式屬性
    Observer.prototype.observeArray = function observeArray(items) {
        for(var i = 0, l = items.length; i < l; i++) {
            observe(items[i]);
        }
    };
    
    
     // 遍歷obj的屬性,將obj對象的屬性轉(zhuǎn)化為響應(yīng)式屬性
    Observer.prototype.walk = function walk(obj) {
        var keys = Object.keys(obj);
        for(var i = 0; i < keys.length; i++) {
           // 給obj的每一個(gè)屬性都賦予getter/setter方法。
           // 這樣一旦屬性被訪問或者更新,這樣我們就可以追蹤到這些變化
            defineReactive(obj, keys[i], obj[keys[i]]);
        }
    };
defineReactive

? 通過 defineProperty 方法, 提供屬性的 getter/setter 方法。

? 讀取 屬性時(shí),觸發(fā) getter,將與響應(yīng)式屬性相關(guān)的vue實(shí)例保存起來。

? 修改 屬性時(shí),觸發(fā) setter,更新與響應(yīng)式屬性相關(guān)的vue實(shí)例。

    function defineReactive(obj, key, val, customSetter, shallow) {
        // 每一個(gè)響應(yīng)式屬性都會有一個(gè) Dep對象實(shí)例, 該對象實(shí)例會存儲訂閱它的Watcher對象實(shí)例
        var dep = new Dep();
        
        // 獲取對象屬性key的描述對象
        var property = Object.getOwnPropertyDescriptor(obj, key);
        
        // 如果屬性是不可配置的,則直接返回
        if(property && property.configurable === false) {
            return
        }

        // 屬性原來的getter/setter
        var getter = property && property.get;
        var setter = property && property.set;
        
        // 如果屬性值是一個(gè)對象,遞歸觀察屬性值,
        var childOb = !shallow && observe(val);
        
        // 重新定義對象obj的屬性key
        Object.defineProperty(obj, key, {
            enumerable : true,
            configurable : true,
            get : function reactiveGetter() {
                // 當(dāng)obj的某個(gè)屬性被訪問的時(shí)候,就會調(diào)用getter方法。
                var value = getter ? getter.call(obj) : val;
                
                
                // 當(dāng)Dep.target不為空時(shí),調(diào)用dep.depend 和 childOb.dep.depend方法做依賴收集
                if(Dep.target) {
                
                    // 通過dep對象, 收集依賴關(guān)系
                    dep.depend();
                    if(childOb) {
                        childOb.dep.depend();
                    }
                    // 如果訪問的是一個(gè)數(shù)組, 則會遍歷這個(gè)數(shù)組, 收集數(shù)組元素的依賴
                    if(Array.isArray(value)) {
                        dependArray(value);
                    }
                }
                return value
            },
            set : function reactiveSetter(newVal) {
                // 當(dāng)改變obj的屬性是,就會調(diào)用setter方法。這是就會調(diào)用dep.notify方法進(jìn)行通知
                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);
                // 當(dāng)響應(yīng)式屬性發(fā)生修改時(shí),通過dep對象通知依賴的vue實(shí)例進(jìn)行更新
                dep.notify();
            }
        });
    }

? 響應(yīng)式屬性, 通過一個(gè) dep 對象, 收集依賴響應(yīng)式屬性的vue實(shí)例,在屬性改變時(shí) 通知vue實(shí)例更新。

? 一個(gè) 響應(yīng)式屬性, 對應(yīng)一個(gè) dep 對象。

Dep

? 在觀察者設(shè)計(jì)模式中,有兩種角色:SubjectObserver。

? Subject 會維護(hù)一個(gè) Observer的依賴列表。當(dāng) Subject 發(fā)生變化時(shí),會通知 Observer 更新。

? 在vue中,響應(yīng)式屬性作為Subject, vue實(shí)例作為Observer, 響應(yīng)式屬性的更新會通知vue實(shí)例更新。

? 響應(yīng)式屬性通過 dep 對象來收集 依賴關(guān)系 。一個(gè)響應(yīng)式屬性,對應(yīng)一個(gè)dep對象。

    var Dep = function Dep() {
        // dep對象的id
        this.id = uid++;
        // 數(shù)組,用來存儲依賴響應(yīng)式屬性的Observer
        this.subs = [];
    };
    
    // 將Observer添加到dep對象的依賴列表中
    Dep.prototype.addSub = function addSub(sub) {
        // Dep對象實(shí)例添加訂閱它的Watcher
        this.subs.push(sub);
    };
    
    // 將Observer從dep對象的依賴列表中刪除
    Dep.prototype.removeSub = function removeSub(sub) {
        // Dep對象實(shí)例移除訂閱它的Watcher
        remove(this.subs, sub);
    };
    
    // 收集依賴關(guān)系
    Dep.prototype.depend = function depend() {
        // 把當(dāng)前Dep對象實(shí)例添加到當(dāng)前正在計(jì)算的Watcher的依賴中
        if(Dep.target) {
            Dep.target.addDep(this);
        }
    };
    
    // 通知Observer更新
    Dep.prototype.notify = function notify() {
        // stabilize the subscriber list first
        var subs = this.subs.slice();
        // 遍歷所有的訂閱Watcher,然后調(diào)用他們的update方法
        for(var i = 0, l = subs.length; i < l; i++) {
            subs[i].update();
        }
    };
proxy

? 通過 defineProperty 方法, 給vue實(shí)例對象添加屬性,提供屬性的 getter/setter 方法。

? 讀取vue實(shí)例的屬性( data配置項(xiàng)中的同名屬性 ), 觸發(fā) getter,讀取 _data 的同名屬性。

? 修改vue實(shí)例的屬性( data配置項(xiàng)中的同名屬性 ), 觸發(fā) setter,修改 _data 的同名屬性。

    // proxy(vm, _data, "message")
    function proxy(target, sourceKey, key) {
        sharedPropertyDefinition.get = function proxyGetter() {
            return this[sourceKey][key]
        };
        sharedPropertyDefinition.set = function proxySetter(val) {
            this[sourceKey][key] = val;
        };
        Object.defineProperty(target, key, sharedPropertyDefinition);
    }

? 通過 proxy 方法,vue實(shí)例 可代理私有屬性 _data, 即通過 vue實(shí)例 可以訪問/修改 響應(yīng)式屬性。

總結(jié)

? 結(jié)合源碼理解, 響應(yīng)式屬性 的原理為:

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/101374.html

相關(guān)文章

  • vue - 響應(yīng)原理梳理(二)

    摘要:原型方法通過原型方法方法來掛載實(shí)例。當(dāng)響應(yīng)式屬性發(fā)生變化時(shí),會通知依賴列表中的對象進(jìn)行更新。此時(shí),對象執(zhí)行方法,重新渲染節(jié)點(diǎn)。在執(zhí)行過程中,如果需要讀取響應(yīng)式屬性,則會觸發(fā)響應(yīng)式屬性的??偨Y(jié)響應(yīng)式屬性的原理 vue實(shí)例 初始化 完成以后,接下來就要進(jìn)行 掛載。 vue實(shí)例掛載,即為將vue實(shí)例對應(yīng)的 template模板,渲染成 Dom節(jié)點(diǎn)。 原型方法 - $mount ? 通過原...

    mochixuan 評論0 收藏0
  • JS核心知識點(diǎn)梳理——原型、繼承(下)

    摘要:引言上篇文章介紹原型,這篇文章接著講繼承,嘔心瀝血之作,大哥們點(diǎn)個(gè)贊呀明確一點(diǎn)并不是真正的面向?qū)ο笳Z言,沒有真正的類,所以我們也沒有類繼承實(shí)現(xiàn)繼承有且僅有兩種方式,和原型鏈在介紹繼承前我們先介紹下其他概念函數(shù)的三種角色一個(gè)函數(shù),有三種角色。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 上篇文章介紹原型,...

    joyqi 評論0 收藏0
  • vue總結(jié)系列--數(shù)據(jù)驅(qū)動和響應(yīng)

    摘要:由于是需要兼容的后臺系統(tǒng),該項(xiàng)目并不能使用到等技術(shù),因此我在上的經(jīng)驗(yàn)大都是使用原生的編寫的,可以看見一個(gè)組件分為兩部分視圖部分,和數(shù)據(jù)部分。 在公司里幫項(xiàng)目組里開發(fā)后臺系統(tǒng)的前端項(xiàng)目也有一段時(shí)間了。 vue這種數(shù)據(jù)驅(qū)動,組件化的框架和react很像,從一開始的快速上手基本的開發(fā),到后來開始自定義組件,對element UI的組件二次封裝以滿足項(xiàng)目需求,期間也是踩了不少坑。由于將來很長一...

    AbnerMing 評論0 收藏0
  • Vue 數(shù)據(jù)響應(yīng)原理

    摘要:接下來,我們就一起深入了解的數(shù)據(jù)響應(yīng)式原理,搞清楚響應(yīng)式的實(shí)現(xiàn)機(jī)制?;卣{(diào)函數(shù)只是打印出新的得到的新的值,由執(zhí)行后生成。及異步更新相信讀過前文,你應(yīng)該對響應(yīng)式原理有基本的認(rèn)識。 前言 Vue.js 的核心包括一套響應(yīng)式系統(tǒng)。 響應(yīng)式,是指當(dāng)數(shù)據(jù)改變后,Vue 會通知到使用該數(shù)據(jù)的代碼。例如,視圖渲染中使用了數(shù)據(jù),數(shù)據(jù)改變后,視圖也會自動更新。 舉個(gè)簡單的例子,對于模板: {{ name ...

    Mike617 評論0 收藏0
  • 淺談Vue中計(jì)算屬性computed的實(shí)現(xiàn)原理

    摘要:雖然計(jì)算屬性在大多數(shù)情況下更合適,但有時(shí)也需要一個(gè)自定義的偵聽器。當(dāng)某個(gè)屬性發(fā)生變化,觸發(fā)攔截函數(shù),然后調(diào)用自身消息訂閱器的方法,遍歷當(dāng)前中保存著所有訂閱者的數(shù)組,并逐個(gè)調(diào)用的方法,完成響應(yīng)更新。 雖然目前的技術(shù)棧已由Vue轉(zhuǎn)到了React,但從之前使用Vue開發(fā)的多個(gè)項(xiàng)目實(shí)際經(jīng)歷來看還是非常愉悅的,Vue文檔清晰規(guī)范,api設(shè)計(jì)簡潔高效,對前端開發(fā)人員友好,上手快,甚至個(gè)人認(rèn)為在很多...

    laznrbfe 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<