热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

vue.js响应式原理解析与实现

这篇文章主要为大家详细介绍了vue.js响应式原理解析与实现,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

从很久之前就已经接触过了angularjs了,当时就已经了解到,angularjs是通过脏检查来实现数据监测以及页面更新渲染。之后,再接触了vue.js,当时也一度很好奇vue.js是如何监测数据更新并且重新渲染页面。今天,就我们就来一步步解析vue.js响应式的原理,并且来实现一个简单的demo。

首先,先让我们来了解一些基础知识。

基础知识

Object.defineProperty

es5新增了Object.defineProperty这个api,它可以允许我们为对象的属性来设定getter和setter,从而我们可以劫持用户对对象属性的取值和赋值。比如以下代码:

const obj = {
};

let val = 'cjg';
Object.defineProperty(obj, 'name', {
 get() {
 console.log('劫持了你的取值操作啦');
 return val;
 },
 set(newVal) {
 console.log('劫持了你的赋值操作啦');
 val = newVal;
 }
});

console.log(obj.name);
obj.name = 'cwc';
console.log(obj.name);

我们通过Object.defineProperty劫持了obj[name]的取值和赋值操作,因此我们就可以在这里做一些手脚啦,比如说,我们可以在obj[name]被赋值的时候触发更新页面操作。

发布订阅模式

发布订阅模式是设计模式中比较常见的一种,其中有两个角色:发布者和订阅者。多个订阅者可以向同一发布者订阅一个事件,当事件发生的时候,发布者通知所有订阅该事件的订阅者。我们来看一个例子了解下。

class Dep {
 constructor() {
 this.subs = [];
 }
 // 增加订阅者
 addSub(sub) {
 if (this.subs.indexOf(sub) <0) {
 this.subs.push(sub);
 }
 }
 // 通知订阅者
 notify() {
 this.subs.forEach((sub) => {
 sub.update();
 })
 }
}

const dep = new Dep();

const sub = {
 update() {
 console.log('sub1 update')
 }
}

const sub1 = {
 update() {
 console.log('sub2 update');
 }
}

dep.addSub(sub);
dep.addSub(sub1);

dep.notify(); // 通知订阅者事件发生,触发他们的更新函数

动手实践

我们了解了Object.defineProperty和发布订阅者模式后,我们不难可以想到,vue.js是基于以上两者来实现数据监听的。

1、vue.js首先通过Object.defineProperty来对要监听的数据进行getter和setter劫持,当数据的属性被赋值/取值的时候,vue.js就可以察觉到并做相应的处理。
2、通过订阅发布模式,我们可以为对象的每个属性都创建一个发布者,当有其他订阅者依赖于这个属性的时候,则将订阅者加入到发布者的队列中。利用Object.defineProperty的数据劫持,在属性的setter调用的时候,该属性的发布者通知所有订阅者更新内容。

接下来,我们来动手实现(详情可以看注释):

class Observer {
 constructor(data) {
 // 如果不是对象,则返回
 if (!data || typeof data !== 'object') {
 return;
 }
 this.data = data;
 this.walk();
 }

 // 对传入的数据进行数据劫持
 walk() {
 for (let key in this.data) {
 this.defineReactive(this.data, key, this.data[key]);
 }
 }
 // 创建当前属性的一个发布实例,使用Object.defineProperty来对当前属性进行数据劫持。
 defineReactive(obj, key, val) {
 // 创建当前属性的发布者
 const dep = new Dep();
 /*
 * 递归对子属性的值进行数据劫持,比如说对以下数据
 * let data = {
 * name: 'cjg',
 * obj: {
 * name: 'zht',
 * age: 22,
 * obj: {
 * name: 'cjg',
 * age: 22,
 * }
 * },
 * };
 * 我们先对data最外层的name和obj进行数据劫持,之后再对obj对象的子属性obj.name,obj.age, obj.obj进行数据劫持,层层递归下去,直到所有的数据都完成了数据劫持工作。
 */
 new Observer(val);
 Object.defineProperty(obj, key, {
 get() {
 // 若当前有对该属性的依赖项,则将其加入到发布者的订阅者队列里
 if (Dep.target) {
  dep.addSub(Dep.target);
 }
 return val;
 },
 set(newVal) {
 if (val === newVal) {
  return;
 }
 val = newVal;
 new Observer(newVal);
 dep.notify();
 }
 })
 }
}

