Vue的响应式系统
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;
})
Vue的响应式系统的更多相关文章
- 你是如何理解Vue的响应式系统的
1.响应式系统简述: 任何一个 Vue Component 都有一个与之对应的 Watcher 实例. Vue 的 data 上的属性会被添加 getter 和 setter 属性. 当 Vue Co ...
- 前端必读:Vue响应式系统大PK
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://www.sitepoint.com/vue-3-reactivity-system ...
- vue原理探索--响应式系统
Vue.js 是一款 MVVM 框架,数据模型仅仅是普通的 JavaScript 对象,但是对这些对象进行操作时,却能影响对应视图,它的核心实现就是「响应式系统」. 首先看一下 Object.defi ...
- Vue 及框架响应式系统原理
个人bolg地址 全局概览 Vue运行内部运行机制 总览图: 初始化及挂载 在 new Vue()之后. Vue 会调用 _init 函数进行初始化,也就是这里的 init 过程,它会初始化生命周期. ...
- 【js】vue 2.5.1 源码学习 (七) 初始化之 initState 响应式系统基本思路
大体思路(六) 本节内容: 一.生命周期的钩子函数的实现 ==> callHook(vm , 'beforeCreate') beforeCreate 实例创建之后 事件数据还未创建 二.初始化 ...
- 前端必读:Vue响应式系统大PK(下)
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://www.sitepoint.com/vue-3-reactivity-system ...
- vue深入响应式原理
vue深入响应式原理 深入响应式原理 — Vue.jshttps://cn.vuejs.org/v2/guide/reactivity.html 注意:这里说的响应式不是bootsharp那种前端UI ...
- Vue 数据响应式原理
Vue 数据响应式原理 Vue.js 的核心包括一套“响应式系统”.“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码.例如,视图渲染中使用了数据,数据改变后,视图也会自动更新. 举个简单 ...
- Vuejs - 深入浅出响应式系统
Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 Javascript 对象.而当你修改它们时,视图会进行更新.这使得状态管理非常简单直接,不过理解其工作原理同样非常重要,这样 ...
随机推荐
- Django连接多个数据库并实现读写分离
当一个项目当中有大量的数据的时候,你所有的IO操作都在一个数据库中操作,会造成项目的性能的降低.如果你能对项目中的数据进行读写分离的话,那么将大大提高你项目的性能.而Django自带的机制也对此提供了 ...
- Django模型层之单表操作
Django模型层之单表操作 一 .ORM简介 我们在使用Django框架开发web应用的过程中,不可避免地会涉及到数据的管理操作(如增.删.改.查),而一旦谈到数据的管理操作,就需要用到数据库管理软 ...
- ansible-playbook的简单传参方式
基本语法: ansible-playbook -v -i myhost test.yml -e "name=xiaoming" // -v 是看运行细节.要更细节 ...
- lmir 随笔
近期需要研究一些特征工程的工作,就打算把微软之前公布出来的特征都复现一遍,今天遇到的特征是 LMIR, 其实也就是language model for information retrieval的简写 ...
- 状态机的Verilog写法
“硬件设计很讲究并行设计思想,虽然用Verilog描述的电路大都是并行实现的,但是对于实际的工程应用,往往需要让硬件来实现一些具有一定顺序的工作,这就要用到状态机思想.什么是状态机呢?简单的说,就是通 ...
- Authorization源码解析
1.首先调用 Subject.isPermitted*/hasRole* 接口,其会委托给SecurityManager.SecurityManager 接着会委托给 Authorizer: Auth ...
- CentOS7 修改Jenkins以root用户运行
修改Jenkins以root用户运行,这在正式环境中是不可取的,但在自己的测试环境中就无所谓了啦,怎么方便怎么来. 1. 修改Jenkins配置文件1.1 修改$JENKINS_USER打开jenki ...
- 全栈项目|小书架|服务器开发-NodeJS 中使用 Sequelize 操作 MySQL数据库
安装 官网:https://sequelize.org/v5/manual/getting-started.html 安装sequelize及数据库连接驱动 npm install --save se ...
- 快速生成html文本文档——typora
下载地址:https://www.typora.io/#windows 一.工具界面: 二.使用工具编辑: 三.导出为html: 四.打开html查看: Markdown语法教程:https://ww ...
- 【开发工具】- Xshell工具的下载和安装
下载地址:https://www.netsarang.com/zh/free-for-home-school/ Xshell 是一个强大的安全终端模拟软件,它支持SSH1, SSH2, 以及Micro ...