零基础入门Vue之画龙点睛——再探监测数据
追忆
上一节:零基础入门Vue之影分身之术——列表渲染&渲染原理浅析
虽然我深知,大佬告诉我”先学应用层在了解底层,以应用层去理解底层“,但Vue的数据如何检测的我不得不去学
否则,在写代码的时候,可能会出现我难以解释的bug
对此,本篇文章,将记录我对Vue检测数据的理解
对于Vue检测数据的实现,我打算由浅入深的去记录
- JavaScript实现数据监控
- 实现简单的数据监测(浅浅的响应式)
- Vue对哪些数据做了监测,哪些没有?
JavaScript的数据检测
Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。
熟悉JavaScript的人,应该在一个月黑风高的夜晚都了解过Object上的一个方法: Object.defineProperty()
而Vue大部分的数据监测都是依赖于这个方法来实现的
ps:本篇不会深度探讨这个静态方法的用法,仅仅对其get和set方法的用法讲解,为下一章节做铺垫
Object.defineProperty(obj, prop, descriptor)
- obj:要添加新属性的对象
- prop:要添加属性的名称(一般为字符串)
- descriptor:这个属性的相关描述(配置)
假设现在有一个对象如下:
let person = {
name:"张三"
};
现在,我试图这个person对象新增一个age属性,那么我可以这么干
Object.defineProperty(person, "age", {
value:18 //设置默认的值
});
此时输出person时可以看到
{name: '张三', age: 18}
题外话:关于为什么是点开后为什么age属性与其他属性颜色不一样,可以将enumerable:true,使它可迭代
但这真的是一个普通属性了吗?并不会,当我试图
person.age = 20
console.log(person.age); //18
似乎修改不了数据,因为少了一个配置项
Object.defineProperty(person, "age", {
value:18,
writable:true //配置为可修改
});
此时上述代码差不多等同于
person.age = 18; //假设age没定义过,重新给person追加一个属性
get
用作属性 getter 的函数,如果没有 getter 则为 undefined。当访问该属性时,将不带参地调用此函数,并将 this 设置为通过该属性访问的对象(因为可能存在继承关系,这可能不是定义该属性的对象)。返回值将被用作该属性的值。默认值为 undefined。
从上面MDN上的话来说,当我们要用到对象上的某个属性时会调用 getter,如果对象没有设置,则默认是undefined,当访问这个属性时,访问得到的结果将是getter返回的结果
我现在有一个需求:当age属性被读取时,就加1岁,并且输出变化后的值
在上述代码里面是完不成这个需求的,此时就需要用到get方法了,当读取时会自动调用get方法,我可以在那个里面进行数据的递增
注意:官网描述中,get不能与 value 或 writable 同时使用
- 定义实际年龄数据:_age
- 当读取person.age,get选择器返回_age,并且把_age递增
具体实现代码如下:
let age = 18; //定义一个实际的年龄数据
let person = { //准备好目标对象
name:"张三"
};
Object.defineProperty(person,"age",{
get(){
console.log("年龄即将递增一岁后:",age + 1);
return age,age++;
}
});
> person.age
年龄即将递增一岁后: 19
18
> person.age
VM1928:8 年龄即将递增一岁后: 20
19
综上所述,当有人要读取某个属性的时候,可以对这个属性值做了处理在返回,或者是调用函数通知其他的事件触发等等
set
用作属性 setter 的函数,如果没有 setter 则为 undefined。当该属性被赋值时,将调用此函数,并带有一个参数(要赋给该属性的值),并将 this 设置为通过该属性分配的对象。默认值为 undefined。
set的用法正好和get方法对应,一个是读一个是写,set方法当要给属性赋值时会被调用,并且可以接收一个参数作为新的值,最终赋值结果由return决定
同样以这个对象为例,还是把get赋值写好,每次都返回当前age的值
let age = 18; //定义一个实际的年龄数据
let person = { //准备好目标对象
name:"张三"
};
Object.defineProperty(person,"age",{
get(){
return age;
}
});
现在呢,如果我去修改他得值,他还是出现改不掉的情况
这是因为set没配置,我先配置个最基本的set看看,能否修改age的值
let age = 18; //定义一个实际的年龄数据
let person = { //准备好目标对象
name:"张三"
};
Object.defineProperty(person,"age",{
get(){
return age; //返回实际年龄
},
set(newVal){
age = newVal //修改实际年龄
}
});
> person.age
18
> person.age = 19
19
> person.age
19
很显然是可以修改的
现在呢,我希望当我对年龄做出了修改,如果不是递增1的话就弹出警告
那么我可以这么干
let age = 18; //定义一个实际的年龄数据
let person = { //准备好目标对象
name:"张三"
};
Object.defineProperty(person,"age",{
get(){
return age;
},
set(newVal){
if(newVal !== age+1){
console.warn("注意:你这个年龄增加的有点快啊!!!");
}
age = newVal
}
});
此时这段代码,能完美的完成需求
画龙点睛
上面的例子,还是不怎么完善,万一有人给年龄随意赋值呢?那我是不是要弹出报错?
所以,当调用set的时候,可以进行一系列的数据类型判断,这里仅需判断是否为数值即可,区别不能为负值不然就抛出错误
代码如下:
let age = 18; //定义一个实际的年龄数据
let person = { //准备好目标对象
name:"张三"
};
Object.defineProperty(person,"age",{
get(){
return age;
},
set(newVal){
if(typeof newVal !== 'number'){
throw "你在想什么呢?";
}else if(newVal < 0){
throw "你跟阎王沟通过?";
}else if(newVal !== age+1){
console.warn("注意:你这个年龄增加的有点快啊!!!");
}
age = newVal;
}
});
实现简单的数据检测
在第一篇:零基础入门Vue之梦开始的地方——插值语法 中我提到如下的说明
"{{}}"在这个表达式里面可以写js的表达式,并且它里面的执行语句的this是vue实例,同时vue官方文档指出,在data中配置的东西最后都会通过数据代理的方式挂在到vue实例上。
data配置项里面的所有数据,都会以数据代理的方式挂在到vm实例上,并且Vue也会提供一个纯净版的Vue._data,此时这个Vue._data等同于我们配置的data
(即:vm._data === data is true)
而这个数据代理就是依赖于上一节说的 Object.defineProperty() 来实现
实现原理简单分析&实现
在Vue中,Vue对实际传入的data并没有直接挂在到vm对象及vm._data上,而是重新通过get和set去做一系列的数据代理和数据监测
这个过程中有许多细节要处理,本篇不可能以这一千不到的字数去说明白Vue的数据检测和数据代理
仅仅只是做一个基本的样例,供我自己学习
首先,我得准备一个方法用来刷新dom意思意思一下
function flashVirtualDom(){
//此处省略新老虚拟dom之间的比较算法
console.log("检测到数据更改,准备刷新虚拟dom");
}
然后呢,我要准备好一个数据
let data = {
name:"张三",
age:18,
friends:["李四","王五","赵高"],
school:{
name:"北京大学",
local:"北京",
学制:4
}
};
目标:接下来我希望不直接操作data的数据,而是用另外的方法去操作data的数据,并且当data数据发送改变时能被我写的代码检测到
既然不是直接操作,那么用户和真实数据应该有一个 中间层,所以我把它明明为Middle
这个中间曾呢,使用Object.defineProperty() 把data的数据挂载到自己实例上,可以操作它的实力间接更改data
(换个说法:在上一节age就是真实数据,而person.age实际上是真实数据的代理,我并没有直接操作age,只不过这次数据交给data对象,更加的密集,集中保管和内部维护)
function Middle(obj){
let keys = Object.keys(obj); //拿到所有的key
for(let key of keys){
//如果是对象
if(typeof obj[key] === "object" && !(obj[key] instanceof Array)){
this[key] = new Middle(obj[key]); //如果是对象嵌套那么递归调用
continue;
}else{
//过滤undefined
if(!obj[key]){
continue;
}
//设置数据代理
Object.defineProperty(this,key,{
get(){
//读取就返回原模原样的值
return obj[key];
},
set(newVal){
//赋值就修改原始数据
flashVirtualDom();
obj[key] = newVal;
}
})
}
}
}
此时,在console执行如下代码
> let m = new Middle(data);
undefined
> m.age = 30
检测到数据更改,准备刷新虚拟dom
30
> m.age
30
展开m,与Vue实例的_data进行对比,相差不大,当我修改其中一个数据时会调用flashVirtualDom();方法用于刷新dom,同样还可以写其他方法做其他操作
数据劫持
在尚硅谷的课程中,有提到”数据劫持“这一概念,对此,本篇也相应的记录下
个人理解的数据劫持,实际上是当数据发生变动时,率先拦截变动,做出处理后,在决定是否要变动数据
或者更改变动的结果,其在我理解更类似于hook的操作,当某个函数或者变量被赋值,然后我对他进行一次拦截
拦截之后做出我想要的操作,操作做完再让他回归正常运行
Vue中的数据监测
在做Vue的开发中,Vue并非对任何数据都做了监测,因此我认为我作为Vue的学习者,应当去了解具体有哪些情况不会被监测到,从而避免日后开发的各种奇奇怪怪的问题。
对于常见数据,有两种比较容易出错
- 对象
- 数组
对象相关的数据监测问题&解决方案
假设,在初始的data里面仅有如下数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="root">
<div>姓名:{{person.name}}</div>
<div>性别:{{person.sex}}</div>
<div>年龄:{{person.age}}</div>
</div>
</body>
<script>
let vm = new Vue({
el:"#root",
data:{
person:{
name:"张三",
sex:"男"
}
}
})
</script>
</html>
这都很正常,但如果我想不修改这个代码,在项目上线后根据后端给的数据动态的增加
假设在某处代码上后端返回的数据有年龄,我想data的数据增加一条年龄并且展示到应该展示的位置
那么我试图这么做
vm.data.age = 19; //假设后端给出的数据是19
此时页面无任何变化
(实际操作,可以先让页面运行起来,然后再console上去追加一个年龄)
这个后期追加的数据在Vue中并没有被监测,导致他没有显示(简单说就是没有get和set方法)
那我该如何解决呢?
Vue非常人性化的提供另外的方法:Vue.set( target, propertyName/index, value )
(注:用vm.$set也是一样的,详细区别可以去翻官方文档)
在这个方法允许给某个对象添加属性并且监测
- target:目标对象
- propertyName/index:属性名称/索引值(可用于数组)
- value:值
所以当我接收到后端数据时,我可以用这个方法追加数据并且被Vue所监测
Vue.set(vm.person,'age',19); //假设后端给出的数据是19
此时age即可直接显示到页面上了
数组相关的数据监测问题&解决办法
假设,这次问题代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="root">
<ul>
<li v-for="per in friends" :key="per.id">
{{per}}
</li>
</ul>
</div>
</body>
<script>
let vm = new Vue({
el:"#root",
data:{
friends:["张三","李四","王五","赵高"]
}
})
</script>
</html>
现在呢,我想要修改friends第一个元素,修改为”张四“
正常做法如下:
vm.friends[0] = "张四";
但页面数据无变化,实际数据已经更改了
这是为什么呢?展开friends数组,发现这并没有数组成员的get和set方法,Vue并未对数组成员做监测,因此改了之后,数据并未刷新
那么,我该如何做呢?官网其实早就给出了答案:变更方法。 官方对以下七个方法进行了包裹(我感觉hook更好理解)
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
依次,但我通过这些方法去做数组进行”增删改查“时,Vue会检测到,所以我想修改第一个元素为李四可以这么干
vm.friends.splice(0,1,"张四")
执行完后页面变了,说明变动被监测到了,除此之外还可以使用set方法
Vue.set(vm.friends,0,"张四");
The End
唔~(一口浊气)
这一篇可真长啊
本篇完~~~~
零基础入门Vue之画龙点睛——再探监测数据的更多相关文章
- 零基础入门 实战mpvue2.0多端小程序框架
第1章 课程快速预览(必看!!!)在这一章节中,老师讲带领你快速预览课程整体.其中,涉及到为什么要做这么一门实战课程.制作一个小程序的完整流程是怎么样的,以及如何做项目的技术选型. 第2章 30 分钟 ...
- 零基础入门 Java 后端开发,有哪些值得看的视频?
目前网络上充满了大量的 Java 视频教程,然而内容却鱼龙混杂,为了防止小伙伴们踩坑,一枫结合自己的学习经验,向大家推荐一些不错的学习资源. 作为一名非科班转码选手,可以说,我是在哔哩哔哩上的研究生! ...
- 从零基础入门JavaScript(1)
从零基础入门JavaScript(1) 1.1 Javascript的简史 1995年的时候 由网景公司开发的,当时的名字叫livescript 为了推广自己的livescript,搭了j ...
- 函数:我的地盘听我的 - 零基础入门学习Python019
函数:我的地盘听我的 让编程改变世界 Change the world by program 函数与过程 在小甲鱼另一个实践性超强的编程视频教学<零基础入门学习Delphi>中,我们谈到了 ...
- 【JAVA零基础入门系列】Day4 变量与常量
这一篇主要讲解Java中的变量,什么是变量,变量的作用以及如何声明,使用变量. 那么什么是变量?对于初学者而言,可以将变量理解为盒子,这些盒子可以用来存放数据,不同类型的数据需要放在对应类型的盒子里. ...
- 【JAVA零基础入门系列】Day5 Java中的运算符
运算符,顾名思义就是用于运算的符号,比如最简单的+-*/,这些运算符可以用来进行数学运算,举个最简单的栗子: 已知长方形的长为3cm,高为4cm,求长方形的面积. 好,我们先新建一个项目,命名为Rec ...
- 【JAVA零基础入门系列】Day6 Java字符串
字符串,是我们最常用的类型,每个用双引号来表示的串都是一个字符串.Java中的字符串是一个预定义的类,跟C++ 一样叫String,而不是Char数组.至于什么叫做类,暂时不做过多介绍,在之后的篇章中 ...
- 【JAVA零基础入门系列】Day8 Java的控制流程
什么是控制流程?简单来说就是控制程序运行逻辑的,因为程序一般而言不会直接一步运行到底,而是需要加上一些判断,一些循环等等.举个栗子,就好比你准备出门买个苹果,把这个过程当成程序的话,可能需要先判断一下 ...
- 【JAVA零基础入门系列】Day10 Java中的数组
什么是数组?顾名思义,就是数据的组合,把一些相同类型的数放到一组里去. 那为什么要用数组呢?比如需要统计全班同学的成绩的时候,如果给班上50个同学的成绩信息都命名一个变量进行存储,显然不方便,而且在做 ...
- 【JAVA零基础入门系列】Day11 Java中的类和对象
今天要说的是Java中两个非常重要的概念--类和对象. 什么是类,什么又是对象呢?类是对特定集合的概括描述,比如,人,这个类,外观特征上,有名字,有年龄,能说话,能吃饭等等,这是我们作为人类的相同特征 ...
随机推荐
- 房贷LPR该如何选择
一.新政是否和你有关? 原文:是指2020年1月1日前金融机构已发放的和已签订合同但未发放的参考贷款基准利率定价的浮动利率贷款(不包括公积金个人住房贷款). 二.新政如何调整? 一种是按照LPR加 ...
- 万字血书Vue—Vue的核心概念
MVVM M:模型(Model):data V:视图(View):模板 VM:视图模型(ViewModel):Vue实例对象 Vue收到了MVVM模型的启发,MVVM是vue实现数据驱动视图和双向数据 ...
- C# WPF侧边栏导航菜单(Dropdown Menu)
时间如流水,只能流去不流回! 点赞再看,养成习惯,这是您给我创作的动力! 本文 Dotnet9 https://dotnet9.com 已收录,站长乐于分享dotnet相关技术,比如Winform.W ...
- 12-异步FIFO
1.异步FIFO的应用 跨时钟域 批量数据 传输效率高 2.异步FIFO结构 FIFO深度 - 双端口RAM设计 3.异步FIFO深度计算 4.异步FIFO读写地址的编码 5.异步FIFO读写时钟域的 ...
- 【RTOS】基于RTOS的嵌入式系统看门狗策略
RTOS - high integrity systems 看门狗策略 Watchdog Strategies for RTOS enabled embedded systems 介绍 看门狗定时器就 ...
- 让vs自动提示没有using的类
默认情况下,没有using的类,敲代码时没有智能提示,需要在[工具]->[选项]中开启
- [转帖]Kubernetes 1.23:IPv4/IPv6 双协议栈网络达到 GA
https://kubernetes.io/zh-cn/blog/2021/12/08/dual-stack-networking-ga/#:~:text=Kubernetes%201.23%EF%B ...
- [转帖]TLB缓存是个神马鬼,如何查看TLB miss?
https://zhuanlan.zhihu.com/p/79607142 介绍TLB之前,我们先来回顾一个操作系统里的基本概念,虚拟内存. 虚拟内存 在用户的视角里,每个进程都有自己独立的地址空间, ...
- ARMv8.0下duckdb的安装与编译过程-解决 Failed to allocate block of 2048 bytes
ARMv8.0下duckdb的安装与编译过程-解决 Failed to allocate block of 2048 bytes 背景 duckdb 是一个很流行的单机版数据库引擎 同事下载了相关的预 ...
- [转帖]深入理解mysql-第十章 mysql查询优化-Explain 详解(上)
目录 一.初识Explain 二.执行计划-table属性 三.执行计划-id属性 四.执行计划-select_type属性 一条查询语句在经过MySQL查询优化器的各种基于成本和规则的优化会后生成一 ...