阅完此文,Vue响应式不再话下

2024-02-22 03:10

本文主要是介绍阅完此文,Vue响应式不再话下,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

vue的双向数据绑定,众所周知是基于Object.defineProperty这个在浏览器的特性api来实现的。但是怎么从视图到数据,数据到视图,这个整个大过程,对于很多盆友来说,还有点不是很清楚。

这篇文章,将会特别轻松的换个角度让你明白整个过程。just do it !!! ????????????


Vue的响应式系统

我们第一次使用Vue的时候,会感觉有些神奇,举个例子:

<div id="app">
<div>价格:¥{{price}}</div>
<div>总价:¥{{price*quantity}}</div>
<div>折扣后:¥{{totlePriceWithTax}}</div>
</div>
<script>
var vm=new Vue({
el:'#app',
data:(){
price:5.00,//单价
quantity:2//数量
},
computed:{
totlePriceWithTax(){
return this.price*this.quantity*1.03
}
}
})
</script>

我们使用vue的时候,不知道它内部做了什么。它都能知道price这个字段的值是否发生过变化,如果发生了变化,他会做如下几件事:

  • 更新页面显示的price的值

  • 重新计算总价的乘法表达式并且更新显示结果

  • 重新调用totlePriceWithTax函数,并且更新显示

这儿,咱们就有一个疑问,vue怎么就知道price变化了之后,都要更新哪些值呢?为什么,每次一变化,就要更新呢?如何跟踪的呢?

JavaScript正常的运行方式

我们把这个例子整理成我们正常的JavaScript程序来看看:

let price=5;
let quantity=2;
let total=price*quantity;//计算总价
pice=20;//price字段发生变更之后
console.log(`变化之后的总价:${total}`);

这个会输出打印多少呢?因为我这儿没有使用Vue,很明显,这儿会输出10:

>> 变化之后的总价:10

在咱们经常使用的Vue中,我们想要在price或者quantity这两个字段更新时,和它有关的表达式也会更新,和它有关的函数也会执行。

>> 变化之后的总价:40

但是,javascript是过程性的,不是响应式的,所以这个代码在实际运行的时候是不行的。为了让total在price更新的时候,它也跟着更新,我们必须让JavaScript语言具备不同的运行方式。

问题

那么我们现在就遇到了一个问题,怎么样,才能在price字段或者quantity更新的时候,total也重新更新显示呢?

尝试一下

首先,我们需要明白price和totle的关联是:

let total=price*quantity;

那么,在price更新之后,需要重新得到新的total,就需要重新执行这个方法。那么就需要有一个地方把这个方法储存起来,在price变更的时候,重新运行储存起来的方法,这样total值就更新了。

那我们就来尝试一下,把函数记录下来,后面变更的时候,再次运行。

let price=5;
let quantity=2;
let total=0;
let target=null;

//记录函数
target=()=>{
total=price*quantity;
}
record();//后面讲解,记住这个我们后面想要运行的函数
target();//同时,我们执行一遍这个方法

record记录函数的实现就很简单了:

let storage=[];//这是要记录函数的地方,就是上面图中椭圆的那个东西


//记录方法的实现,这个时候的target就是我们要记录的方法
function record(){
storage.push(target)
}

这一步,将target储存了起来,这样我们后面就可以运行它。这个时候,我们就需要一个运行所有记录的内容的函数。那我们就来搞一哈:


function replay(){
storage.forEach((run)=>{
run();
})
}

这儿,我们遍历了所有记录的内容,并且每一个都执行。

这个时候,我们的代码就可以更改一下:

let price=5;
let quantity=2;
let total=0;
let target=null;

function record(){
storage.push(target)
}

function replay(){
storage.forEach((run)=>{
run();
})
}

target=()=>{
total=price*quantity;
}
record();
target();

console.log(total)// 10
price=20;
replay();
console.log(total)//40

这样我们就实现了,一个记录的过程,但是这样没有一个很好地管理,我们能不能把记录这块的内容,维护成一个类,让这个类维护一个tagert列表,每次需要重新运行的时候,这个类都会得到通知。

