阅读 1531

简单理解Vue响应式原理

该文章简单模拟Vue响应式原理(MVVM),笔者还只是个大三的学生,文章有很多不到之处欢迎各位大佬指出错误,多多交流。

这是Vue官方文档的响应式原理图



官方文档的东西对于初学者来说都有点晦涩难懂,本文就将以一种简单的方式来聊聊Vue的响应式原理。

问题提出

Vuejs是数据驱动型,数据发生改变界面也会刷新改变。但这并不是理所当然,在其内部做了很多复杂的操作。

  • 思路

    1. 首先要搞懂Vue内部是如何监听数据的改变?

      通过Object.definePropety这个方法来监听数据的改变。

      这个方法的第三个参数的 Setters 和 Getters是关键。详情

    2. 已经监听了数据的改变,Vue是如何知道要通知哪些元素界面发生刷新呢?

      通过发布订阅者模式。


    Observer会监听所有的data属性并且给他们创建一一对应的Dep对象.

    Compile 会解析模板中指令(同时也会将界面初始化)。 一个指令就创建一个Watcher对象, 然后添加到相对应的Dep对象的订阅中绑定更新函数。如果一个属性的value发生改变Observer就会通知所对应的Dep对象调用notify()通知所有的订阅者 更新界面。

    详情看下图

    PS:画图技术渣渣,见谅。

具体实现

笔者只是简单模拟,还没有达到可以剖析源码的程度。

  • 先用正则做下Mustache语法(双大括号)转化。
//render.js
var render = function(template, data) {
  const reg = /\{\{(\w+)\}\}/; //不做贪婪匹配 
  if (reg.test(template)) {  //退出条件 false
     //是否需要编译
    // vue源码模板编译用的正则方法
    const key = reg.exec(template)[1]
    // console.log(key)
    template = template.replace(reg, data[key]);
    return  render(template, data)  //递归渲染
  }

  // template.replace(/{{(.)+/)
  return template
}
复制代码
  • 上代码(代码有挺详细的注释)。
<!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>Document</title>
</head>
<body>

  <div id="app">
   
  </div>

  <script src="./render.js"></script>
  <script>
    const obj = {  //模拟数据
      name: '张三',
      age: '18',
      sex: '男'
    }

   
    // 发布订阅者模式
    class Dep {
      constructor() {
        this.subs = []
      }
        
      addSub(watch) { //添加订阅方法
        this.subs.push(watch)
      }

      notify() { //通知订阅者更新
        this.subs.forEach(item => {
          item.update();
        })
      }
    }
    class Watch { 
      constructor(name) {
        this.name = name;
      }
      update() {
        console.log(this.name + 'update')
        //更新视图
        document.getElementById('app').innerHTML = render(template,obj)  
      }
    }
    
    Object.keys(obj).forEach(key => {  //遍历obj对象
      let dep = new Dep; //创建发布者对象
      let value = obj[key];
      Object.defineProperty(obj, key, {//监听数据
        set: function(newValue) {  //数据改变
          console.log( '数据' +  key + '改变了')
          // 更新数据
          value = newValue
          dep.notify() //通知订阅者更新
          
        },
        get: function() { //获取数据
          console.log('数据' + key + '加入了响应式系统')
          let w = new Watch(value)
          dep.addSub(w); 
          return value
        }
      })
    })

    obj.message = "没用的"
    //待编译的模板
    var template = '我是{{name}}, {{name}}  年龄 {{age}},性别 {{sex}} 。 {{message}} '
    document.getElementById('app').innerHTML = render(template,obj);

  </script>

</body>
</html>
复制代码
  • 效果图如下

模板编译将obj初始化的数据都加入了响应式系统。

obj.message = "没用的"这个数据没在初始化中,所有没有打印。即不是响应式属性。

Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有根级响应式属性。详情看文档声明响应式属性


页面成功的发生了刷新。看到这差不多就能初步了解了响应式原理吧!


再次证明下message不是响应式属性。我们在Vue开发中要时刻注意这点,可以避免很多坑。


最后

笔者是一个入门前端不久的小白,这篇文章挺浅显的,可能也有一些错误,希望大佬们多多给点建议。如果这篇文章对你有那么一点点小帮助的话,不妨点个赞吧。

文章分类
前端
文章标签