Vue源碼學習(一)———數據雙向綁定 Observer

從最簡單的案例,來學習Vue.js源碼。vue

<body>
        <div id='app'>
            <input type="text" v-model="message">---{{message}}
        </div>
    </body>
    <script src='./vue.js'></script>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                message: 'Hello Vue!'
            }
        });
</script>

(一)爲什麼能夠直接使用 Vue?node

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define(factory) :
            (global.Vue = factory());
}(this, (function () {
    'use strict';
   function Vue$3(options){
this._init(options);
}
   return Vue$3;
})));

   此寫法,即兼容了 cmd ,也兼容了ES6,也將Vue對象掛載到window對象。react

(二)Vue雙向綁定的實現原理es6

   Vue 採用數據劫持,經過Object.defineProperty的getter和setter,結合觀察者模式來實現數據綁定。數組

   與此相關的對象有四個:服務器

   Observer(數據監聽器)對數據對象的全部屬性進行監聽,若是 Data Change  ===通知==> Watcher;閉包

   Watcher(訂閱者): 鏈接 Observer、Compile 的橋樑。app

   Dep(消息訂閱器):收集 Watcher,數據變更觸發 notify 函數,再調用 Watcher.update() ide

        Compile:(Directive 指令解析器):對元素節點進行掃描和解析,替換、綁定相應回調函數。函數

   其實這裏我有一個疑惑: 爲何Dep 要收集 Watcher?

   看源碼的時候我重點關注上面 Observer、Watcher、Dep.

     進入Vue構造函數,實例化對象,進入 Vue.prototype._init() 函數。

  (1) Vue.prototype._init() 函數

 1 Vue.prototype._init = function (options) {
 2             var vm = this;
 3             // a uid
 4             vm._uid = uid$1++;
 5             vm._isVue = true; //表明一個Vue實例,不是組件,不用被監聽
 6             if (options && options._isComponent) {
 7                 initInternalComponent(vm, options);//注入的參數中是組件處理
 8             } else {
 9                 //實例內部初始化,返回 vm.constructor.options ,若是含有 super,內部也會調用 mergeOptions()
10                 //extend(Vue.options,XXX)以及 initGlobalAPI() 中擴展了options 全部實例基礎參數
11                 var tempOptions = resolveConstructorOptions(vm.constructor);
12                 vm.$options = mergeOptions(tempOptions, options || {}, vm);
13             }
14             //完成後,vm 擁有了 _renderProxy 對象屬性
15             initProxy(vm);
16             //爲何要保存自身引用呢?
17             vm._self = vm;
18             //一步一步往 vm 添加屬性,方法
19             initLifecycle(vm); //
20             initEvents(vm);//父子組件通訊工做
21             initRender(vm);
22             callHook(vm, 'beforeCreate');
23             initInjections(vm); // resolve injections before data/props
24             initState(vm);//完成 watch observe 等初始化
25             initProvide(vm); // resolve provide after data/props
26             callHook(vm, 'created');
27 
28             //Vue 傳入的參數中有el屬性,進行掛載,啓動
29             if (vm.$options.el) {
30                 vm.$mount(vm.$options.el);
31             }
32         };
33     }

       2-13行代碼,經過整合傳入的參數,賦值vm. $options屬性中,以便方便的取出。

    若是咱們使用了 Vuex或者VueRouter,也會將其方法屬性掛載在 $options屬性中。

  (2)initProxy()

    爲vm新增了一個  _renderProxy 代碼屬性。  

 1  initProxy = function initProxy(vm) {
 2             if (hasProxy) {
 3                 //是否支持 es6 的代理
 4                 var options = vm.$options;
 5                 var handlers = options.render && options.render._withStripped
 6                     ? getHandler
 7                     : hasHandler;
 8                 vm._renderProxy = new Proxy(vm, handlers);
 9             } else {
10                 vm._renderProxy = vm;
11             }
12         };
13         var hasHandler = {
14             has: function has(target, key) {
15                 var has = key in target;
16                 //判斷key是否跟內置全局變量衝突
17                 var isAllowed = allowedGlobals(key) || key.charAt(0) === '_';
18                 if (!has && !isAllowed) {
19                     warnNonPresent(target, key);
20                 }
21                 return has || !isAllowed
22             }
23         };
24 
25         var getHandler = {
26             get: function get(target, key) {
27                 if (typeof key === 'string' && !(key in target)) {
28                     warnNonPresent(target, key);
29                 }
30                 return target[key]
31             }
32         };

     vm. _renderProxy  會在 Vue.prototype._render() 中以下使用。

     vnode = render.call(vm._renderProxy, vm.$createElement);

  (三) Observer 的初始化

    (1) initData()函數

     initData完成了對model元素Data數據格式化、元素代理初始化、監聽初始化。

 1  function initData(vm) {
 2         var data = vm.$options.data;
 3         /*獲取自定義 data 數據,這裏爲何使用閉包,而不是直接獲取 options.data*/
 4         data = vm._data = typeof data === 'function' ?
 5             getData(data, vm) : data || {};
 6 
 7         var keys = Object.keys(data);
 8         var props = vm.$options.props;
 9         var methods = vm.$options.methods;
10         var i = keys.length;
11         while (i--) {
12             //循環爲每一個 data屬性加入代理
13             var key = keys[i];
14             if (props && hasOwn(props, key)) {
15             } else if (!isReserved(key)) {
16                 //若是key以 $ _ 開頭,不做處理,是Vue的關鍵字
17                 //複製建立新的屬性。直接掛在在vm 下,且自定義了 get/set 方法,
18                 //其真實的值在 vm._data 下
19                 proxy(vm, "_data", key);
20             }
21         }
22         //處理完成後,vm 以及 vm._data 都含有用戶定義的model數據
23         //監聽
24         observe(data, true /* asRootData */);
25     }

  (2)observe(data,true)  

    這個函數會返回一個Observer() 實例對象, 若是model還未添加監聽屬性,則添加。 

 1  function observe(value, asRootData) {
 2         if (!isObject(value)) {
 3             return
 4         }
 5         var ob;
 6         if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
 7             //有 __ob__ 屬性,即已經被監聽
 8             ob = value.__ob__;
 9         } else if (
10         //是否應該被監聽 是不是服務器渲染 必須爲數組或對象 是否可擴展 是否實例纔有的屬性
11         observerState.shouldConvert && !isServerRendering() &&
12         (Array.isArray(value) || isPlainObject(value)) &&
13         Object.isExtensible(value) && !value._isVue) {//生成一個觀察者
14             ob = new Observer(value);
15         }
16         //根數據計數 屬性 ++
17         if (asRootData && ob) {
18             ob.vmCount++;
19         }
20         //ob 包含了 dep,value,__ob__屬性引用自身,自定義 get\set 方法,
21         //計數vmCount,原型方法haiyou observeArray,監聽對象的walk方法
22         return ob
23     }

    observe()函數的關鍵點在於 ob=new Observer(value) 。

    (3)Observer 對象

    Observer初始化時,會將當前Observer自身引用掛載在 model 中,

    同時循環爲每一個model屬性重寫get/set 方法,這樣就實現了數據劫持。        

 1 /**
 2      * 建立Dep對象實例
 3      * 將自身this添加到value的ob屬性上
 4      */
 5     var Observer = function Observer(value) {
 6         this.value = value;
 7         this.dep = new Dep(); //Dep 構造函數: uid--id,subs[] --依賴收集
 8         this.vmCount = 0;
 9         def(value, '__ob__', this);//爲model屬性添加 __ob__屬性
10         if (Array.isArray(value)) {
11             //遞歸調用 Observer(value) 最後仍然走 walk()
12             var augment = hasProto ? protoAugment : copyAugment;
13             augment(value, arrayMethods, arrayKeys);//原型擴展
14             this.observeArray(value);
15         } else {
16             this.walk(value);
17         }
18     };
19 
20     //Walk 爲每一個屬性對象添加get/set
21     Observer.prototype.walk = function walk(obj) {
22         var keys = Object.keys(obj);
23         for (var i = 0; i < keys.length; i++) {
24             defineReactive$$1(obj, keys[i], obj[keys[i]]);
25         }
26     };

   (4)defineReactive$$1()

    這個函數主要是對Object某個屬性值設置了數據劫持,也就是經過重寫對象屬性中的 get/set 方法,

    一旦改變,就會馬上觸發相關函數。

 1   /**
 2      * 對象屬性被劫持 經過調用Object.defineProperty 給data的每一個屬性添加 getter setter方法,
 3      * 當data某個屬性被訪問時,調用getter方法,判斷當 Dep.target 不爲空時調用 dep.denpend 和 childObj.dep.denpend方法
 4      * 當改變data的屬性時,調用setter方法,這時調用 dep.notify方法進行通知
 5      *
 6      */
 7     function defineReactive$$1(obj, key, val, customSetter, shallow) {
 8         var dep = new Dep();//依賴管理
 9 
10         var property = Object.getOwnPropertyDescriptor(obj, key);//返回鍵描述信息
11         if (property && property.configurable === false) {
12             //不能夠修改直接返回
13             return
14         }
15 
16         var getter = property && property.get;
17         var setter = property && property.set;
18 
19         var childOb = !shallow && observe(val);
20         Object.defineProperty(obj, key, {
21             enumerable: true,
22             configurable: true,
23             get: function reactiveGetter() {
24                 var value = getter ? getter.call(obj) : val;
25                 if (Dep.target) {
26                     dep.depend();
27                     if (childOb) {
28                         childOb.dep.depend();
29                         if (Array.isArray(value)) {
30                             dependArray(value);
31                         }
32                     }
33                 }
34                 return value
35             },
36             set: function reactiveSetter(newVal) {
37                 var value = getter ? getter.call(obj) : val; //獲取當前值,是前一個值
38                 if (newVal === value || (newVal !== newVal && value !== value)) {
39                     //值沒有發生變化,再也不作任何處理
40                     return
41                 }
42                 /* eslint-enable no-self-compare */
43                 if ("development" !== 'production' && customSetter) {
44                     customSetter();
45                 }
46                 if (setter) {
47                     setter.call(obj, newVal);//調用默認setter方法或將新值賦給當前值
48                 } else {
49                     val = newVal;
50                 }
51                 childOb = !shallow && observe(newVal);
52                 dep.notify();//賦值後通知依賴變化
53             }
54         });
55     }

   可是我有一點疑惑的是:model值改變,會按照  hasHandler ==> proxySetter ==> reactiveSetter 這樣一種順序,

 直至最後 觸發 Dep.notify()  爲何?

相關文章
相關標籤/搜索