年轻人,火力旺,说干就干。维护一个单独的Dep类,代码如下:

class Dep{
constructor(){
this.subscribers=[];//维护所有target的列表,在得到通知的时候,全部都会运行
}
depend(){
if(target&&!this.subscribers.includes(target)){
//只有target有方法,并且没有被记录过
this.subscribers.push(target);
}
}
notify(){
this.subscribers.forEach((sub)=>{
sub();
})
}
}

在这个类中,我们不再使用storage,使用subscribers这个字符来记录target函数的内容,也不再使用record,使用depend,也用了notify替代了replay,这个时候要运行,就只需要:

const dep=new Dep();

let price=5;
let quantity=2;
let total=0;
let target=null;

target=()=>{
total=price*quantity;
}
dep.depend();//记录到subscribers中
target();

console.log(total)// 10
price=20;
dep.notify();//遍历执行所有target,分发内容
console.log(total)//40

这样,整体的过程就会好一点,但是还是会显得很冗余,如果能过把匿名函数创建,观察,更新的这些行为封装起来,那就更好了。

年轻人,总是冲动,咱们说干就干。把原来的创建和记录:

target=()=>{
total=price*quantity;
}
dep.depend();//记录到subscribers中
target();

这块内容封装起来,咱们给封装起来的函数起名叫做watcher,封装起来之后,我们就只需要这样调用:

watcher(()=>{
total=price*quantity
})

那我们在实现watcher的时候,这么做就好:

function watcher(myFunc){
target=myFunc;//传入的函数赋值
dep.depend();//收集
target();//执行一下
target=null;//重置
}

这儿,咱们看到watcher函数接受了一个变量myFunc,这个myFunc后面接收的是匿名函数,然后赋值给target属性,调用dep.depend(),将以订阅者的形式添加target到记录的地方,然后调用target,并且重置。

现在结合上面的代码咱们尝试一下这个代码:

price=20;
console.log(total);
dep.notify();
console.log(total);

这里面有一个问题,就是target为什么要设置成全局变量,而不是将其传递给需要的函数。咱们后面会细聊。

现在我们有一个Dep类了,但是我们整整想要实现的情况是,每一个变量都有响应的地方记录它关联的变更,每个变量都有自己的Dep。这个可咋整?

年轻人,不怕事,说干就干。咱们首先把所有的变量放到一起:

let data={
price:5,
quantity:2
}

现在我们假设每一个属性(price和quantity)都有自己内部的Dep类。

当我们运行watcher这个函数的时候:

wacther(()=>{
total=data.price*data.quantity
})

因为我们是使用到了data.price的值,那么我们希望price属性的Dep类可以将使用它的匿名函数(储存在target上)放在订阅数组中,记录下来(通过调用dep.depend())。同时data.quantity这个变量也被访问了,所以也希望能够被记录下来,放在对应的订阅数组中:

如果这个时候还有其他的地方也在使用data.price,我们也希望可以把对应的匿名函数放到Dep类中记录下来。

那么,什么时候会调用price对应的Dep中的notify呢?在price赋值,值发生改变的时候。我们最后希望发生的效果是:

>> total
10
>> price=20
>> total
40

我们希望,当数据被访问的时候,能够把对应的target匿名函数储存到订阅数组中,当属性变更的时候,能够运行对应的储存在订阅数组中的匿名函数。

解决方案

这个一眼看过去,访问时,改变时。脑海中直接就出来了Object.defineProperty,这个允许我们为属性定义getter和setter函数。在展示如何和Dep结合的之前,先看下用法:

let data={price:5,quantity:2};
Object.defineProperty(data,'price',{
get(){
console.log('被访问')
},
set(newVal){
console.log('被修改')
}
});
data.price;//输出:被访问
data.price=20;//输出:被修改

这里,我们并没有实际的修改get和set的值,因为功能被覆盖了。现在,我们希望get的时候能够返回一个值,set的时候能够更新值。所以我们先添加一个变量internalValue来储存当前的price的值。

let data={price:5,quantity:2};

