>首页> IT >

当前观察:手把手带你了解VUE响应式原理

时间:2022-08-26 20:45:57       来源:转载
本篇文章我们来了解 Vue2.X响应式原理,然后我们来实现一个 vue响应式原理(写的内容简单)实现步骤和注释写的很清晰,大家有兴趣可以耐心观看,希望对大家有所帮助!


(资料图片)

如何快速入门VUE3.0:进入学习

Vue2.X响应式原理

1.defineProperty 的应用

Vue2.X响应式中使用到了 defineProperty进行数据劫持,所以我们对它必须有一定的了解,那么我们先来了解它的使用方法把, 这里我们来使用 defineProperty来模拟 Vue中的 data。(学习视频分享:vue视频教程)

    
<script> // 模拟 Vue的data let data = { msg: "", } // 模拟 Vue 实例 let vm = {} // 对 vm 的 msg 进行数据劫持 Object.defineProperty(vm, "msg", { // 获取数据 get() { return data.msg }, // 设置 msg set(newValue) { // 如果传入的值相等就不用修改 if (newValue === data.msg) return // 修改数据 data.msg = newValue document.querySelector("#app").textContent = data.msg }, }) // 这样子就调用了 defineProperty vm.msg 的 set vm.msg = "1234" </script>

可以看见 上面 vm.msg数据是响应式

2.defineProperty修改多个参数为响应式

看了上面的方法只能修改一个属性,实际上我们 data中数据不可能只有一个,我们何不定义一个方法把data中的数据进行遍历都修改成响应式呢

    
<script> // 模拟 Vue的data let data = { msg: "哈哈", age: "18", } // 模拟 Vue 实例 let vm = {} // 把多个属性转化 响应式 function proxyData() { // 把data 中每一项都[msg,age] 拿出来操作 Object.keys(data).forEach((key) => { // 对 vm 的 属性 进行数据劫持 Object.defineProperty(vm, key, { // 可枚举 enumerable: true, // 可配置 configurable: true, // 获取数据 get() { return data[key] }, // 设置 属性值 set(newValue) { // 如果传入的值相等就不用修改 if (newValue === data[key]) return // 修改数据 data[key] = newValue document.querySelector("#app").textContent = data[key] }, }) }) } // 调用方法 proxyData(data)</script>

3.Proxy

先来了解下 Proxy的两个参数

new Proxy(target,handler)

target:要使用 Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p的行为

其实 和 Vue2.X实现的逻辑差不多,不过实现的方法不一样

那么就放上代码了

    
<script> // 模拟 Vue data let data = { msg: "", age: "", } // 模拟 Vue 的一个实例 // Proxy 第一个 let vm = new Proxy(data, { // get() 获取值 // target 表示需要代理的对象这里指的就是 data // key 就是对象的 键 get(target, key) { return target[key] }, // 设置值 // newValue 是设置的值 set(target, key, newValue) { // 也先判断下是否和之前的值一样 节省性能 if (target[key] === newValue) return // 进行设置值 target[key] = newValue document.querySelector("#app").textContent = target[key] }, }) </script>

触发setget的方法

// 触发了set方法vm.msg = "haha"// 触发了get方法console.log(vm.msg)

4.发布订阅模式

首先来说简单介绍下 一共有三个角色

发布者订阅者信号中心举个现实中例子 作者(发布者)写一篇文章 发到了掘金(信号中心) ,掘金可以处理文章然后推送到了首页,然后各自大佬(订阅者)就可以订阅文章

在Vue 中的例子 就是EventBus$on$emit

