VUE官方文档学习---深入响应式原理


title: VUE官方文档学习—深入响应式原理
date: 2021-6-6 20:41:08
author: Xilong88
tags: Vue

先上一张图:

VUE官方文档学习---深入响应式原理_第1张图片
这是官方对响应式原理的解释图

我在网上查到一个简易版的响应式原理的代码

原贴在这

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Two-way data-binding</title>
</head>
<body>
  
 <div id="app">
  <input type="text" v-model="text">
  {
     {
      text }}
 </div>
 
 <script>
  function observe (obj, vm) {
     
   Object.keys(obj).forEach(function (key) {
     
    defineReactive(vm, key, obj[key]);
   });
  }
 
  function defineReactive (obj, key, val) {
     
 
   var dep = new Dep();
 
   Object.defineProperty(obj, key, {
     
    get: function () {
     
     // 添加订阅者watcher到主题对象Dep
     if (Dep.target) dep.addSub(Dep.target);
     return val
    },
    set: function (newVal) {
     
     if (newVal === val) return
     val = newVal;
     // 作为发布者发出通知
     dep.notify();
    }
   });
  }
 
  function nodeToFragment (node, vm) {
     
   var flag = document.createDocumentFragment();
   var child;
    console.log(node)
   while (child = node.firstChild) {
     
    compile(child, vm);
    flag.appendChild(child); // 将子节点劫持到文档片段中
   }
 
   return flag;
  }
 
  function compile (node, vm) {
     
   var reg = /\{\{(.*)\}\}/;
   // 节点类型为元素
   if (node.nodeType === 1) {
     
    var attr = node.attributes;
    // 解析属性
    for (var i = 0; i < attr.length; i++) {
     
     if (attr[i].nodeName == 'v-model') {
     
      var name = attr[i].nodeValue; // 获取v-model绑定的属性名
      node.addEventListener('input', function (e) {
     
       // 给相应的data属性赋值,进而触发该属性的set方法
       vm[name] = e.target.value;
      });
      node.value = vm[name]; // 将data的值赋给该node
      node.removeAttribute('v-model');
     }
    };
 
    new Watcher(vm, node, name, 'input');
   }
   // 节点类型为text
   if (node.nodeType === 3) {
     
    if (reg.test(node.nodeValue)) {
     
     var name = RegExp.$1; // 获取匹配到的字符串
     name = name.trim();
 
     new Watcher(vm, node, name, 'text');
    }
   }
  }
 
  function Watcher (vm, node, name, nodeType) {
     
   Dep.target = this;
   this.name = name;
   this.node = node;
   this.vm = vm;
   this.nodeType = nodeType;
   this.update();
   Dep.target = null;
  }
 
  Watcher.prototype = {
     
   update: function () {
     
    this.get();
    if (this.nodeType == 'text') {
     
     this.node.nodeValue = this.value;
    }
    if (this.nodeType == 'input') {
     
     this.node.value = this.value;
    }
   },
   // 获取data中的属性值
   get: function () {
     
    this.value = this.vm[this.name]; // 触发相应属性的get
   }
  }
 
  function Dep () {
     
   this.subs = []
  }
 
  Dep.prototype = {
     
   addSub: function(sub) {
     
    this.subs.push(sub);
   },
 
   notify: function() {
     
    this.subs.forEach(function(sub) {
     
     sub.update();
    });
   }
  };
 
  function Vue (options) {
     
   this.data = options.data;
   var data = this.data;
 
   observe(data, this);
 
   var id = options.el;
   var dom = nodeToFragment(document.getElementById(id), this);
 
   // 编译完成后,将dom返回到app中
   document.getElementById(id).appendChild(dom); 
  }
 
  var vm = new Vue({
     
   el: 'app',
   data: {
     
    text: 'hello world'
   }
  });
 
 </script>
 
</body>
</html>

研究这段代码基本上就能理解响应式原理了

核心流程就是(拿v-model绑定的input举例),初始化Vue时,用defineProperty给data里的属性添加get和set方法,解析标签后,给input添加监听器,监听input事件,(后续input触发就会触发set)。

接下来实例化一个watcher,实例化的时候会调用watcher的get方法,获取data属性的值,同时触发了get,然后get就把这个watcher放入到一个数组中,表示这个watcher订阅了这个属性。

好,接下来input一些值,然后就会触发set,然后set就会去通知数组里面的所有watcher,也是就调用它们的notify方法,然后watcher就会去更新,在图上就是re-render

所以关键还是 defineProperty定义的getter,setter。还有new Watcher。

这就能解释为什么只有在初始化的时候在data里面,才能响应式了。

所以,假如我们直接改变文本,就是在控制台里面修改dom值,这样没有事件监听修改,所以VM里面的data是不会改变的,所以双向绑定,在修改dom属性的时候,要有事件监听触发data属性的setter才行。

假如想后续添加,Vue提供了方法:

Vue.set(object, propertyName, value)

vm.$set实例方法,是set的别名。

this.$set(this.someObject,'b',2)

所以想改变data里面的对象,就应该改变对象的所有,也就是传一个新对象进去,这样才会调用setter。

// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({
     }, this.someObject, {
      a: 1, b: 2 })

对于数组也是一样的道理:

// Vue.set 通过set触发setter
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice  说明splice可以触发setter
vm.items.splice(indexOfItem, 1, newValue)

异步更新队列

当一个事件循环里面触发多次watcher,并不会立马re-render,而是把操作添加到队列里面去,在下次事件循环中优化去重,然后再更新。

Vue.nextTick(callback)

就是用来在下次更新DOM完之后,回调。

可以在数据变化之后立即使用 Vue.nextTick(callback),这样回调函数将在 DOM 更新完成后被调用

Vue.component('example', {
     
  template: '{
     { message }}',
  data: function () {
     
    return {
     
      message: '未更新'
    }
  },
  methods: {
     
    updateMessage: function () {
     
      this.message = '已更新'
      console.log(this.$el.textContent) // => '未更新'
      this.$nextTick(function () {
     
        console.log(this.$el.textContent) // => '已更新'
      })
    }
  }
})

因为 $nextTick() 返回一个 Promise 对象,所以你可以使用新的 ES2017 async/await 语法完成相同的事情:

methods: {
     
  updateMessage: async function () {
     
    this.message = '已更新'
    console.log(this.$el.textContent) // => '未更新'
    await this.$nextTick()
    console.log(this.$el.textContent) // => '已更新'
  }
}

你可能感兴趣的:(Vue官方文档学习,vue,响应式)