let internalValue=data.price;//初始值

Object.defineProperty(data,'price',{
get(){
console.log('被访问');
return internalValue
},
set(newVal){
console.log('被修改');
internalValue=newVal
}
});
total=data.price*data.quantity;//调用get
data.price=20;//调用set

这样我们就可以把所有我们想要的监听的数据,全部给处理一下:

let data={price:5,quantity:2};

Object.keys(data).forEach((key)=>{
let internalValue=data[key];//初始值

Object.defineProperty(data,key,{
get(){
console.log('被访问');
return internalValue
},
set(newVal){
console.log('被修改');
internalValue=newVal
}
});
})

total=data.price*data.quantity;//调用get
data.price=20;//调用set

这样所有的数据都变了可监听的了。

把他们结合起来

total=data.price*data.quantity

当这个代码运行的时候,会触发price属性对应的get方法,我们希望price的Dep可以记住这个对应的匿名函数(target)。通过这个方式,如果发生改变,触发了set,那么就能够调用这个属性对应的储存起来的匿名函数。

  • Get—记住匿名函数,当值发生变化的时候重新运行。

  • Set—运行保存的匿名函数,对应匿名函数绑定的值就会发生变化

切换到Dep class的模式:

  • price被访问时—调用dep.depend保存当前target

  • price被改变时—调用price的dep.notify,重新运行所有的target

最后,我们就把这个结合起来,年轻人,不要磨磨蹭蹭,突突两下就可以了:

let data={price:5,quantity:2};
let target=null;


class Dep{
constructor(){
this.subscribers=[];//维护所有target的列表,在得到通知的时候,全部都会运行
}
depend(){
if(target&&!this.subscribers.includes(target)){
//只有target有方法,并且没有被记录过
this.subscribers.push(target);
}
}
notify(){
this.subscribers.forEach((sub)=>{
sub();
})
}
}


Object.keys(data).forEach((key)=>{
let internalValue=data[key];//初始值

Object.defineProperty(data,key,{
get(){
console.log('被访问');
dep.depend();//添加对应的匿名函数target
return internalValue
},
set(newVal){
console.log('被修改');
internalValue=newVal;
dep.notify();//触发对应的储存的函数
}
});
})

function watcher(myFunc){
target=myFunc;//传入的函数赋值
target();//执行一下
target=null;//重置
}
watcher(()=>{
data.total=data.price*data.quantity;
})

这就结合了这一块的东西,price和quantity两个属性变成了响应式的情况,可以下来试一下。

直接上架构图:

最后,Vue2中还有很多东西,Vue3也出来了,我们这块出了对应的课程。年轻人不要犹犹豫豫。机会和成长总在犹豫的时候就溜走了。

在这样一个信息爆炸、知识唾手可得的时代,年轻人一定要做个明白人,懂得筛选和判断优质内容。

你可能经常会领取到海量前端资料包,收藏起来就再也没看过。

但今天,我们想给你点真正有品质的内容——【你不知道的Vue.js 性能优化】

  • 本次专题课深度讲解 Vue.js 性能优化,以及 Vue3.0 那些值得关注的新特性。在高级前端岗位面试中,性能优化是一个必问的知识点,本课程通过对 Vue 面试核心知识点的拆解,带你解锁你可能不知道的 Vue.js 性能优化,直达大厂offer!

它将带你学到什么?

1.Vue首屏优化实践

  • 大厂面试问Vue项目优化时的各种讲解

  • 核心工程化知识点讲解

  • 不同的核心优化方案剖析

  • 常考Vue知识点串讲

2.面试常问的Vue双向数据深度解析

  • 修正对于Object.defineProperty的错误理解

  • Vue2中双向数据绑定为什么性能不好?

  • 数组的双向数据绑定怎么处理的

3.深度对比 Vue2 & 3,助你直达offer

  • 浅尝Vue3的使用

  • Vue3的新特性解析

  • Vue3核心双向数据绑定的实现解析

  • 深度对比Vue2,助你直达offer

扫描下图二维码,原价98元的优质好课 限时1元

