Vue源码一行行分析(二)双向绑定原理

一、前言

今天来一步步分析Vue双向绑定

二、源码分析

接我自己上篇Vue源码一行行分析(一)整体分析,没看的先看我的第一篇吧,可以得知,在initState方法中进行数据监控绑定。
在这里插入图片描述

  function initState (vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if (opts.props) { initProps(vm, opts.props); } // 先检查options里有没有props 初始化props相关
    //先检查options里有没有methods 初始化methods相关 因为我们现在讲的是双向绑定,这里的先不讲,后续补充
    if (opts.methods) { initMethods(vm, opts.methods); }
    if (opts.data) { // options有没有data,有则初始化data
      initData(vm); 
    } else {// options没有data 则把一个空对象当作根节点数据
      observe(vm._data = {}, true /* asRootData */);
    }
    if (opts.computed) { initComputed(vm, opts.computed); } // 初始化computed
    if (opts.watch && opts.watch !== nativeWatch) { // 初始化监听
      initWatch(vm, opts.watch);
    }
  }
 function initData (vm) { // 初始化data
    var data = vm.$options.data; // 获取实例的data 
     // 即data = data() {
        //     return {
        //         msg: 'hello vue'
        //     }
        // },
    data = vm._data = typeof data === 'function' //给_data赋值 判断data是不是函数 是函数执行 getData,不是则为data
      ? getData(data, vm)
      : data || {}; // 执行过getData方法之后 data = vm._data = {msg: 'hello vue'}
    if (!isPlainObject(data)) { // 是否是纯粹的对象 isPlainObject函数内容为:return _toString.call(obj) === '[object Object]'
      data = {}; // 不是对象 警告报错 所以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对象的键 这里 keys = ['msg']
    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)) { // 先不管methods
          warn(
            ("Method \"" + key + "\" has already been defined as a data property."),
            vm
          );
        }
      }
      if (props && hasOwn(props, 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); // 不是以$或者_开头 ,将键值放入到_data 即 vm._data.msg = 'hello vue'
      }
    }
    // observe data
    observe(data, true /* asRootData */);
  }

因为data中的值放置在_data中,所以你要获得msg的值你只能通过this._data.msg获得,那我岂不是获取每一个值都要this._data.xxx,对于程序员来说这是难以接受的,我们只想要什么取什么 this.xxx才是我想要的,怎么简单怎么来,所以才有proxy方法,使用Object.defineProperty
在这里插入图片描述

 var sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
  };

  function proxy (target, sourceKey, key) { 
  // target代表vm,sourceKey代表_data,key代表你要取的值
  // 意思就是你如果要使用this.msg获得msg,其实就是指向this._data.msg,得到同样的效果,
  // 所以该方法名叫代理,使用this.msg代理this._data.msg,简单方便
    sharedPropertyDefinition.get = function proxyGetter () {
      return this[sourceKey][key]
    };
    sharedPropertyDefinition.set = function proxySetter (val) {
      this[sourceKey][key] = val;
    };
    Object.defineProperty(target, key, sharedPropertyDefinition);
  }