之前代码缩进4个单位有点宽,这里改成2个

  
<script> class Vue { constructor() { // 用来存储事件 // 存储的 例子 this.subs = { "myclick": [fn1, fn2, fn3] ,"inputchange": [fn1, fn2] } this.subs = {} } // 实现 $on 方法 type是任务队列的类型 ,fn是方法 $on(type, fn) { // 判断在 subs是否有当前类型的 方法队列存在 if (!this.subs[type]) { // 没有就新增一个 默认为空数组 this.subs[type] = [] } // 把方法加到该类型中 this.subs[type].push(fn) } // 实现 $emit 方法 $emit(type) { // 首先得判断该方法是否存在 if (this.subs[type]) { // 获取到参数 const args = Array.prototype.slice.call(arguments, 1) // 循环队列调用 fn this.subs[type].forEach((fn) => fn(...args)) } } } // 使用 const eventHub = new Vue() // 使用 $on 添加一个 sum 类型的 方法到 subs["sum"]中 eventHub.$on("sum", function () { let count = [...arguments].reduce((x, y) => x + y) console.log(count) }) // 触发 sum 方法 eventHub.$emit("sum", 1, 2, 4, 5, 6, 7, 8, 9, 10) </script>

5.观察者模式

与发布订阅者不同 观察者中 发布者和订阅者(观察者)是相互依赖的 必须要求观察者订阅内容改变事件 ,而发布订阅者是由调度中心进行调度,那么看看观察者模式 是如何相互依赖,下面就举个简单例子

  
<script> // 目标 class Subject { constructor() { this.observerLists = [] } // 添加观察者 addObs(obs) { // 判断观察者是否有 和 存在更新订阅的方法 if (obs && obs.update) { // 添加到观察者列表中 this.observerLists.push(obs) } } // 通知观察者 notify() { this.observerLists.forEach((obs) => { // 每个观察者收到通知后 会更新事件 obs.update() }) } // 清空观察者 empty() { this.subs = [] } } class Observer { // 定义观察者内容更新事件 update() { // 在更新事件要处理的逻辑 console.log("目标更新了") } } // 使用 // 创建目标 let sub = new Subject() // 创建观察者 let obs1 = new Observer() let obs2 = new Observer() // 把观察者添加到列表中 sub.addObs(obs1) sub.addObs(obs2) // 目标开启了通知 每个观察者者都会自己触发 update 更新事件 sub.notify() </script>

6.模拟Vue的响应式原理

接收初始化的参数,这里只举几个简单的例子 eldataoptions通过私有方法 _proxyDatadata注册到 Vue中 转成gettersetter使用 observerdata中的属性转为 响应式 添加到 自身身上使用 observer方法监听 data的所有属性变化来 通过观察者模式 更新视图使用 compiler编译元素节点上面指令 和 文本节点差值表达式

1.vue.js

在这里获取到 eldata

通过 _proxyDatadata的属性 注册到Vue并转成 gettersetter

/* vue.js */class Vue {  constructor(options) {    // 获取到传入的对象 没有默认为空对象    this.$options = options || {}    // 获取 el    this.$el =      typeof options.el === "string"        ? document.querySelector(options.el)        : options.el    // 获取 data    this.$data = options.data || {}    // 调用 _proxyData 处理 data中的属性    this._proxyData(this.$data)  }  // 把data 中的属性注册到 Vue  _proxyData(data) {    Object.keys(data).forEach((key) => {      // 进行数据劫持      // 把每个data的属性 到添加到 Vue 转化为 getter setter方法      Object.defineProperty(this, key, {        // 设置可以枚举        enumerable: true,        // 设置可以配置        configurable: true,        // 获取数据        get() {          return data[key]        },        // 设置数据        set(newValue) {          // 判断新值和旧值是否相等          if (newValue === data[key]) return          // 设置新值          data[key] = newValue        },      })    })  }}

2.observer.js

在这里把 data中的 属性变为响应式加在自身的身上,还有一个主要功能就是 观察者模式在 第 4.dep.js会有详细的使用

/* observer.js */class Observer {  constructor(data) {    // 用来遍历 data    this.walk(data)  }  // 遍历 data 转为响应式  walk(data) {    // 判断 data是否为空 和 对象    if (!data || typeof data !== "object") return    // 遍历 data    Object.keys(data).forEach((key) => {      // 转为响应式      this.defineReactive(data, key, data[key])    })  }  // 转为响应式  // 要注意的 和vue.js 写的不同的是  // vue.js中是将 属性给了 Vue 转为 getter setter  // 这里是 将data中的属性转为getter setter  defineReactive(obj, key, value) {    // 如果是对象类型的 也调用walk 变成响应式,不是对象类型的直接在walk会被return    this.walk(value)    // 保存一下 this    const self = this    Object.defineProperty(obj, key, {      // 设置可枚举      enumerable: true,      // 设置可配置      configurable: true,      // 获取值      get() {        return value      },      // 设置值      set(newValue) {        // 判断旧值和新值是否相等        if (newValue === value) return        // 设置新值        value = newValue        // 赋值的话如果是newValue是对象,对象里面的属性也应该设置为响应式的        self.walk(newValue)      },    })  }}

在html中引入的话注意顺序

<script src="./js/observer.js"></script><script src="./js/vue.js"></script>

然后在vue.js中使用 Observer

/* vue.js */class Vue {  constructor(options) {    ...    // 使用 Obsever 把data中的数据转为响应式    new Observer(this.$data)  }  // 把data 中的属性注册到 Vue  _proxyData(data) {   ...  }}

看到这里为什么做了两个重复性的操作呢?重复性两次把 data的属性转为响应式

obsever.js中是把 data的所有属性 加到 data自身 变为响应式 转成 gettersetter方式

vue.js中 也把 data的 的所有属性 加到 Vue上,是为了以后方面操作可以用 Vue的实例直接访问到 或者在 Vue中使用 this访问

使用例子:

    
<script src="./js/observer.js"></script> <script src="./js/vue.js"></script> <script> let vm = new Vue({ el: "#app", data: { msg: "123", age: 21, }, }) </script>

这样在Vue$data中都存在了 所有的data属性了 并且是响应式的

3.compiler.js

comilper.js在这个文件里实现对文本节点 和 元素节点指令编译 主要是为了举例子 当然这个写的很简单 指令主要实现 v-textv-model

/* compiler.js */class Compiler {  // vm 指 Vue 实例  constructor(vm) {    // 拿到 vm    this.vm = vm    // 拿到 el    this.el = vm.$el    // 编译模板    this.compile(this.el)  }  // 编译模板  compile(el) {    // 获取子节点 如果使用 forEach遍历就把伪数组转为真的数组    let childNodes = [...el.childNodes]    childNodes.forEach((node) => {      // 根据不同的节点类型进行编译      // 文本类型的节点      if (this.isTextNode(node)) {        // 编译文本节点        this.compileText(node)      } else if (this.isElementNode(node)) {        //元素节点        this.compileElement(node)      }      // 判断是否还存在子节点考虑递归      if (node.childNodes && node.childNodes.length) {        // 继续递归编译模板        this.compile(node)      }    })  }  // 编译文本节点(简单的实现)  compileText(node) {    // 核心思想利用把正则表达式把{{}}去掉找到里面的变量    // 再去Vue找这个变量赋值给node.textContent    let reg = /\{\{(.+?)\}\}/    // 获取节点的文本内容    let val = node.textContent    // 判断是否有 {{}}    if (reg.test(val)) {      // 获取分组一  也就是 {{}} 里面的内容 去除前后空格      let key = RegExp.$1.trim()      // 进行替换再赋值给node      node.textContent = val.replace(reg, this.vm[key])    }  }  // 编译元素节点这里只处理指令  compileElement(node) {    // 获取到元素节点上面的所有属性进行遍历    ![...node.attributes].forEach((attr) => {      // 获取属性名      let attrName = attr.name      // 判断是否是 v- 开头的指令      if (this.isDirective(attrName)) {        // 除去 v- 方便操作        attrName = attrName.substr(2)        // 获取 指令的值就是  v-text = "msg"  中msg        // msg 作为 key 去Vue 找这个变量        let key = attr.value        // 指令操作 执行指令方法        // vue指令很多为了避免大量个 if判断这里就写个 uapdate 方法        this.update(node, key, attrName)      }    })  }  // 添加指令方法 并且执行  update(node, key, attrName) {    // 比如添加 textUpdater 就是用来处理 v-text 方法    // 我们应该就内置一个 textUpdater 方法进行调用    // 加个后缀加什么无所谓但是要定义相应的方法    let updateFn = this[attrName + "Updater"]    // 如果存在这个内置方法 就可以调用了    updateFn && updateFn(node, key, this.vm[key])  }  // 提前写好 相应的指定方法比如这个 v-text  // 使用的时候 和 Vue 的一样  textUpdater(node, key, value) {    node.textContent = value  }      // v-model  modelUpdater(node, key, value) {    node.value = value  }      // 判断元素的属性是否是 vue 指令  isDirective(attr) {    return attr.startsWith("v-")  }  // 判断是否是元素节点  isElementNode(node) {    return node.nodeType === 1  }  // 判断是否是 文本 节点  isTextNode(node) {    return node.nodeType === 3  }}

4.dep.js

写一个Dep类 它相当于 观察者中的发布者 每个响应式属性都会创建这么一个 Dep对象 ,负责收集该依赖属性的Watcher对象 (是在使用响应式数据的时候做的操作)

当我们对响应式属性在 setter中进行更新的时候,会调用 Depnotify方法发送更新通知

然后去调用 Watcher中的 update实现视图的更新操作(是当数据发生变化的时候去通知观察者调用观察者的update更新视图)

总的来说 在Dep(这里指发布者) 中负责收集依赖 添加观察者(这里指Wathcer),然后在 setter数据更新的时候通知观察者

说的这么多重复的话,大家应该知道是在哪个阶段 收集依赖 哪个阶段 通知观察者了吧,下面就来实现一下吧

/* dep.js */class Dep {  constructor() {    // 存储观察者    this.subs = []  }  // 添加观察者  addSub(sub) {    // 判断观察者是否存在 和 是否拥有update方法    if (sub && sub.update) {      this.subs.push(sub)    }  }  // 通知方法  notify() {    // 触发每个观察者的更新方法    this.subs.forEach((sub) => {      sub.update()    })  }}

get中添加 Dep.target(观察者)

set中 触发 notify(通知)

/* observer.js */class Observer {  ...  }  // 遍历 data 转为响应式  walk(data) {   ...  }  // 这里是 将data中的属性转为getter setter  defineReactive(obj, key, value) {...    // 创建 Dep 对象    let dep = new Dep()    Object.defineProperty(obj, key, {  ...      // 获取值      get() {        // 在这里添加观察者对象 Dep.target 表示观察者        Dep.target && dep.addSub(Dep.target)        return value      },      // 设置值      set(newValue) {        if (newValue === value) return        value = newValue        self.walk(newValue)        // 触发通知 更新视图        dep.notify()      },    })  }}

5.watcher.js

**watcher **的作用 数据更新后 收到通知之后 调用 update进行更新

/* watcher.js */class Watcher {  constructor(vm, key, cb) {    // vm 是 Vue 实例    this.vm = vm    // key 是 data 中的属性    this.key = key    // cb 回调函数 更新视图的具体方法    this.cb = cb    // 把观察者的存放在 Dep.target    Dep.target = this    // 旧数据 更新视图的时候要进行比较    // 还有一点就是 vm[key] 这个时候就触发了 get 方法    // 之前在 get 把 观察者 通过dep.addSub(Dep.target) 添加到了 dep.subs中    this.oldValue = vm[key]    // Dep.target 就不用存在了 因为上面的操作已经存好了    Dep.target = null  }  // 观察者中的必备方法 用来更新视图  update() {    // 获取新值    let newValue = this.vm[this.key]    // 比较旧值和新值    if (newValue === this.oldValue) return    // 调用具体的更新方法    this.cb(newValue)  }}

那么去哪里创建 Watcher呢?还记得在 compiler.js中 对文本节点的编译操作吗

在编译完文本节点后 在这里添加一个 Watcher

还有 v-textv-model指令 当编译的是元素节点 就添加一个 Watcher

/* compiler.js */class Compiler {  // vm 指 Vue 实例  constructor(vm) {    // 拿到 vm    this.vm = vm    // 拿到 el    this.el = vm.$el    // 编译模板    this.compile(this.el)  }  // 编译模板  compile(el) {    let childNodes = [...el.childNodes]    childNodes.forEach((node) => {      if (this.isTextNode(node)) {        // 编译文本节点        this.compileText(node)      }        ...  }  // 编译文本节点(简单的实现)  compileText(node) {    let reg = /\{\{(.+)\}\}/    let val = node.textContent    if (reg.test(val)) {      let key = RegExp.$1.trim()      node.textContent = val.replace(reg, this.vm[key])      // 创建观察者      new Watcher(this.vm, key, newValue => {        node.textContent = newValue      })    }  }  ...  // v-text   textUpdater(node, key, value) {    node.textContent = value     // 创建观察者2    new Watcher(this.vm, key, (newValue) => {      node.textContent = newValue    })  }  // v-model  modelUpdater(node, key, value) {    node.value = value    // 创建观察者    new Watcher(this.vm, key, (newValue) => {      node.value = newValue    })    // 这里实现双向绑定 监听input 事件修改 data中的属性    node.addEventListener("input", () => {      this.vm[key] = node.value    })  }}

当 我们改变 响应式属性的时候 触发了 set()方法 ,然后里面 发布者 dep.notify方法启动了,拿到了 所有的 观察者 watcher实例去执行 update方法调用了回调函数 cb(newValue)方法并把 新值传递到了 cb()当中 cb方法是的具体更新视图的方法 去更新视图

比如上面的例子里的第三个参数 cb方法

new Watcher(this.vm, key, newValue => {    node.textContent = newValue})

还有一点要实现v-model的双向绑定

不仅要通过修改数据来触发更新视图,还得为node添加 input事件 改变 data数据中的属性

来达到双向绑定的效果

7.测试下自己写的

  
{{msg}}
{{age}}
<script src="./js/dep.js"></script> <script src="./js/watcher.js"></script> <script src="./js/compiler.js"></script> <script src="./js/observer.js"></script> <script src="./js/vue.js"></script> <script> let vm = new Vue({ el: "#app", data: { msg: "123", age: 21, }, }) </script>

OK 基本实现了 通过 观察者模式 来 实现 响应式原理

8.五个文件代码

这里直接把5个文件个代码贴出来 上面有的地方省略了,下面是完整的方便大家阅读

vue.js

/* vue.js */class Vue {  constructor(options) {    // 获取到传入的对象 没有默认为空对象    this.$options = options || {}    // 获取 el    this.$el =      typeof options.el === "string"        ? document.querySelector(options.el)        : options.el    // 获取 data    this.$data = options.data || {}    // 调用 _proxyData 处理 data中的属性    this._proxyData(this.$data)    // 使用 Obsever 把data中的数据转为响应式    new Observer(this.$data)    // 编译模板    new Compiler(this)  }  // 把data 中的属性注册到 Vue  _proxyData(data) {    Object.keys(data).forEach((key) => {      // 进行数据劫持      // 把每个data的属性 到添加到 Vue 转化为 getter setter方法      Object.defineProperty(this, key, {        // 设置可以枚举        enumerable: true,        // 设置可以配置        configurable: true,        // 获取数据        get() {          return data[key]        },        // 设置数据        set(newValue) {          // 判断新值和旧值是否相等          if (newValue === data[key]) return          // 设置新值          data[key] = newValue        },      })    })  }}

obsever.js

/* observer.js */class Observer {  constructor(data) {    // 用来遍历 data    this.walk(data)  }  // 遍历 data 转为响应式  walk(data) {    // 判断 data是否为空 和 对象    if (!data || typeof data !== "object") return    // 遍历 data    Object.keys(data).forEach((key) => {      // 转为响应式      this.defineReactive(data, key, data[key])    })  }  // 转为响应式  // 要注意的 和vue.js 写的不同的是  // vue.js中是将 属性给了 Vue 转为 getter setter  // 这里是 将data中的属性转为getter setter  defineReactive(obj, key, value) {    // 如果是对象类型的 也调用walk 变成响应式,不是对象类型的直接在walk会被return    this.walk(value)    // 保存一下 this    const self = this    // 创建 Dep 对象    let dep = new Dep()    Object.defineProperty(obj, key, {      // 设置可枚举      enumerable: true,      // 设置可配置      configurable: true,      // 获取值      get() {        // 在这里添加观察者对象 Dep.target 表示观察者        Dep.target && dep.addSub(Dep.target)        return value      },      // 设置值      set(newValue) {        // 判断旧值和新值是否相等        if (newValue === value) return        // 设置新值        value = newValue        // 赋值的话如果是newValue是对象,对象里面的属性也应该设置为响应式的        self.walk(newValue)        // 触发通知 更新视图        dep.notify()      },    })  }}

compiler.js

/* compiler.js */class Compiler {  // vm 指 Vue 实例  constructor(vm) {    // 拿到 vm    this.vm = vm    // 拿到 el    this.el = vm.$el    // 编译模板    this.compile(this.el)  }  // 编译模板  compile(el) {    // 获取子节点 如果使用 forEach遍历就把伪数组转为真的数组    let childNodes = [...el.childNodes]    childNodes.forEach((node) => {      // 根据不同的节点类型进行编译      // 文本类型的节点      if (this.isTextNode(node)) {        // 编译文本节点        this.compileText(node)      } else if (this.isElementNode(node)) {        //元素节点        this.compileElement(node)      }      // 判断是否还存在子节点考虑递归      if (node.childNodes && node.childNodes.length) {        // 继续递归编译模板        this.compile(node)      }    })  }  // 编译文本节点(简单的实现)  compileText(node) {    // 核心思想利用把正则表达式把{{}}去掉找到里面的变量    // 再去Vue找这个变量赋值给node.textContent    let reg = /\{\{(.+?)\}\}/    // 获取节点的文本内容    let val = node.textContent    // 判断是否有 {{}}    if (reg.test(val)) {      // 获取分组一  也就是 {{}} 里面的内容 去除前后空格      let key = RegExp.$1.trim()      // 进行替换再赋值给node      node.textContent = val.replace(reg, this.vm[key])      // 创建观察者      new Watcher(this.vm, key, (newValue) => {        node.textContent = newValue      })    }  }  // 编译元素节点这里只处理指令  compileElement(node) {    // 获取到元素节点上面的所有属性进行遍历    ![...node.attributes].forEach((attr) => {      // 获取属性名      let attrName = attr.name      // 判断是否是 v- 开头的指令      if (this.isDirective(attrName)) {        // 除去 v- 方便操作        attrName = attrName.substr(2)        // 获取 指令的值就是  v-text = "msg"  中msg        // msg 作为 key 去Vue 找这个变量        let key = attr.value        // 指令操作 执行指令方法        // vue指令很多为了避免大量个 if判断这里就写个 uapdate 方法        this.update(node, key, attrName)      }    })  }  // 添加指令方法 并且执行  update(node, key, attrName) {    // 比如添加 textUpdater 就是用来处理 v-text 方法    // 我们应该就内置一个 textUpdater 方法进行调用    // 加个后缀加什么无所谓但是要定义相应的方法    let updateFn = this[attrName + "Updater"]    // 如果存在这个内置方法 就可以调用了    updateFn && updateFn.call(this, node, key, this.vm[key])  }  // 提前写好 相应的指定方法比如这个 v-text  // 使用的时候 和 Vue 的一样  textUpdater(node, key, value) {    node.textContent = value    // 创建观察者    new Watcher(this.vm, key, (newValue) => {      node.textContent = newValue    })  }  // v-model  modelUpdater(node, key, value) {    node.value = value    // 创建观察者    new Watcher(this.vm, key, (newValue) => {      node.value = newValue    })    // 这里实现双向绑定    node.addEventListener("input", () => {      this.vm[key] = node.value    })  }  // 判断元素的属性是否是 vue 指令  isDirective(attr) {    return attr.startsWith("v-")  }  // 判断是否是元素节点  isElementNode(node) {    return node.nodeType === 1  }  // 判断是否是 文本 节点  isTextNode(node) {    return node.nodeType === 3  }}

dep.js

/* dep.js */class Dep {  constructor() {    // 存储观察者    this.subs = []  }  // 添加观察者  addSub(sub) {    // 判断观察者是否存在 和 是否拥有update方法    if (sub && sub.update) {      this.subs.push(sub)    }  }  // 通知方法  notify() {    // 触发每个观察者的更新方法    this.subs.forEach((sub) => {      sub.update()    })  }}

watcher.js

/* watcher.js */class Watcher {  constructor(vm, key, cb) {    // vm 是 Vue 实例    this.vm = vm    // key 是 data 中的属性    this.key = key    // cb 回调函数 更新视图的具体方法    this.cb = cb    // 把观察者的存放在 Dep.target    Dep.target = this    // 旧数据 更新视图的时候要进行比较    // 还有一点就是 vm[key] 这个时候就触发了 get 方法    // 之前在 get 把 观察者 通过dep.addSub(Dep.target) 添加到了 dep.subs中    this.oldValue = vm[key]    // Dep.target 就不用存在了 因为上面的操作已经存好了    Dep.target = null  }  // 观察者中的必备方法 用来更新视图  update() {    // 获取新值    let newValue = this.vm[this.key]    // 比较旧值和新值    if (newValue === this.oldValue) return    // 调用具体的更新方法    this.cb(newValue)  }}

(学习视频分享:web前端开发、编程基础视频)

以上就是手把手带你了解VUE响应式原理的详细内容,更多请关注php中文网其它相关文章!

关键词: 观察者模式 所有属性 是否相等