// 发布者,将依赖该属性的watcher都加入subs数组,当该属性改变的时候,则调用所有依赖该属性的watcher的更新函数,触发更新。
class Dep {
 constructor() {
 this.subs = [];
 }

 addSub(sub) {
 if (this.subs.indexOf(sub) <0) {
 this.subs.push(sub);
 }
 }

 notify() {
 this.subs.forEach((sub) => {
 sub.update();
 })
 }
}

Dep.target = null;

// 观察者
class Watcher {
 /**
 *Creates an instance of Watcher.
 * @param {*} vm
 * @param {*} keys
 * @param {*} updateCb
 * @memberof Watcher
 */
 constructor(vm, keys, updateCb) {
 this.vm = vm;
 this.keys = keys;
 this.updateCb = updateCb;
 this.value = null;
 this.get();
 }

 // 根据vm和keys获取到最新的观察值
 get() {
 Dep.target = this;
 const keys = this.keys.split('.');
 let value = this.vm;
 keys.forEach(_key => {
 value = value[_key];
 });
 this.value = value;
 Dep.target = null;
 return this.value;
 }

 update() {
 const oldValue = this.value;
 const newValue = this.get();
 if (oldValue !== newValue) {
 this.updateCb(oldValue, newValue);
 }
 }
}

let data = {
 name: 'cjg',
 obj: {
 name: 'zht',
 },
};

new Observer(data);
// 监听data对象的name属性,当data.name发现变化的时候,触发cb函数
new Watcher(data, 'name', (oldValue, newValue) => {
 console.log(oldValue, newValue);
})

data.name = 'zht';

// 监听data对象的obj.name属性,当data.obj.name发现变化的时候,触发cb函数
new Watcher(data, 'obj.name', (oldValue, newValue) => {
 console.log(oldValue, newValue);
})

data.obj.name = 'cwc';
data.obj.name = 'dmh';

结语

这样,一个简单的响应式数据监听就完成了。当然,这个也只是一个简单的demo,来说明vue.js响应式的原理,真实的vue.js源码会更加复杂,因为加了很多其他逻辑。

接下来我可能会将其与html联系起来,实现v-model、computed和{{}}语法。代码地址 有兴趣的欢迎来一起研究探讨下。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 认识Vue关于Vue的描述有不少,不外乎都会拿来与Angular和React对比,同样头顶MVVM双向数据驱动设计模式光环的Angular自然被对比的最多,但到目前为止,Angul ... [详细]
  • 【Vue基础】监听属性watch
    Vue监听属性是watch,我们可以通过watch来响应数据的变化。代码示例: ... [详细]
  • <!DOCTYPEhtml><htmllang=en><head><metacharset=UT ... [详细]
  • vue.js如何实现数据的双向绑定呢?与angular不同。vue利用的是es5的defineproperty特性。1.一个小例子 ... [详细]
  • Final关键字的含义及用法详解
    本文详细介绍了Java中final关键字的含义和用法。final关键字可以修饰非抽象类、非抽象类成员方法和变量。final类不能被继承,final类中的方法默认是final的。final方法不能被子类的方法覆盖,但可以被继承。final成员变量表示常量,只能被赋值一次,赋值后值不再改变。文章还讨论了final类和final方法的应用场景,以及使用final方法的两个原因:锁定方法防止修改和提高执行效率。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了求解gcdexgcd斐蜀定理的迭代法和递归法,并解释了exgcd的概念和应用。exgcd是指对于不完全为0的非负整数a和b,gcd(a,b)表示a和b的最大公约数,必然存在整数对x和y,使得gcd(a,b)=ax+by。此外,本文还给出了相应的代码示例。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 电销机器人作为一种人工智能技术载体,可以帮助企业提升电销效率并节省人工成本。然而,电销机器人市场缺乏统一的市场准入标准,产品品质良莠不齐。创业者在代理或购买电销机器人时应注意谨防用录音冒充真人语音通话以及宣传技术与实际效果不符的情况。选择电销机器人时需要考察公司资质和产品品质,尤其要关注语音识别率。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • 我尝试使用Vue.js在Laravel中实现imageupload吗?但是,我不知道为什么图像 ... [详细]
author-avatar
无敌BUG
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有