接下来到最后的observe方法了

  function observe (value, asRootData) {// 将data传入,第二参数是否作为根节点数据
    if (!isObject(value) || value instanceof VNode) { 
    // 如果传入的data不是对象 或者不输入VNdode 虚拟节点 则返回 该判断用于递归结束判断,后面会讲到
      return
    }
    var ob;
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
      ob = value.__ob__;// value有__ob__且value.__ob__的原型在Observer的原型链上
    } else if (
      shouldObserve &&
      !isServerRendering() &&
      (Array.isArray(value) || isPlainObject(value)) &&
      Object.isExtensible(value) &&
      !value._isVue
    ) {
     // // shouldObserve定义为true isServerRendering是否服务器渲染以及是否是数组是否是对象且Object.isExtensible(是否可扩展性)
      ob = new Observer(value);
    }
    if (asRootData && ob) {
      ob.vmCount++;
    }
    return ob
  }

  var Observer = function Observer (value) {
    this.value = value; // 将data放入到Observer this上的value上
    this.dep = new Dep(); // // Dep 消息管理器 粗略讲一下 Observer劫持数据 Watcher订阅信息,所以需要一个Dep消息管理器中转,连接Observer和Watcher
    this.vmCount = 0;
    def(value, '__ob__', this); // 把this赋值给value对象的__ob__属性 即 value.__ob__ = {dep:new Dep(),value,vaCount = 0}
    if (Array.isArray(value)) { // 如果传入的data是数组
      if (hasProto) {
        protoAugment(value, arrayMethods);
      } else {
        copyAugment(value, arrayMethods, arrayKeys);
      }
      this.observeArray(value);
    } else { // 传入不是数组
      this.walk(value);
    }
  };
  Observer.prototype.walk = function walk (obj) {
    var keys = Object.keys(obj);  // 获得data的键 开始循环监听
    for (var i = 0; i < keys.length; i++) {
      defineReactive$$1(obj, keys[i]);
    }
  };
  function defineReactive$$1 (
    obj, // data
    key, // data中的某一个键
    val,
    customSetter,
    shallow
  ) {
    var dep = new Dep();

    var property = Object.getOwnPropertyDescriptor(obj, key); // 获得data中对象属性描述符
    //var o, d;
	//o = { get foo() { return 17; } };
    // d = Object.getOwnPropertyDescriptor(o, "foo");
    // d {
    //   configurable: true, // 可配置 为true可以被改变或者属性可被删除
    //   enumerable: true, // 可枚举(为true意思就是可以遍历)
    //   get: /*the getter function*/,
    //   set: undefined
    // }
    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];
    }

    var childOb = !shallow && observe(val); // 递归,这里也上面的observe首先判断对应,如果val还是对象
    // observe(val) 递归 为什么要递归解释一下 我们这里的data为{msg: "hello vue"},但是如果为{msg: {test:'xxx'}}
    // 不止要劫持msg 还要劫持test,这就是我们为什么设置data里面,对象嵌套对象,里面的属性值都能双向绑定的原因。
    // 劫持对象中的每个属性,属性还是对象,递归继续劫持
    // 下方是Vue的双向绑定核心 使用Object.defineProperty数据劫持
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
      // 当取值的时候触发 例如要获得msg 使用this.msg就会进入此方法
        var value = getter ? getter.call(obj) : val; //如果该属性自身带有getter使用自身,否则取得原值
        if (Dep.target) {// 这里的Dep.target为Watcher实例,如果存在Dep.target,将Watcher加入Dep中
          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();  // 调用Dep.notify,Dep通知Watcher,发生改变
      }
    });
  }

我想你看到defineReactive$$1这个方法很迷糊把,这是啥玩意,看不懂这个Object.defineProperty到底在劫持的时候做了啥。不要烦躁,你要知道你要学习是一个框架的核心,一个框架的思想,如果你看到这里就走了,看不下去了,那你永远都不会进步的,答应我,留下来,你可能变秃,但是你会变强呀,啊哈哈哈哈哈,废话少说,我们来画一个流程图来帮助你来理解:
在这里插入图片描述
上面流程图是分为两部分

上半部分是总概,Observer劫持监听数据,如果属性值发生改变(执行了属性的set),由Dep消息管理中心通知Watcher发生改变,Wathcer会把自身加入到Dep中进行管理(执行了属性get)。

具体的情况是下半部分:开始调用Observer,使用Object.defineProperty对属性进行劫持,递归监听属性,在get,set里面埋好点,当执行完new Observer()之后,随后执行 new Watcher(),在执行new Watcher()时,会把 Dep.target = this;(this代表Watcher实例),然后手动触发get,所以就进入了Object.defineProperty里定义的get,get方法里判断Dep.target有值,调用dep.depend()方法,此方法实际就是subs.push(this),即把Watcher放入一个数组管理(Dep消息管理中心实际就是一个数组),在这里就实现了添加订阅者到Dep中,当属性发生改变时,即触发Object.defineProperty里定义的set,举个例子,this.msg = ‘HI’(由’hello vue’改成了’HI’),然后调用dep.notify()方法去更新,当然我们这是设置的是一个字符串,那我们设置成为一个对象呢?this.msg = {name: ‘123’},这个时候也要监听到name属性,所以在调用dep.notify()之前要先调用前面的劫持递归去监听数据。到此,Observer,Dep,Watcher三者已经讲的很清楚了把,我已经尽量把这些东西揉碎了掰开了让你弄懂,至于dep.notify()后续的就先不在本章讲了,后续会讲到。

至此,我们已经把Observer,Watcher,Dep讲的很清楚了,先执行Observer,埋好点,get时Dep.target有值即把Watcher放入Dep的数组中,set时通知Watcher值发生改变,点已经埋好,然后调用new Watcher,手动触发get,将Watcher实例放入Dep统一进行管理,至此一个环已经形成,这才是双向绑定的核心。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值