Vue进阶系列汇总如下,欢迎阅读。

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

Vue 进阶系列(二)之插件原理及实现

Vue 进阶系列(三)之Render函数原理及实现

什么是响应式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); // 4
console.log(b); // 30
b = 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": 123
obj.foo = 234 // 输出 setting key "foo" to 234
obj.foo // 输出 getting key "foo": 234
复制代码

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

实现:

function convert (obj) {// 迭代对象的所有属性// 并使用Object.defineProperty()转换成getter/settersObject.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    // 引用赋值给activeUpdateupdate()                        // 调用update,即调用内部的dep.dependactiveUpdate = 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/settersObject.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 !== newValinternalValue = newVal// 触发后重新计算if (changed) {dep.notify()}}})})return obj
}let activeUpdate = nullfunction autorun (update) {// 包裹update函数到"wrappedUpdate"函数中,// "wrappedUpdate"函数执行时注册和注销自身const wrappedUpdate = () => {activeUpdate = wrappedUpdateupdate()activeUpdate = null}wrappedUpdate()
}
复制代码

结合Vue文档里的流程图就更加清晰了。

Job Done!!!

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

交流

本人Github链接如下,欢迎各位Star

github.com/yygmind/blo…

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

Vue 进阶系列(一)之响应式原理及实现相关推荐

  1. Vue.js 框架源码与进阶 - Vue.js 源码剖析 - 响应式原理

    文章目录 一.准备工作 1.1 Vue 源码的获取 1.2 源目录结构 1.3 了解 Flow 1.4 调试设置 1.5 Vue 的不同构建版本 1.6 寻找入口文件 1.7 从入口开始 二.Vue ...

  2. Vue深入学习3—数据响应式原理

    1.数据响应式原理 1.1.MVVM是什么? 简单来说,就是数据变了,视图也会跟着变,首先你得定义一个带有{{ }}的模板Model,当数据中的值变化了,视图View就会跟着变化:视图模型View-m ...

  3. vue总结系列--数据驱动和响应式

    在公司里帮项目组里开发后台系统的前端项目也有一段时间了. vue这种数据驱动,组件化的框架和react很像, 从一开始的快速上手基本的开发,到后来开始自定义组件,对element UI的组件二次封装以 ...

  4. Vue响应式原理的简单模型

    1.前言 最近在梳理vue响应式的原理,有一些心得,值得写一篇博客出来看看. 其实之前也尝试过了解vue响应式的原理,毕竟现在面试看你用的是vue的话,基本上都会问你几句vue响应式的原理.以往学习这 ...

  5. 【Vue.js源码解析 一】-- 响应式原理

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 课程目标 Vue.js 的静态成员和实例成员初始化过程 首次渲染的过程 数据响应式原理 – 最核心的特性之一 准备工作 ...

  6. data access components 2.0未响应_Vue2.x 源码剖析之响应式原理

    # Study Notes 本博主会持续更新各种前端的技术,如果各位道友喜欢,可以关注.收藏.点赞下本博主的文章. Vue.js 源码剖析-响应式原理 响应式处理的入口 src/core/insta ...

  7. 【2019 前端进阶之路】深入 Vue 响应式原理,活捉一个 MVVM

    作者:江三疯,专注前端开发.欢迎关注公众号前端发动机,第一时间获得作者文章推送,还有各类前端优质文章,致力于成为推动前端成长的引擎. 前言 作为 Vue 面试中的必考题之一,Vue 的响应式原理,想必 ...

  8. 深入浅出Vue响应式原理

    前言 Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.这使得状态管理非常简单直接,不过理解其工作原理同样重要,这 ...

  9. 深入浅出 Vue 响应式原理!

    作者 | 浪里行舟 责编 | 胡巍巍 Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.这使得状态管理非常简单直接 ...

最新文章

  1. C++范围解析运算符::
  2. 世界上最长寿的泡泡:加点东西,生命延长到465天
  3. 三星s轻奢android+p,三星Galaxy S轻奢版5月21日将至:骁龙660处理器+安卓8.0
  4. LeetCode:Two Sum
  5. 复变函数与积分变换-手写笔记
  6. 计算机更新要联网吗,解决steam需要在线进行更新。请确认您的网络连接正常的方法...
  7. 如何在 20 分钟内给你的 K8s PaaS 上线一个新功能?
  8. 【机器学习基础】Python数据预处理:彻底理解标准化和归一化
  9. 获取Class类对象的三种方式
  10. Spring集成基础知识
  11. 脉冲神经元的膜电位释放方式,分为hard和soft两种
  12. ButterKnife 8.6.0 使用
  13. 翻译:web制作、开发人员需知的Web缓存知识
  14. Topaz DeNoise AI 2.3.6汉化版|AI智能降噪插件Topaz DeNoise AI 2.3.6中文版
  15. 房产证和不动产权证有什么区别?
  16. 用于打开文件的php函数是,PHP文件操作(PHP读写文件)
  17. Web:仿苹果官网首页HTML和CSS
  18. SRS 流媒体服务器对http-flv流进行配置
  19. 0x5003eaed指令引用的0x00000000内存。该内存不能为read
  20. 阿里旺铺运营怎样做到低价引流

热门文章

  1. SQL性能第2篇:查询分析和访问路径制定
  2. SpringCloud 微服务网关Gateway 动态路由配置
  3. Java企业面试算法新得体会之6大数据和空间限制问题6问
  4. Lucene 和 Elastic
  5. errgroup 分析
  6. volatile是怎么保证可见性和有序性的,为什么无法保证原子性
  7. php垃圾回收算法分代,PHP的垃圾回收机制代码实例讲解
  8. Mybatis 框架源码解读(详细流程图+时序图)
  9. java面向对象数组_Java面向对象的构造器与数组对象
  10. python设计模式22-模板模式