Vue双向绑定源码分析

一、前言

一直想研究一下vue的源码,但苦于没有耐力坚持下去,所以就一直拖着,今天就来研究一下吧,事情总是要做的,闲话少叙,我们都知道,vue双向绑定的核心是通过Object.defineProperty()来劫持数据,再结合设计模式发布/定义来设计的,那么什么叫双向绑定呢,双向绑定意味着视图发生改变,数据也发生改变,数据发生改变,视图也会发生改变,双向绑定的,如下图所示:
在这里插入图片描述

二、Object.defineProperty()

Object.defineProperty()是什么意思呢?,来个例子。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Object.defineProperty</title>
</head>

<body>
  <script>
    let obj = {
    }
    let book = ""

    Object.defineProperty(obj, "book", {
      get: function () {
        return book
      },
      set: function (newVal) {
        book = newVal + "啦啦"
      }
    })
    obj.book = 'Vue.js实战'
    console.log(obj.book)
    console.log(obj)
  </script>
</body>

</html>

在这里插入图片描述
从上面可以看到多了下面几个属性 get,set ,而我们去看一下vue里面也具备同样的get,set属性,不妨可以去试一下,这里不过多阐述。

三、Observe源码分析

// 引入vue文件 new一个Vue对象
  var vm = new Vue({
    el: '#app',
    // beforeCreate() {
    //   console.log(1)
    // },
    created() {
      console.log(this.msg)
    },
    data: {
      msg: 'hello vue'
    }
  })
 function observe(value, asRootData) {
 	// value是上面 { msg: 'hello vue'}  asRootData一个flag 是不是作为根数据
    if (!isObject(value) || value instanceof VNode) {
    // 传过来的value是否是纯粹的对象 或者 value的原型是否是VNode虚拟节点 ,如果满足条件 return
      return
    }
    var ob;
    // value中是否有 __ob__属性且他的原型是否是Observer类
    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
  }
  var Observer = function Observer(value) {
  // value: { msg: 'hello vue'}  
    this.value = value; // 将value放到Observer上
    this.dep = new Dep(); // 消息管理中心
    /*
      var Dep = function Dep() {
	    this.id = uid++;
	    this.subs = []; // 观察数组列表
	  };
    */
    this.vmCount = 0;
    def(value, '__ob__', this); // 将this赋值给__ob__属性并挂载到value上
    if (Array.isArray(value)) { // 判断value是不是数组
      if (hasProto) {
        protoAugment(value, arrayMethods);
      } else {
        copyAugment(value, arrayMethods, arrayKeys);
      }
      this.observeArray(value);
    } else {//value是对象 执行下方的walk
      this.walk(value);
    }
  };
  Observer.prototype.walk = function walk(obj) {
  // obj:{ msg: 'hello vue'}  
    var keys = Object.keys(obj); // 获取obj的键 这里是"msg"
    for (var i = 0; i < keys.length; i++) {//循环
      defineReactive$$1(obj, keys[i]);
    }
  };

 function defineReactive$$1(
    obj, // { msg: 'hello vue'}  
    key, // "msg"
    val,
    customSetter,
    shallow
  ) {
  	
    var dep = new Dep();

    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]; // val = "hello vue"
    }
    /*
    这里是重点,因为对象中可以内嵌对象,要对每一个属性就行数据劫持,就应该使用递归
    假设我们前面传过来的是下方的格式,那么上面一行应该是  val = obj[key]; 
   	val应该是 {book: "Vue.js", message: {test: 123}} 递归调用observe(val)
   	而  observe方法中有这个判断是否为纯粹对象 book不是纯粹对象 直接返回 所以childOb = false,
   	而message照着上面的循坏 递归一下 observe =>Observe继续调用,下面画一个流程图,让大家看的更清楚
   	if (!isObject(value) || value instanceof VNode) {
    // 传过来的value是否是纯粹的对象 或者 value的原型是否是VNode虚拟节点 ,如果满足条件 return
      return
    }
    data() {
	    return {
		    msg: {
		    book: "Vue.js",
		    message: {
		    test: 123
		    }
		    }
	    }
    }
    */
    var childOb = !shallow && observe(val); // 递归val
    Object.defineProperty(obj, key, { // 开始劫持数据
      enumerable: true,
      configurable: true,
      get: function reactiveGetter() {
      // 如果getter不为空 则调用getter的方法,否则使用val
        var value = getter ? getter.call(obj) : val;
        if (Dep.target) { //如果消息管理中心中target不为空执行下方,这里的target就要配合Watcher使用
        // ,后面再讲
          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();
      }
    });
  }

具体流程如下图所示:
在这里插入图片描述

上面的数据劫持get set 讲到Watcher的时候再详细讲,到此 Observe已经讲完了

四、Watcher源码分析

function Watcher(vm, exp, cb) {
    this.cb = cb;
    this.vm = vm;
    this.exp = exp;
    // 此处为了触发属性的getter,从而在dep添加自己
    this.value = this.get(); 
}

调用get

 Watcher.prototype.get = function get() {
    pushTarget(this); // 推入栈中
    var value;
    var vm = this.vm;
    try {
      value = this.getter.call(vm, vm); // 这句关键 会触发前面的数据拦截里面的get
    } catch (e) {
      if (this.user) {
        handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value);
      }
      popTarget();
      this.cleanupDeps();
    }
    return value
  };
 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();
      }
    });
Dep.prototype.depend = function depend() {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  };

  Watcher.prototype.addDep = function addDep(dep) {
    var id = dep.id;
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      if (!this.depIds.has(id)) { //如果订阅者不存在 则添加订阅者
        dep.addSub(this);
      }
    }
  };
  Dep.prototype.addSub = function addSub(sub) {
    this.subs.push(sub); // 往subs数组添加数据 添加订阅者
  };

这篇文章确实不错,可以参考一下。

发布了46 篇原创文章 · 获赞 8 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/yuanqi3131/article/details/102775839