???? ????彩蛋 ???? ????

购课之后,还将赠送价值196元的【 Webpack 从入门到精通 】全系列教程。

以下是赠送的详细内容

我觉得你们应该点 「在看」 

这篇关于阅完此文,Vue响应式不再话下的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/733966

相关文章

vite搭建vue3项目的搭建步骤

《vite搭建vue3项目的搭建步骤》本文主要介绍了vite搭建vue3项目的搭建步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1.确保Nodejs环境2.使用vite-cli工具3.进入项目安装依赖1.确保Nodejs环境

Nginx搭建前端本地预览环境的完整步骤教学

《Nginx搭建前端本地预览环境的完整步骤教学》这篇文章主要为大家详细介绍了Nginx搭建前端本地预览环境的完整步骤教学,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录项目目录结构核心配置文件:nginx.conf脚本化操作:nginx.shnpm 脚本集成总结:对前端的意义很多

前端缓存策略的自解方案全解析

《前端缓存策略的自解方案全解析》缓存从来都是前端的一个痛点,很多前端搞不清楚缓存到底是何物,:本文主要介绍前端缓存的自解方案,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、为什么“清缓存”成了技术圈的梗二、先给缓存“把个脉”:浏览器到底缓存了谁?三、设计思路:把“发版”做成“自愈”四、代码

通过React实现页面的无限滚动效果

《通过React实现页面的无限滚动效果》今天我们来聊聊无限滚动这个现代Web开发中不可或缺的技术,无论你是刷微博、逛知乎还是看脚本,无限滚动都已经渗透到我们日常的浏览体验中,那么,如何优雅地实现它呢?... 目录1. 早期的解决方案2. 交叉观察者:IntersectionObserver2.1 Inter

Vue3视频播放组件 vue3-video-play使用方式

《Vue3视频播放组件vue3-video-play使用方式》vue3-video-play是Vue3的视频播放组件,基于原生video标签开发,支持MP4和HLS流,提供全局/局部引入方式,可监听... 目录一、安装二、全局引入三、局部引入四、基本使用五、事件监听六、播放 HLS 流七、更多功能总结在 v

JS纯前端实现浏览器语音播报、朗读功能的完整代码

《JS纯前端实现浏览器语音播报、朗读功能的完整代码》在现代互联网的发展中,语音技术正逐渐成为改变用户体验的重要一环,下面:本文主要介绍JS纯前端实现浏览器语音播报、朗读功能的相关资料,文中通过代码... 目录一、朗读单条文本:① 语音自选参数,按钮控制语音:② 效果图:二、朗读多条文本:① 语音有默认值:②

vue监听属性watch的用法及使用场景详解

《vue监听属性watch的用法及使用场景详解》watch是vue中常用的监听器,它主要用于侦听数据的变化,在数据发生变化的时候执行一些操作,:本文主要介绍vue监听属性watch的用法及使用场景... 目录1. 监听属性 watch2. 常规用法3. 监听对象和route变化4. 使用场景附Watch 的

前端导出Excel文件出现乱码或文件损坏问题的解决办法

《前端导出Excel文件出现乱码或文件损坏问题的解决办法》在现代网页应用程序中,前端有时需要与后端进行数据交互,包括下载文件,:本文主要介绍前端导出Excel文件出现乱码或文件损坏问题的解决办法,... 目录1. 检查后端返回的数据格式2. 前端正确处理二进制数据方案 1:直接下载(推荐)方案 2:手动构造

Vue实现路由守卫的示例代码

《Vue实现路由守卫的示例代码》Vue路由守卫是控制页面导航的钩子函数,主要用于鉴权、数据预加载等场景,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录一、概念二、类型三、实战一、概念路由守卫(Navigation Guards)本质上就是 在路

uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)

《uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)》在uni-app开发中,文件上传和图片处理是很常见的需求,但也经常会遇到各种问题,下面:本文主要介绍uni-app小程序项目中实... 目录方式一:使用<canvas>实现图片压缩(推荐,兼容性好)示例代码(小程序平台):方式二:使用uni