You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
观察上面代码,我们通过改变数据,来自动更新视图。当我们有多个地方引用这个 name 时,视图都会自动更新。
<template>
<divid="app">
<span @click="setName">{{ name }}</span>
<span>{{ name }}</span>
<span>{{ name }}</span>
<span>{{ name }}</span>
</div>
</template>
当我们使用目前主流的前端框架 Vue.js 和 React 开发业务时,只需关注页面数据如何变化,因为数据变化后,视图也会自动更新,这让我们从繁杂的 DOM 操作中解脱出来,提高开发效率。
// 实现观察者类classObserverimplementsIObserver{constructor(privatename: string){}update(): void{console.log(`${this.name} has been notified.`);}}
2.4 测试代码
functionuseObserver(){constsubject: ISubject=newSubject();constLeo=newObserver("Leo");constRobin=newObserver("Robin");constPual=newObserver("Pual");subject.addObserver(Leo);subject.addObserver(Robin);subject.addObserver(Pual);subject.notify();subject.removeObserver(Pual);subject.notify();}useObserver();// [LOG]: "Leo has been notified." // [LOG]: "Robin has been notified." // [LOG]: "Pual has been notified." // [LOG]: "Leo has been notified." // [LOG]: "Robin has been notified."
letleo={},age=18;Object.defineProperty(leo,'age',{get(){// to do somethingconsole.log('监听到请求数据');returnage;},set(newAge){// to do somethingconsole.log('监听到修改数据');age=newAge>age ? age : newAge}})leo.age=20;// 监听到修改数据console.log(leo.age);// 监听到请求数据 // 18leo.age=10;// 监听到修改数据console.log(leo.age);// 监听到请求数据 // 10
访问 leo 对象的 age 属性,会通过 get 描述符处理,而修改 age 属性,则会通过 set 描述符处理。
classVue{constructor(options={}){this.$el=options.el;this.$data=options.data();this.$methods=options.methods;// [核心流程]将普通 data 对象转换为响应式对象newObserver(this.$data);if(this.$el){// [核心流程]将解析模板的内容newCompile(this.$el,this)}}}window.Vue=Vue;
提到“响应式”三个字,大家立刻想到啥?响应式布局?响应式编程?
从字面意思可以看出,具有“响应式”特征的事物会根据条件变化,使得目标自动作出对应变化。比如在“响应式布局”中,页面根据不同设备尺寸自动显示不同样式。
Vue.js 中的响应式也是一样,当数据发生变化后,使用到该数据的视图也会相应进行自动更新。
接下来我根据个人理解,和大家一起探索下 Vue.js 中的响应式原理,如有错误,欢迎指点😺~~
一、Vue.js 响应式的使用
现在有个很简单的需求,点击页面中 “leo” 文本后,文本内容修改为“你好,前端自习课”。
我们可以直接操作 DOM,来完成这个需求:
实现起来比较简单,当我们需要修改的数据有很多时(比如相同数据被多处引用),这样的操作将变得复杂。
既然说到 Vue.js,我们就来看看 Vue.js 怎么实现上面需求:
观察上面代码,我们通过改变数据,来自动更新视图。当我们有多个地方引用这个
name
时,视图都会自动更新。当我们使用目前主流的前端框架 Vue.js 和 React 开发业务时,只需关注页面数据如何变化,因为数据变化后,视图也会自动更新,这让我们从繁杂的 DOM 操作中解脱出来,提高开发效率。
二、回顾观察者模式
前面反复提到“通过改变数据,来自动更新视图”,换个说法就是“数据改变后,使用该数据的地方被动发生响应,更新视图”。
是不是有种熟悉的感觉?数据无需关注自身被多少对象引用,只需在数据变化时,通知到引用的对象即可,引用的对象作出响应。恩,有种观察者模式的味道?
1. 观察者模式流程
观察者模式表示一种“一对多”的关系,n 个观察者关注 1 个被观察者,被观察者可以主动通知所有观察者。接下图:
在这张图中,粉丝想及时收到“前端自习课”最新文章,只需关注即可,“前端自习课”有新文章,会主动推送给每个粉丝。该过程中,“前端自习课”是被观察者,每位“粉丝”是观察者。
2. 观察者模式核心
观察者模式核心组成包括:n 个观察者和 1 个被观察者。这里实现一个简单观察者模式:
2.1 定义接口
2.2 实现被观察者类
2.3 实现观察者类
2.4 测试代码
三、回顾 Object.defineProperty()
Vue.js 的数据响应式原理是基于 JS 标准内置对象方法
Object.defineProperty()
方法来实现,该方法不兼容 IE8 和 FF22 及以下版本浏览器,这也是为什么 Vue.js 只能在这些版本之上的浏览器中才能运行的原因。理解
Object.defineProperty()
对我们理解 Vue.js 响应式原理非常重要。1. 概念介绍
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。语法如下:
obj
:要定义属性的源对象;prop
:要定义或修改的属性名称或 Symbol;descriptor
:要定义或修改的属性描述符,包括configurable
、enumerable
、value
、writable
、get
、set
,具体的可以去参阅文档;修改后的源对象。
举个简单🌰例子:
2. 实现 getter/setter
我们知道
Object.defineProperty()
方法第三个参数是属性描述符(descriptor
),支持设置get
和set
描述符:get
描述符:当访问该属性时,会调用此函数,默认值为undefined
;set
描述符:当修改该属性时,会调用此函数,默认值为undefined
。这两个操作符为我们提供拦截数据进行操作的可能性,修改前面示例,添加 getter/setter 方法:
访问
leo
对象的age
属性,会通过get
描述符处理,而修改age
属性,则会通过set
描述符处理。四、实现简单的数据响应式
通过前面两个小节,我们复习了“观察者模式”和“
Object.defineProperty()
” 方法,这两个知识点在 Vue.js 响应式原理中非常重要。接下来我们来实现一个很简单的数据响应式变化,需求如下:点击“更新数据”按钮,文本更新。
接下来我们将实现三个类:
Dep
被观察者类,用来生成被观察者;Watcher
观察者类,用来生成观察者;Observer
类,将普通数据转换为响应式数据,从而实现响应式对象。用一张图来描述三者之间关系,现在看不懂没关系,这小节看完可以再回顾这张图:
1. 实现精简观察者模式
这里参照前面复习“观察者模式”的示例,做下精简:
Vue.js 响应式原理中,观察者模式起到非常重要的作用。其中:
Dep
被观察者类,提供用来收集观察者(addSub
)方法和通知观察者(notify
)方法;Watcher
观察者类,实例化时支持传入回调(cb
)方法,并提供更新(update
)方法;2. 实现生成响应式的类
这一步需要实现
Observer
类,核心是通过Object.defineProperty()
方法为对象的每个属性设置 getter/setter,目的是将普通数据转换为响应式数据,从而实现响应式对象。这里以最简单的单层对象为例(下一节会介绍深层对象),如:
接下来实现
Observer
类:上面代码的核心是
defineReactive
方法,它遍历原始对象中每个属性,为每个属性实例化一个被观察者(Dep
),然后分别调用Object.defineProperty()
方法,为每个属性添加 getter/setter。Watcher
创建一个观察者,并执行被观察者的addSub()
方法添加一个观察者;notify()
方法通知所有观察者,执行观察者update()
方法。3. 测试代码
为了方便观察数据变化,我们为“更新数据”按钮绑定点击事件来修改数据:
测试代码如下:
测试代码中,核心在于通过实例化
Observer
,将测试数据转换为响应式数据,然后模拟数据变化,来观察视图变化。每次点击“更新数据”按钮,在控制台中都能看到“数据发生变化!”的提示,说明我们已经能通过 setter 观察到数据的变化情况。
当然,你还可以在控制台手动修改
initData
对象中的text
属性,来体验响应式变化~~到这里,我们实现了非常简单的数据响应式变化,当然 Vue.js 肯定没有这么简单,这个先理解,下一节看 Vue.js 响应式原理,思路就会清晰很多。
可以再回顾下这张图,对整个过程会更清晰:
五、实现简单 Vue.js 响应式
这里大家可以再回顾下下面这张官网经典的图,思考下前面讲的示例。
(图片来自:https://cn.vuejs.org/v2/guide/reactivity.html)
上一节实现了简单的数据响应式,接下来继续通过完善该示例,实现一个简单的 Vue.js 响应式,测试代码如下:
是不是很有内味了,下面是我们最终实现后项目目录:
知道每一个文件功能以后,接下来将每一步串联起来。
1. 实现入口文件
我们首先实现入口文件,包括
index.html
/index.js
2 个简单文件,用来方便接下来的测试。1.1 index.html
1.2 index.js
2. 实现核心入口 vue.js
vue.js
文件是我们实现的整个响应式的入口文件,暴露一个Vue
类,并挂载全局。Vue
类入参为一个配置项option
,使用起来跟 Vue.js 一样,包括$el
挂载点、$data
数据对象和$methods
方法列表(本文不详细介绍)。通过实例化
Oberser
类,将普通 data 对象转换为响应式对象,然后判断是否传入el
参数,存在时,则实例化Compile
类,解析模版内容。总结下
Vue
这个类工作流程 :3. 实现 observer.js
observer.js 文件实现了
Observer
类,用来将普通对象转换为响应式对象:相比较第四节实现的
Observer
类,这里做了调整:walk
核心方法,用来遍历对象每个属性,分别调用数据劫持方法(defineReactive()
);defineReactive()
的 getter 中,判断Dep.target
存在才添加观察者,下一节会详细介绍Dep.target
;defineReactive()
的 setter 中,判断当前新值(newValue
)是否为对象,如果是,则直接调用this.walk()
方法将当前对象再次转为响应式对象,处理深层对象。通过改善后的
Observer
类,我们就可以实现将单层或深层嵌套的普通对象转换为响应式对象。4. 实现 watcher.js
这里实现了
Dep
被观察者类(依赖收集者)和Watcher
观察者类。相比较第四节实现的
Watcher
类,这里做了调整:Dep.target
值操作;oldValue
变量,保存变化的数据作为旧值,后续作为判断是否更新的依据;update()
方法中,增加当前操作对象key
对应值的新旧值比较,如果不同,才执行回调。Dep.target
是当前全局唯一的订阅者,因为同一时间只允许一个订阅者被处理。target
指当前正在处理的目标订阅者,当前订阅者处理完就赋值为null
。这里Dep.target
会在defineReactive()
的 getter 中使用到。通过改善后的
Watcher
类,我们操作当前操作对象key
对应值的时候,可以在数据有变化的情况才执行回调,减少资源浪费。4. 实现 compile.js
compile.js 实现了 Vue.js 的模版编译,如将 HTML 中的
{{text}}
模版转换为具体变量的值。compile.js 介绍内容较多,考虑到篇幅问题,并且本文核心介绍响应式原理,所以这里就暂时不介绍 compile.js 的实现,在学习的朋友可以到我 Github 上下载该文件直接下载使用即可,地址:
https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Gist/Vue/leo-vue-reactive/compile.js
5. 测试代码
到这里,我们已经将第四节的 demo 改造成简易版 Vue.js 响应式,接下来打开 index.html 看看效果:
当 index.js 中执行到:
页面便发生更新,页面显示的文本内容从“你好,前端自习课”更新成“我们必须经常保持旧的记忆和新的希望。”。
到这里,我们的简易版 Vue.js 响应式原理实现好了,能跟着文章看到这里的朋友,给你点个大大的赞👍
六、总结
本文首先通过回顾观察者模式和
Object.defineProperty()
方法,介绍 Vue.js 响应式原理的核心知识点,然后带大家通过一个简单示例实现简单响应式,最后通过改造这个简单响应式的示例,实现一个简单 Vue.js 响应式原理的示例。相信看完本文的朋友,对 Vue.js 的响应式原理的理解会更深刻,希望大家理清思路,再好好回味下~
参考资料
The text was updated successfully, but these errors were encountered: