《Vue 进阶系列之响应式原理及实现》

时间:2022-09-26 15:23:43

https://www.bilibili.com/video/av51444410/?p=5

https://github.com/amandakelake/blog/issues/63

https://mp.weixin.qq.com/s/X3s4ysLfwclEOXIuKzOK2g

Vue 进阶系列之响应式原理及实现

前端大全 3/17
 

以下文章来源于高级前端进阶 ,作者木

高级前端进阶

木易杨,资深前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!

(给前端大全加星标,提升前端技能)

转自: 高级前端进阶

什么是响应式Reactivity

Reactivity表示一个状态改变之后,如何动态改变整个系统,在实际项目应用场景中即数据如何动态改变Dom。

需求

现在有一个需求,有a和b两个变量,要求b一直是a的10倍,怎么做?

简单尝试1:

let a = 3;let b = a * 10;console.log(b); // 30

乍一看好像满足要求,但此时b的值是固定的,不管怎么修改a,b并不会跟着一起改变。也就是说b并没有和a保持数据上的同步。只有在a变化之后重新定义b的值,b才会变化。

a = 4;console.log(a); // 4console.log(b); // 30b = a * 10;console.log(b); // 40

简单尝试2:

将a和b的关系定义在函数内,那么在改变a之后执行这个函数,b的值就会改变。伪代码如下。

onAChanged(() => {
   b = a * 10;
})

所以现在的问题就变成了如何实现onAChanged函数,当a改变之后自动执行onAChanged,请看后续。

结合view层

现在把a、b和view页面相结合,此时a对应于数据,b对应于页面。业务场景很简单,改变数据a之后就改变页面b。

<span class="cell b"></span>document
   .querySelector('.cell.b')
   .textContent = state.a * 10

现在建立数据a和页面b的关系,用函数包裹之后建立以下关系。

<span class="cell b"></span>onStateChanged(() => {    document
       .querySelector(‘.cell.b’)
       .textContent = state.a * 10})

再次抽象之后如下所示。

<span class="cell b">
   {{ state.a * 10 }}
</span> onStateChanged(() => {
   view = render(state)
})

view = render(state)是所有的页面渲染的高级抽象。这里暂不考虑view = render(state)的实现,因为需要涉及到DOM结构及其实现等一系列技术细节。这边需要的是onStateChanged的实现。

实现

实现方式是通过Object.defineProperty中的gettersetter方法。具体使用方法参考如下链接。

MDN之Object.defineProperty

需要注意的是getset函数是存取描述符,valuewritable函数是数据描述符。描述符必须是这两种形式之一,但二者不能共存,不然会出现异常。

实例1:实现convert()函数

要求如下:

  • 1、传入对象obj作为参数

  • 2、使用Object.defineProperty转换对象的所有属性

  • 3、转换后的对象保留原始行为,但在get或者set操作中输出日志

示例:

const obj = { foo: 123 }
convert(obj) obj.foo // 输出 getting key "foo": 123obj.foo = 234 // 输出 setting key "foo" to 234obj.foo // 输出 getting key "foo": 234

在了解Object.definePropertygettersetter的使用方法之后,通过修改getset函数就可以实现onAChangedonStateChanged

实现:

function convert (obj) {  // 迭代对象的所有属性
 // 并使用Object.defineProperty()转换成getter/setters
 Object.keys(obj).forEach(key => {  
   // 保存原始值
   let internalValue = obj[key]    
   Object.defineProperty(obj, key, {
     get () {        
      console.log(`getting key "${key}": ${internalValue}`)        return internalValue
     },
     set (newValue) {        
      console.log(`setting key "${key}" to: ${newValue}`)
       internalValue = newValue
     }
   })
 })
}

实例2:实现Dep

要求如下:

  • 1、创建一个Dep类,包含两个方法:dependnotify

  • 2、创建一个autorun函数,传入一个update函数作为参数

  • 3、在update函数中调用dep.depend(),显式依赖于Dep实例

  • 4、调用dep.notify()触发update函数重新运行

示例:

const dep = new Dep()

autorun(() => {
 dep.depend()  console.log('updated')
})// 注册订阅者,输出 updateddep.notify()// 通知改变,输出 updated

首先需要定义autorun函数,接收update函数作为参数。因为调用autorun时要在Dep中注册订阅者,同时调用dep.notify()时要重新执行update函数,所以Dep中必须持有update引用,这里使用变量activeUpdate表示包裹update的函数

实现代码如下。

