Vue.js中实现响应式的原理是什么
Vue.js中实现响应式的原理是什么,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
创新互联公司主要从事成都网站制作、成都做网站、网页设计、企业做网站、公司建网站等业务。立足成都服务洛龙,十余年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:18980820575
一、Vue.js 响应式的使用
现在有个很简单的需求,点击页面中 “leo” 文本后,文本内容修改为“你好,前端自习课”。
我们可以直接操作 DOM,来完成这个需求:
leo
const node = document.querySelector('#name') node.innerText = '你好,前端自习课';
实现起来比较简单,当我们需要修改的数据有很多时(比如相同数据被多处引用),这样的操作将变得复杂。
既然说到 Vue.js,我们就来看看 Vue.js 怎么实现上面需求:
{{ name }}
观察上面代码,我们通过改变数据,来自动更新视图。当我们有多个地方引用这个 name 时,视图都会自动更新。
{{ name }} {{ name }} {{ name }} {{ name }}
当我们使用目前主流的前端框架 Vue.js 和 React 开发业务时,只需关注页面数据如何变化,因为数据变化后,视图也会自动更新,这让我们从繁杂的 DOM 操作中解脱出来,提高开发效率。
二、回顾观察者模式
前面反复提到“通过改变数据,来自动更新视图”,换个说法就是“数据改变后,使用该数据的地方被动发生响应,更新视图”。
是不是有种熟悉的感觉?数据无需关注自身被多少对象引用,只需在数据变化时,通知到引用的对象即可,引用的对象作出响应。恩,有种观察者模式的味道?
关于观察者模式,可阅读我之前写的《图解设计模式之观察者模式(TypeScript)》。
1. 观察者模式流程
观察者模式表示一种“一对多”的关系,n 个观察者关注 1 个被观察者,被观察者可以主动通知所有观察者。接下图:
在这张图中,粉丝想及时收到“前端自习课”最新文章,只需关注即可,“前端自习课”有新文章,会主动推送给每个粉丝。该过程中,“前端自习课”是被观察者,每位“粉丝”是观察者。
2. 观察者模式核心
观察者模式核心组成包括:n 个观察者和 1 个被观察者。这里实现一个简单观察者模式:
2.1 定义接口
// 观察目标接口 interface ISubject { addObserver: (observer: Observer) => void; // 添加观察者 removeObserver: (observer: Observer) => void; // 移除观察者 notify: () => void; // 通知观察者 } // 观察者接口 interface IObserver { update: () => void; }
2.2 实现被观察者类
// 实现被观察者类 class Subject implements ISubject { private observers: IObserver[] = []; public addObserver(observer: IObserver): void { this.observers.push(observer); } public removeObserver(observer: IObserver): void { const idx: number = this.observers.indexOf(observer); ~idx && this.observers.splice(idx, 1); } public notify(): void { this.observers.forEach(observer => { observer.update(); }); } }
2.3 实现观察者类
// 实现观察者类 class Observer implements IObserver { constructor(private name: string) { } update(): void { console.log(`${this.name} has been notified.`); } }
2.4 测试代码
function useObserver(){ const subject: ISubject = new Subject(); const Leo = new Observer("Leo"); const Robin = new Observer("Robin"); const Pual = new Observer("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."
三、回顾 Object.defineProperty()
Vue.js 的数据响应式原理是基于 JS 标准内置对象方法 Object.defineProperty() 方法来实现,该方法不兼容 IE8 和 FF22 及以下版本浏览器,这也是为什么 Vue.js 只能在这些版本之上的浏览器中才能运行的原因。
理解 Object.defineProperty() 对我们理解 Vue.js 响应式原理非常重要。
Vue.js 3 使用 proxy 方法实现响应式,两者类似,我们只需搞懂Object.defineProperty() , proxy 也就差不多理解了。
1. 概念介绍
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
语法如下:
Object.defineProperty(obj, prop, descriptor)
入参说明:
obj :要定义属性的源对象;
prop :要定义或修改的属性名称或 Symbol;
descriptor :要定义或修改的属性描述符,包括 configurable、enumerable、value、writable、get、set,具体的可以去参阅文档;
出参说明:
修改后的源对象。
举个简单?例子:
const leo = {}; Object.defineProperty(leo, 'age', { value: 18, writable: true }) console.log(leo.age); // 18 leo.age = 22; console.log(leo.age); // 22
2. 实现 getter/setter
我们知道 Object.defineProperty() 方法第三个参数是属性描述符(descriptor),支持设置 get 和 set 描述符:
get 描述符:当访问该属性时,会调用此函数,默认值为 undefined ;
set 描述符:当修改该属性时,会调用此函数,默认值为 undefined 。
一旦对象拥有了 getter/setter 方法,我们可以简单将该对象称为响应式对象。
这两个操作符为我们提供拦截数据进行操作的可能性,修改前面示例,添加 getter/setter 方法:
let leo = {}, age = 18; Object.defineProperty(leo, 'age', { get(){ // to do something console.log('监听到请求数据'); return age; }, set(newAge){ // to do something console.log('监听到修改数据'); age = newAge > age ? age : newAge } }) leo.age = 20; // 监听到修改数据 console.log(leo.age); // 监听到请求数据 // 18 leo.age = 10; // 监听到修改数据 console.log(leo.age); // 监听到请求数据 // 10
访问 leo 对象的 age 属性,会通过 get 描述符处理,而修改 age 属性,则会通过 set 描述符处理。
四、实现简单的数据响应式
通过前面两个小节,我们复习了“观察者模式”和“Object.defineProperty()” 方法,这两个知识点在 Vue.js 响应式原理中非常重要。
接下来我们来实现一个很简单的数据响应式变化,需求如下:点击“更新数据”按钮,文本更新。
接下来我们将实现三个类:
Dep 被观察者类,用来生成被观察者;
Watcher 观察者类,用来生成观察者;
Observer 类,将普通数据转换为响应式数据,从而实现响应式对象。
用一张图来描述三者之间关系,现在看不懂没关系,这小节看完可以再回顾这张图:
1. 实现精简观察者模式
这里参照前面复习“观察者模式”的示例,做下精简:
// 实现被观察者类 class Dep { constructor() { this.subs = []; } addSub(watcher) { this.subs.push(watcher); } notify(data) { this.subs.forEach(sub => sub.update(data)); } } // 实现观察者类 class Watcher { constructor(cb) { this.cb = cb; } update(data) { this.cb(data); } }
Vue.js 响应式原理中,观察者模式起到非常重要的作用。其中:
Dep 被观察者类,提供用来收集观察者( addSub )方法和通知观察者( notify )方法;
Watcher 观察者类,实例化时支持传入回调( cb )方法,并提供更新( update )方法;
2. 实现生成响应式的类
这一步需要实现 Observer 类,核心是通过 Object.defineProperty() 方法为对象的每个属性设置 getter/setter,目的是将普通数据转换为响应式数据,从而实现响应式对象。
这里以最简单的单层对象为例(下一节会介绍深层对象),如:
let initData = { text: '你好,前端自习课', desc: '每日清晨,享受一篇前端优秀文章。' };
接下来实现 Observer 类:
// 实现响应式类(最简单单层的对象,暂不考虑深层对象) class Observer { constructor (node, data) { this.defineReactive(node, data) } // 实现数据劫持(核心方法) // 遍历 data 中所有的数据,都添加上 getter 和 setter 方法 defineReactive(vm, obj) { //每一个属性都重新定义get、set for(let key in obj){ let value = obj[key], dep = new Dep(); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { // 创建观察者 let watcher = new Watcher(v => vvm.innerText = v); dep.addSub(watcher); return value; }, set(newValue) { value = newValue; // 通知所有观察者 dep.notify(newValue); } }) } } }
上面代码的核心是 defineReactive 方法,它遍历原始对象中每个属性,为每个属性实例化一个被观察者(Dep),然后分别调用 Object.defineProperty() 方法,为每个属性添加 getter/setter。
访问数据时,getter 执行依赖收集(即添加观察者),通过实例化 Watcher 创建一个观察者,并执行被观察者的 addSub() 方法添加一个观察者;
修改数据时,setter 执行派发更新(即通知观察者),通过调用被观察者的 notify() 方法通知所有观察者,执行观察者 update() 方法。
3. 测试代码
为了方便观察数据变化,我们为“更新数据”按钮绑定点击事件来修改数据: