Vue核心源码之响应式原理

246 阅读3分钟

在我们使用Vue的过程中,我们很容易就能发现通过更改Vue组件中的状态,视图就会自动更新。这样使用者就不需要关心dom的操作,只需把精力放在对数据的处理上。给开发人员带来的很大的便利,也极大的提升了开发的效率。这一切离不开我们的Vue的响应式原理。一起来了解下吧。

现在可以开始我们的正题了......

一、响应式数据如何实现

在研究源码前,我们首先应该明确需求,这是vue需要达成的基本目标,了解这些这有利于我们对源码的理解。
"数据改变,然后视图更新"。
1、数据在赋值之后,还有另一个操作,视图需要更新。这里需要增加视图更新逻辑。
2、视图更新,是哪个视图需要更新?你可能会说,更新组件内模板对应的视图啊,但是代码无法理解"组件内对应的视图"这段语言,我们需要用代码建立响应式数据跟视图渲染之间的联系。所以这里需要增加数据和渲染视图关系绑定的逻辑。
需求明确了,接下来就是代码实现了。这里我们假定我们需要监听的属性是a,于是便有了如下的伪代码。

let rValue;
Object.defineProperty(data,'a',{
  set(val){
    rValue = val;
    // 增加视图更新逻辑
  },
  get(){
    // 数据和视图关系绑定
    return rValue;
  }
})

二、vue源码实现

Vue组件定义在data中的数据最后都会被封装在一个对象中返回,这个对象接下来会进行响应式处理,使得data中的每一个属性都能变成响应式。 为了方便理解我们结合一个简单的视图案例一起来看下。 视图案例:有一个新闻类别的新闻列表,切换新闻类别可以获取到这个新闻列表下的新闻。当然在没有数据时,页面应该是空的。 ————————————————————————————
体育
. 詹姆斯打篮球 --- 得20分
. 梅西踢足球 ----- 进球2个
———————————————————————————— 你的模板中会有如下代码

<template>
    {{category}}
    <ul>
      <li v-for="nl in newsList" :key="nl">
          <span>{{nl.label}}</sapn>
          <span>{{nl.value}}</sapn>
      </li>
    </ul>
</template>
<script>
    data(){
        return {
            category:'', // 新闻类别   1、体育  2、宠物
            newsList:[]  // 新闻列表
        }
    }
</script>

vue源码实现。为了方便理解,已做部分删减。 Vue在初始化的过程中,会以你在data中定义的对象为参数创建Observer实例,内部会遍历你在data上定义的每一个属性,然后做数据劫持。

function Observer(object) {
  this.value = object;
  this.dep =  new Dep(); //@答疑1
  def(object, '__ob__', this); //定义一个属性到object上--> object.__obj__ = this,
  if (isArray(object)) {
      value.__proto__ = arrayMethods;
      this.observeArray(object);
  }else {
    var keys = Object.keys(object);
    for (var i = 0; i < keys.length; i++) {
      defineReactive(object, keys[i], object[key]);
    }
  }
}
Observer.prototype.observeArray = function (value) {
  for (var i = 0, l = value.length; i < l; i++) {
      observe(value[i]);
  }
};
// 定义响应式的函数     
function defineReactive(obj, key, val) {
  var dep = new Dep(); //@答疑1
  var childOb = observe(val);
  Object.defineProperty(obj, key, {
   set: function (newVal) {
          val = newVal;
          childOb = observe(val);
          dep.notify();
   },
   get: function () {
      var value = val;
      if (Dep.target) {
          dep.depend();
          if (childOb) {
              childOb.dep.depend();
          }
      }
      return value;
  }
  });
}
// Dep 负责帮助相应式数据与依赖建立联系 响应式数据变化后通知对应的 依赖更新
function Dep() {
   this.subs = [];
}
Dep.prototype.addSub = function (sub) {
  this.subs.push(sub);
};
Dep.prototype.depend = function () {
  if (Dep.target) {
      Dep.target.addDep(this);
      this.addSub()
  }
};
Dep.prototype.notify = function (info) { //遍历依赖 并让依赖更新
  for (var i = 0, l = subs.length; i < l; i++) {
      subs[i].update();
  }
};

三、vue源码答疑

1、 var dep = new Dep(); // 怎么搞出来个不认识的Dep,他是干嘛的? 答:在第一节"响应式数据如何实现" 中提到需要建立响应式数据和视图渲染的联系。这里Dep负责的就是建立视图渲染和响应式数据的联系,并可以在合适的时候通知视图渲染逻辑的执行。 在defineReactive 函数中,获取属性值的get函数中,有dep.depend()这个逻辑,