let activeUpdate = null function autorun (update) {  const wrappedUpdate = () => {
   activeUpdate = wrappedUpdate    // 引用赋值给activeUpdate
   update()                        // 调用update,即调用内部的dep.depend
   activeUpdate = null             // 绑定成功之后清除引用
 }
 wrappedUpdate()                   // 调用}

wrappedUpdate本质是一个闭包,update函数内部可以获取到activeUpdate变量,同理dep.depend()内部也可以获取到activeUpdate变量,所以Dep的实现就很简单了。

实现代码如下。

class Dep {  // 初始化
 constructor () {          
   this.subscribers = new Set()
 }  // 订阅update函数列表
 depend () {    if (activeUpdate) {    
     this.subscribers.add(activeUpdate)
   }
 }  // 所有update函数重新运行
 notify () {              
   this.subscribers.forEach(sub => sub())
 }
}

结合上面两部分就是完整实现。

实例3:实现响应式系统

要求如下:

  • 1、结合上述两个实例,convert()重命名为观察者observe()

  • 2、observe()转换对象的属性使之响应式,对于每个转换后的属性,它会被分配一个Dep实例,该实例跟踪订阅update函数列表,并在调用setter时触发它们重新运行

  • 3、autorun()接收update函数作为参数,并在update函数订阅的属性发生变化时重新运行。

示例:

const state = {  count: 0}

observe(state)

autorun(() => {  console.log(state.count)
})// 输出 count is: 0state.count++// 输出 count is: 1

结合实例1和实例2之后就可以实现上述要求,observe中修改obj属性的同时分配Dep的实例,并在get中注册订阅者,在set中通知改变。autorun函数保存不变。 实现如下:

class Dep {  // 初始化
 constructor () {          
   this.subscribers = new Set()
 }  // 订阅update函数列表
 depend () {    if (activeUpdate) {    
     this.subscribers.add(activeUpdate)
   }
 }  // 所有update函数重新运行
 notify () {              
   this.subscribers.forEach(sub => sub())
 }
}function observe (obj) {  // 迭代对象的所有属性
 // 并使用Object.defineProperty()转换成getter/setters
 Object.keys(obj).forEach(key => {    let internalValue = obj[key]    // 每个属性分配一个Dep实例
   const dep = new Dep()    Object.defineProperty(obj, key, {    
     // getter负责注册订阅者
     get () {
       dep.depend()        return internalValue
     },      // setter负责通知改变
     set (newVal) {        const changed = internalValue !== newVal
       internalValue = newVal        
       // 触发后重新计算
       if (changed) {
         dep.notify()
       }
     }
   })
 })  return obj
}let activeUpdate = nullfunction autorun (update) {  // 包裹update函数到"wrappedUpdate"函数中,
 // "wrappedUpdate"函数执行时注册和注销自身
 const wrappedUpdate = () => {
   activeUpdate = wrappedUpdate
   update()
   activeUpdate = null
 }
 wrappedUpdate()
}

结合Vue文档里的流程图就更加清晰了。 《Vue 进阶系列之响应式原理及实现》

Job Done!!!

本文内容参考自VUE作者尤大的付费视频

《Vue 进阶系列之响应式原理及实现》的更多相关文章

  1. vue进阶用法-深入响应式原理

    异步更新队列 当vue异步执行更新DOM时,只要观察到数据变化,vue经开启一个队列,并缓冲在同一时间循环中发生的所有数据改变.如果同一个watch被多次触发,只会一次推入到队列中.然后在下一个事件循 ...

  2. vue 数组 新增元素 响应式原理 7种方法

    1.问题 思考一个问题,以下代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8" ...

  3. vue学习之深入响应式原理

    vue的响应式原理 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全 ...

  4. Vue 进阶系列(一)之响应式原理及实现

    Vue 进阶系列(一)之响应式原理及实现:https://juejin.im/post/5bce6a26e51d4579e9711f1d Vue 进阶系列(二)之插件原理及实现:https://jue ...

  5. 深度解析 Vue 响应式原理

    深度解析 Vue 响应式原理 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还是进 ...

  6. Vue&period;js学习 Item12 – 内部响应式原理探究

    深入响应式原理 大部分的基础内容我们已经讲到了,现在讲点底层内容.Vue.js 最显著的一个功能是响应系统 —— 模型只是普通对象,修改它则更新视图.这让状态管理非常简单且直观,不过理解它的原理也很重 ...

  7. Vue&period;js响应式原理

      写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:answershuto/learnV ...

  8. vue&period;js响应式原理解析与实现

    vue.js响应式原理解析与实现 从很久之前就已经接触过了angularjs了,当时就已经了解到,angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很 ...

  9. 深入浅出Vue基于&OpenCurlyDoubleQuote;依赖收集”的响应式原理(转)

    add by zhj: 文章写的很通俗易懂,明白了Object.defineProperty的用法 原文:https://zhuanlan.zhihu.com/p/29318017 每当问到VueJS ...

随机推荐

  1. python学习笔记10(Python的内存管理)

      用这张图激励一下自己,身边也就只有一位全栈数据工程师!!! 32. Python的内存管理 1. 对象的内存使用 对于整型和短字符串对象,一般内存中只有一个存储,多次引用.其他的长字符串和其他对象 ...

  2. DataSet筛选数据然后添加到新的DataSet中引发的一系列血案

    直入代码: var ds2 = new DataSet(); ) { ].Select(" usertype <> 'UU'"); ) { DataTable tmp ...

  3. 【转】重载&lpar;overload&rpar;,覆盖&lpar;override&rpar;&comma;隐藏&lpar;hide&rpar;的区别

    原文网址:http://www.cppblog.com/zgysx/archive/2007/03/12/19662.html 写正题之前,先给出几个关键字的中英文对照,重载(overload),覆盖 ...

  4. 高频交易:Solarflare组建超低延迟网络

    10Gb以太网适配器制.网卡造商Solarflare目前正在将自己的网卡系列产品转变为服务器产品.其产品在金融领域有着广泛的应用. Solarflare首先将现场可编程门阵列(FPGA)放入网络适配器 ...

  5. dict和set的使用

    使用dict和set dict Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度. 举个例子 ...

  6. 使用css3写一朵云

  7. jQuery复习:第四章

    一.jQuery中的事件 1.加载DOM $(document).ready()和window.onload方法具有类似功能,但是执行时机不同.window.onload方法是在网页中所有的元素都加载 ...

  8. c&num; 浮点数计算问题

    给大家看个计算题,看看大家的算术能力. 0.1 +0.1 +0.1 - 0.3 等于几? 大家可能会说这么简单的问题,是不是看不起我?肯定等于0啊. 如果大家直接算的是没有问题的,但是如果用计算机呢? ...

  9. Fleck For Web Socket

    效果图 (前沿).WebSocket是一种基于TCP/IP通讯一种新的通讯协议,它实现了服务器和客户端双工通讯,允许服务器主动发送给客户端. (浏览器对Socket的支持) . 浏览器 支持情况 Ch ...

  10. scrapy yield

    生成器 一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环 ...