zoukankan      html  css  js  c++  java
  • Vue源码学习(一)———数据双向绑定 Observer

    从最简单的案例,来学习Vue.js源码。

    <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?

    (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对象。

    (二)Vue双向绑定的实现原理

       Vue 采用数据劫持,通过Object.defineProperty的getter和setter,结合观察者模式来实现数据绑定。

       与此相关的对象有四个:

       Observer(数据监听器)对数据对象的所有属性进行监听,如果 Data Change  ===通知==> Watcher;

       Watcher(订阅者): 连接 Observer、Compile 的桥梁。

       Dep(消息订阅器):收集 Watcher,数据变动触发 notify 函数,再调用 Watcher.update() 

            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__属性引用自身,自定义 getset 方法,
    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()  为什么?

  • 相关阅读:
    DTD和Schema的区别
    在使用Maven中出现的小错误
    struts2 中 paramsPrepareParamsStack 拦截器
    Hibernate的save()和persist()的区别
    Spring学习笔记
    Hello Spring
    Hibernate3 和Hibernate4 在配置文件上的区别
    今日学习-商品数据库查询
    Java中避免表单重复提交
    Java学习笔记
  • 原文地址:https://www.cnblogs.com/xianrongbin/p/2376288.html
Copyright © 2011-2022 走看看