手把手带你了解VUE响应式原理

本篇文章我们来了解 vue2.x 响应式原理,然后我们来实现一个 vue 响应式原理(写的内容简单)实现步骤和注释写的很清晰,大家有兴趣可以耐心观看,希望对大家有所帮助!

手把手带你了解VUE响应式原理

Vue2.X响应式原理

1.defineProperty 的应用

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

    
// 模拟 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'

登录后复制

7

立即学习“前端免费学习笔记(深入)”;

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

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

修改多个参数

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

    
// 模拟 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)

登录后复制

3.Proxy

Vue3 中使用 Proxy 来设置响应式的属性

先来了解下 Proxy 的两个参数

new Proxy(target,handler)

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

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

那么就放上代码了

    
// 模拟 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] }, })

登录后复制

触发setget 的方法

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

登录后复制

4.发布订阅模式

在Vue 响应式中应用到了 发布订阅模式 我们先来了解下

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

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

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

那么我们就简单模仿一下 Vue 的事件总线吧

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

  
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)

登录后复制

5.观察者模式

与 发布订阅 的差异

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

  
// 目标 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()

登录后复制

6.模拟Vue的响应式原理

这里来实现一个小型简单的 Vue 主要实现以下的功能

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

1.vue.js

在这里获取到 el data

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

/* 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中引入的话注意顺序


登录后复制

然后在vue.js 中使用 Observer

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

登录后复制

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

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

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

使用例子:

    
let vm = new Vue({ el: '#app', data: { msg: '123', age: 21, }, })

登录后复制

image-20210725162744305

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

3.compiler.js

comilper.js在这个文件里实现对文本节点 和 元素节点指令编译 主要是为了举例子 当然这个写的很简单 指令主要实现 v-text v-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

/* 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()    })  }}

登录后复制登录后复制

obsever.js 中使用Dep

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-text v-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}}
let vm = new Vue({ el: '#app', data: { msg: '123', age: 21, }, })

登录后复制

8

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)  }}

登录后复制登录后复制

(学习视频分享:vue视频教程、vue视频教程)

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/3231492.html

(0)
上一篇 2025年4月1日 16:56:10
下一篇 2025年4月1日 16:56:20

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 微信小程序的前端框架(如 Taro、uni-app)使用教程

    taro 和 uni-app 是用于开发微信小程序的高效前端框架。1. taro 采用 react 语法,uni-app 基于 vue.js。2. 它们通过编译器将代码转换为各平台原生代码,支持跨平台开发。3. 使用示例包括基本页面创建和复…

    2025年4月28日
    000
  • 哪些小程序框架适合快速开发?

    在移动互联网时代,小程序因其轻量级和即用即走的特性迅速崛起,成为众多企业和个人开发者的首选。然而,对于初学者或希望快速搭建小程序的开发者来说,选择合适的框架至关重要。本文将介绍几个适合快速开发的小程序框架,帮助读者更好地理解并选择合适的工具…

    2025年4月28日
    000
  • 小程序跨平台开发:实现多平台共享

    小程序跨平台开发已成为企业提升效率、节约成本、扩展用户群体的关键策略。本文将详细探讨小程序跨平台开发的概念、优势以及如何通过小程序共享开发实现多平台的无缝对接。小程序跨平台开发是指开发者编写一套代码,通过特定框架和工具的转换,可以在多个平台…

    2025年4月28日
    000
  • 零基础怎样自制小程序?

    小程序作为一种便捷的移动应用形式,越来越受到人们的关注和喜爱。对于零基础的初学者来说,如何自制一个小程序呢?本文将为您提供一些简单的指导步骤。 一、掌握小程序的基本概念 小程序是一种轻量级应用,可以在微信平台上运行,无需下载和安装。它具有类…

    2025年4月28日
    000
  • 可视化微信小程序开发工具的优势有哪些?

    可视化微信小程序开发工具是一款专门为微信小程序开发者设计的无代码开发平台。它提供了可视化界面设计、插件功能开发以及自动代码生成等功能,旨在帮助开发者快速高效地完成小程序开发,节省时间和成本,提升开发效率。 一、可视化微信小程序开发工具的优势…

    2025年4月28日
    000
  • UNIAPP小程序是什么?

    uniapp小程序是一种多平台移动应用开发框架,旨在通过使用一套代码基础来简化开发过程,减少重复代码的编写,从而提升开发效率并降低成本。本文将详细介绍uniapp小程序的特点、优势以及应用场景。 UNIAPP的特点: 多平台兼容性:UNIA…

    2025年4月28日
    000
  • APP开发和小程序开发的平台一样吗?

    随着移动互联网的迅猛发展,移动应用(app)和小程序成为了用户获取服务和信息的主要渠道。虽然app和小程序都是为移动设备设计的应用程序,但在开发平台和技术上存在一定的差异。 APP开发: APP是一款独立的应用程序,需要在操作系统上安装和运…

    2025年4月28日
    000
  • 安卓Android App用什么语言和框架开发?

    随着华为、小米、oppo等国产品牌手机研发技术的不断成熟,安卓手机用户的使用比例也在显著增加。5g技术的普及使国内多种5g安卓手机品牌保持稳定增长。根据数据统计,2021年第三季度,国内安卓5g手机的激活设备数占比持续上升,其中华为5g手机…

    2025年4月27日 IT业界
    000
  • Vue3知识地图二:Vue生命周期函数与常用模板语法

    在之前的文章中给大家分享了vue的知识思维导图,如果没看过的话可以点击下方链接查看。本篇继续给大家分享关于vue生命周期函数与常用模板语法的思维导图,希望对大家有帮助!欢迎大家收藏分享学习! 系列文章:《Vue3知识地图一:学前了解与应用创…

    2025年4月5日
    200
  • Vue3知识地图三:Vue样式绑定语法与列表循环渲染

    在之前的文章中给大家分享了vue的知识思维导图,如果没看过的话可以点击下方链接查看。,本篇继续给大家分享vue样式绑定语法与列表循环渲染,希望对大家有帮助!欢迎大家收藏分享学习! 系列文章:《Vue3知识地图一:学前了解与应用创建》|《Vu…

    2025年4月5日
    100

发表回复

登录后才能评论