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:\n' +
        '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 有空继续搞

 

posted on 2020-09-04 18:34  rainbowLover  阅读(233)  评论(0编辑  收藏  举报