zoukankan      html  css  js  c++  java
  • vue源码分析(五)>>:data

    今天来看看data,从_init看起,看看初始化时候都走了哪些操作,最终实现了数据响应式的;

    源码还是从_init走起,_init方法就是初始化各个options的入口,再看下方法体吧:

    Vue.prototype._init = function (options) {
          console.log("--------1-----------");
          var vm = this;
          // a uid
          vm._uid = uid$3++;
    
          var startTag, endTag;
          /* istanbul ignore if */
          if (config.performance && mark) {
            startTag = "vue-perf-start:" + (vm._uid);
            endTag = "vue-perf-end:" + (vm._uid);
            mark(startTag);
          }
    
          // a flag to avoid this being observed
          // 一个避免被观察到的标志
          vm._isVue = true;
          // merge options
          // 合并opt信息
          if (options && options._isComponent) {
            // optimize internal component instantiation
            // since dynamic options merging is pretty slow, and none of the
            // internal component options needs special treatment.
            // 优化内部组件实例化,因为动态选项合并非常慢,而且没有一个内部组件选项需要特殊处理。
            initInternalComponent(vm, options);
          } else {
            vm.$options = mergeOptions(
              resolveConstructorOptions(vm.constructor),
              options || {},
              vm
            );
          }
          /* istanbul ignore else */
          {
            initProxy(vm);
          }
          // expose real self
          vm._self = vm;
          initLifecycle(vm);
          initEvents(vm);
          initRender(vm);// ---2
          callHook(vm, 'beforeCreate');
          initInjections(vm); // resolve injections before data/props
          initState(vm);// 初始化数据相关
          initProvide(vm); // resolve provide after data/props
          callHook(vm, 'created');
    
          /* istanbul ignore if */
          if (config.performance && mark) {
            vm._name = formatComponentName(vm, false);
            mark(endTag);
            measure(("vue " + (vm._name) + " init"), startTag, endTag);
          }
    
          if (vm.$options.el) {
            vm.$mount(vm.$options.el);// 挂载vue实例到dom上
          }
        };

    然后看initState方法,

      function initState (vm) {
        console.log('initState :>---> ', vm);
        vm._watchers = [];
        var opts = vm.$options;
        if (opts.props) { initProps(vm, opts.props); }// 初始化props
        if (opts.methods) { initMethods(vm, opts.methods); }// 初始化方法
        if (opts.data) {// 初始化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);
        }
      }

    看到,如果存在data属性,就去走initData方法,没有就给实例上加一个_data属性(是一个空对象),然后将这个data走observe方法,这个方法随后上;

    然后看initData()方法:

      function initData (vm) {
        var data = vm.$options.data;
        // 如果data是一个方法 就执行这个方法
        data = vm._data = typeof data === 'function'
          ? getData(data, vm)
          : data || {};
        if (!isPlainObject(data)) {
          data = {};
          warn(
            'data functions should return an object:
    ' +
            'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
            vm
          );
        }
        // proxy data on instance
        var keys = Object.keys(data);// 所有定义的data的第一层的key
        var props = vm.$options.props;// 所有定义的props
        var methods = vm.$options.methods; // 所有定义的methods
        var i = keys.length;
        while (i--) {
          var key = keys[i];
          {
            if (methods && hasOwn(methods, key)) {// key不能和方法名相同
              warn(
                ("Method "" + key + "" has already been defined as a data property."),
                vm
              );
            }
          }
          if (props && hasOwn(props, key)) {// key不可以和props定义的名称相同
            warn(
              "The data property "" + key + "" is already declared as a prop. " +
              "Use prop default value instead.",
              vm
            );
          } else if (!isReserved(key)) {// 看看是不是以$_开头 如果是以他们开头的 就给你抛异常
            proxy(vm, "_data", key);// 设置代理
          }
        }
        // observe data
        observe(data, true /* asRootData */); // 走observe方法
      }

    这里对data的属性做合法性判断,然后先后走了proxy和observe方法,proxy是给属性设置代理,就是让data的属性可以用this.xx直接访问,看下咋实现的:

      /**
       * 让data和props里边的数据可以直接this.key访问
       * @param {vm} target 对象
       * @param {_data,_props} sourceKey 参数集合
       * @param {String} key 参数key
       */
      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);
      }

    接下来看看observe方法:

      /**
       * Attempt to create an observer instance for a value,尝试为一个值创建一个观察者实例,
       * returns the new observer if successfully observed,如果观测成功,返回新的观察者,
       * or the existing observer if the value already has one.或现有观察者(如果值已经有一个)。
       */
      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 (
          shouldObserve &&
          !isServerRendering() &&
          (Array.isArray(value) || isPlainObject(value)) &&
          Object.isExtensible(value) &&
          !value._isVue
        ) {
          ob = new Observer(value);
        }
        if (asRootData && ob) {
          ob.vmCount++;
        }
        return ob
      }

    看着这么多判断,暂时只用关心创建Observer咋实现就行,看看这个类是干啥的:

     /**
       * Observer class that is attached to each observed
       * object. Once attached, the observer converts the target
       * object's property keys into getter/setters that
       * collect dependencies and dispatch updates.
       * 附加到每个被观察对象的观察者类
      *对象。一旦附加,观察者转换目标
      对象的属性键转换为getter/setter
      *收集依赖和调度更新。
       */
      var Observer = function Observer (value) {
        this.value = value;
        this.dep = new Dep();
        this.vmCount = 0;
        // 相当于 value.__ob__ = this
        def(value, '__ob__', this);
        if (Array.isArray(value)) {// 是数组
          if (hasProto) {
            protoAugment(value, arrayMethods);
          } else {
            copyAugment(value, arrayMethods, arrayKeys);
          }
          this.observeArray(value);
        } else {// 是对象
          this.walk(value);
        }
      };
    
      /**
       * Walk through all properties and convert them into
       * getter/setters. This method should only be called when
       * value type is Object.
       * 遍历所有属性并将它们转换为getter/setter。此方法仅在值类型为Object时调用。
       */
      Observer.prototype.walk = function walk (obj) {
        var keys = Object.keys(obj);
        for (var i = 0; i < keys.length; i++) {
          defineReactive$$1(obj, keys[i]);
        }
      };

    咱初始化进来 data类型肯定是object,所以先看walk方法,这里遍历data对象,走这个defineReactive$$1方法,这个方法:

      /**
       * Define a reactive property on an Object.
       * 把对象的属性加上geter和setter变成响应式的
       */
      function defineReactive$$1 (
        obj,
        key,
        val,
        customSetter,
        shallow
      ) {
        var dep = new Dep();// 收集依赖的 东西比较多, 随后专门研究
        debugger
        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;
        if ((!getter || setter) && arguments.length === 2) { // 参数是两个 就是刚调用的时候传递了两个参数
          val = obj[key];
        }
    
        // 这里因为没有传shallow 所以会走observe,
        // 发现没有,又回去了,这就叫递归
        // 递归就会有跳出的判断 这里跳出递归的判断就是传进去的val不是对象
        // 也就是说 他会递归给每一个属性defineProperty
        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 (customSetter) {
              customSetter();
            }
            // #7981: for accessor properties without setter
            if (getter && !setter) { return }
            if (setter) {
              setter.call(obj, newVal);
            } else {
              val = newVal;
            }
            childOb = !shallow && observe(newVal);// 这里新值 又会走一遍
            dep.notify();
          }
        });
      }

    这个方法就是将data的所有属性加上set和get方法,让他们变成响应式的;

    data大概就是这个逻辑,给每一个属性变成响应式的,收集依赖,dep和watcher之后再细讲;怎么去驱动视图的也等这俩类研究完一起说;

    接下来看看遇到数组怎么操作:

      var Observer = function Observer (value) {
        this.value = value;
        this.dep = new Dep();
        this.vmCount = 0;
        // 相当于 value.__ob__ = this
        def(value, '__ob__', this);
        if (Array.isArray(value)) {// 是数组
          if (hasProto) {
            protoAugment(value, arrayMethods);
          } else {
            copyAugment(value, arrayMethods, arrayKeys);
          }
          this.observeArray(value);
        } else {// 是对象
          this.walk(value);
        }
      };

    首先看看arrayMethods是啥子:

      var arrayProto = Array.prototype;
      var arrayMethods = Object.create(arrayProto);

    就是数组的方法,然后看看这两个方法:

    /**
     * Augment a target Object or Array by intercepting
     * the prototype chain using __proto__
     */
    function protoAugment (target, src: Object) {
      /* eslint-disable no-proto */
      target.__proto__ = src
      /* eslint-enable no-proto */
    }
    
    /**
     * Augment a target Object or Array by defining
     * hidden properties.
     */
    /* istanbul ignore next */
    function copyAugment (target: Object, src: Object, keys: Array<string>) {
      for (let i = 0, l = keys.length; i < l; i++) {
        const key = keys[i]
        def(target, key, src[key])
      }
    }

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

    数组实现响应式代码如下:

      /*
       * not type checking this file because flow doesn't play well with
       * dynamically accessing methods on Array prototype
       */
    
      var arrayProto = Array.prototype;
      var arrayMethods = Object.create(arrayProto);
    
      var methodsToPatch = [
        'push',
        'pop',
        'shift',
        'unshift',
        'splice',
        'sort',
        'reverse'
      ];
    
      /**
       * Intercept mutating methods and emit events
       */
      methodsToPatch.forEach(function (method) {
        debugger
        // 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
        });
      });
    
      /*  */
    
      var arrayKeys = Object.getOwnPropertyNames(arrayMethods);
    可以看到,arrayMethods 首先继承了 Array,然后对数组中所有能改变数组自身的方法,如 push、pop 等这些方法进行重写。重写后的方法会先执行它们本身原有的逻辑,并对能增加数组长度的 3 个方法 push、unshift、splice 方法做了判断,获取到插入的值,然后把新添加的值变成一个响应式对象,并且再调用 ob.dep.notify() 手动触发依赖通知,这就很好地解释了用 vm.items.splice(newLength) 方法可以检测到变化

    总结:

    1.data的属性名不能以_或$开头;

    2.data的属性名不能和方法名或props名一样,不然就冲突了个屁了;

    3.数组操作时候注意,响应式操作不一样;用数组方法操作数组会触发,直接用=号赋值的还有改变长度的,都触发不了

    补充一下:对象和数组某些操作没有实现响应式,但vue提供了Vue.set(target,key,value)即this.$set(target,key,value)的方法,这个设置的值是响应式的,以下是set的源码:

    /**
       * Set a property on an object. Adds the new property and
       * triggers change notification if the property doesn't
       * already exist.
       * 在对象上设置属性。添加新属性,并在属性不存在时触发更改通知。
       */
      function set (target, key, val) {
        debugger
        if (isUndef(target) || isPrimitive(target)
        ) {
          warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));
        }
        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)) {
          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) {// 如果属性有ob,就是已经实现数据劫持了 也不往下走了
          target[key] = val;
          return val
        }
        defineReactive$$1(ob.value, key, val);// 添加依赖
        ob.dep.notify();// 收集依赖
        return val
      }

    这个方法返回的是设置的值。

    over

    里边 Dep Watcher 有空继续搞

  • 相关阅读:
    深入了解C语言
    $_SERVER变量结构
    整理了一份招PHP高级工程师的面试题
    深入探究VC —— 编译器cl.exe(2)【转】http://blog.csdn.net/wangningyu/article/details/4837419
    vs2010 工程设置,调试中的目录问题【转】http://www.cnblogs.com/mlj318/archive/2011/10/09/2203510.html
    深入探究VC —— 链接器link.exe(4)【转】http://blog.csdn.net/wangningyu/article/details/4849452
    移植ffmpeg到VC环境心得 【转】
    深入了解VC++编译器【转】
    VC2010中"Include Directories" 和 "Additional Include Directories"的区别
    深入探究VC —— 资源编译器rc.exe(3)【转】http://blog.csdn.net/wangningyu/article/details/4844687
  • 原文地址:https://www.cnblogs.com/rainbowLover/p/13615520.html
Copyright © 2011-2022 走看看