详解Vue响应式的部分实现
作者:小婉子啊 发布时间:2022-12-21 23:25:53
什么是响应式
简单来说当数据发生变化时,对数据有依赖的代码会重新执行。
例如在Vue中,当我们的数据发生改变,界面上对该数据的引用组件会重新渲染
组件data的数据一旦变化,立即出发视图的更新;
computed属性在依赖发生变化时,自动重新计算新值;提供watch * ,可以监听到数据的变化
Vue2与Vue3响应式之间的区别
Vue2使用ES5的defineProperty实现
Vue3使用的是ES6的propxy.(PS:这也就是为什么Vue2不支持IE7/8,而Vue3不支持IE11.)
使用Object.defineProperty监听对象
该方法允许精确地添加或修改对象的属性,并返回此对象。
Object.defineProperty()方法会在直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
备注:(应当直接在object构造器对象上调用此方法,而不是在任意一个object类型的实例上调用)
语法:Object.defineProperty(obj, prop, descriptor)
obj:要设置属性的对象;
prop:要设置的属性名,这个属性可以是已存在也可以是不存在的;
descriptor:要定义或修改的属性描述符。该参数接收一个对象,用来对属性进行描述。如value(值),writable(是否可重写),enumerable(是否可枚举)等
枚举时使用for...in 或 Object.keys方法可以改变这些属性的值,默认情况下,使用 Object.defineProperty()
添加的属性值是不可修改(immutable)的。
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符
数据描述符:是一个具有值的属性,该值是可写的,也可以是不可写的
存取描述符:由getter函数和setter函数所描述的属性
一个描述符只能是这两者其中之一,不能同时是两者
使用Object.defineProperty监听对象
利用 Object.defineProperty
重写 get
和 set
,将对象属性的赋值和获取变成函数,我们可以实现一个简单的双向绑定
get: 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。该函数的返回值会被用作属性的值。默认为 undefined。
set: 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。默认为 undefined。
//实现一个简单的双向绑定
const data = {}
const name = 'xiaowanzi'
Object.defineProperty(data, 'name', {
get: function () {
console.log('get')
return name
},
set: function (newVal) {
console.log('set')
name = newVal
}
})
//测试
console.log(data.name)//get xiaowanzi
data.name = 'list'//set
如果我们想让对象的所有属性都具有响应式,就需要对全部属性进行遍历,实现getter和setter:
//实现Vue响应式原理
let obj = {
name: 'aaa',
age: 18
}
//获取obj对象的所有key
const keys = Object.keys(obj)//Object.keys()返回一个由一个给定对象的资深可枚举属性组成的数组,数组中的属性名的排列顺序和正常循环遍历该对象时返回的顺序一致
//遍历Key数组,对obj对象的每一个属性进行处理
keys.forEach(key => {
//使用value变量保存key对应的属性值
let value = obj[key]
//使用Object.defineProperty
Object.defineProperty(obj, key, {
get() {//当获取属性时,回来到这里
console.log(`${key}属性被获取`)
return value
},
set(newValue) {//当修改属性时,会来到这里,并且设置的值会传给newValue
console.log(`${key}属性被修改`)
//这里不能写成obj[key]=newValue
//如果这样写相当于又对该属性进行修改值,又会进入set,就死循环了
value = newValue
}
})
})
//现在我们已经可以实现监听obj对象的读取与修改了
console.log(obj.name)//在打印'aaa'之前会先打印'name被获取',也就是说监听到属性的获取。
obj.name = 'bbb'//打印name属性被修改,也就是说监听到了属性的改变
//实现Vue响应式原理
let obj={
name:'aaa',
age:18
}
//获取obj对象的所有key
const keys=object.keys(obj)//Object.keys()返回一个由一个给定对象的资深可枚举属性组成的数组,数组中的属性名的排列顺序和正常循环遍历该对象时返回的顺序一致
//遍历Key数组,对obj对象的每一个属性进行处理
keys.forEach(key=>{
//使用value变量保存key对应的属性值
let value = obj[key]
//使用Object.defineProperty
Object.defineProperty(obj,key,{
get(){//当获取属性时,回来到这里
console.log(`${key}属性被获取`)
return value
},
set(newValue){//当修改属性时,会来到这里,并且设置的值会传给newValue
console.log(`${key}属性被修改`)
//这里不能写成obj[key]=newValue
//如果这样写相当于又对该属性进行修改值,又会进入set,就死循环了
value=newValue
}
})
})
//现在我们已经可以实现监听obj对象的读取与修改了
console.log(obj.name)//在打印'aaa'之前会先打印'name被获取',也就是说监听到属性的获取。
obj.name='bbb'//打印name属性被修改,也就是说监听到了属性的改变
缺点
可以实现监听对象的属性,但是它没有办法做到对对象新增的属性进行监听,同时也没有办法做到对数据进行监听
使用ES6的Proxy实现监听对象
该API就是用来实现监听对象的,而且该API对数组同样也是有效果的,在使用Proxy
时,通常会搭配Reflect
一起使用 Proxy
用于创建代理对象,从而实现基本操作的拦截和自定义(如属性的查找,赋值,枚举,函数调用等)
术语:
handler:包含捕捉器(trap)的占位符对象,可译为处理器对象。
traps:提供属性访问的方法。这类似于操作系统中捕获器的概念。
target:被 Proxy 代理虚拟化的对象。它常被作为代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)
语法:
const p = new Proxy(target, handler)
1.第一个参数target:要包装的目标对象
2.第二个参数handle:接收一个对象,内部定义了操作目标对象时的方法;
参数:
target:要使用
Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理
p
的行为。
方法
Proxy.revocable()
创建一个可撤销的
Proxy
对象。
Reflect
是一个内置的对象,它提供拦截 JavaScript 操作的方法。Reflect
不是一个函数对象,因此它是不可构造的。
通过给对象设置代理,我们可以拦截对象属性的取值/赋值操作。
举个例子:
const student = {
age: 23
}
const handler = {
get(target, prop) {
console.log("读值:", key, value);
target[key] = value;
return target[prop]
},
set(target, key, value) {
console.log("设置值", key, value);
target[key] = value;
return true
}
}
const proxy = new Proxy(studengt, handler)
console.log(proxy.age)//23
//proxy.age=32 //32
实现代码
//Proxy+Reflect
let obj = {
name: 'aaa',
age: 18
}
//第一个参数为要代理的对象,第二个参数位hander
const proxy = new Proxy(obj, {
//当访问第一个属性的时候会得到getter
//同时会传递三个参数
//target要进行代理对象,这里就是obj
//key被访问的属性
//receiver用来绑定this
get(target, key, receiver) {
console.log(`${key}属性被访问`)
return Reflect.get(target, key, receiver)
},
//当某一属性修改的时,回来到Setter
// 同时会传递四个参数
// target要进行代理的对象,这里就是obj
// 可以被访问的属性
// newValue新修改的值
// receiver用来绑定this
set(target, key, newValue, receiver) {
console.log(`${key}属性修改`)
return Reflect.set(target, key, newValue, receiver)
}
})
// 以上代码执行完,得到的就是proxy对象就是obj对象的代理
// 我们只需要修改代理对象的就可以做到修改原型对象的效果
// 而且我们对代理对象的修改使我们能够监听到的
console.log(proxy.name)
proxy.name = 'bbb'
来源:https://juejin.cn/post/7174028597546614814


猜你喜欢
- 一.正则基础1. []方括号表示匹配可以匹配方括号中的任意点单个字符,方括号只允许匹配单个字符。2. | 或表示两项之间的一个选择,它不能和
- 本文实例讲述了Spring实战之协调作用域不同步的Bean操作。分享给大家供大家参考,具体如下:一 配置<?xml version=&
- 百度百科说法:Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务
- FrameLayout 在这个布局中,所有的子元素都不能被指定放置的位置,他们统统防御这块区域的左上角, 并且后面的子元素直接覆盖在前面的子
- 本文实例为大家分享了Android文本视图TextView实现聊天室的具体代码,供大家参考,具体内容如下Math.random()生成随机数
- boolean isGBK(String s) throws UnsupportedEncodingException { if(s.equ
- 该方法针对idea版本(2020.2.x)C:\Users\yanghao\AppData\Roaming\JetBrains\Intell
- 关于Plupload的介绍,相信它的官网http://www.plupload.com/已经给得很详细了。Plupload的上传原理简单点说
- 大部分app打开pdf文件是通过intent调起手机中能打开pdf文件的工具,来查看pdf文件,如果需求是,用户在app内下载好pdf文件后
- 一、Thread 的常见构造方法方法说明Thread()创建线程对象Thread(Runnable target)使用 Runnable 对
- 本文实例讲述了Android编程单选项框RadioGroup用法。分享给大家供大家参考,具体如下:今天介绍的是RadioGroup 的组事件
- webservice的应用已经越来越广泛了,下面介绍几种在Java体系中开发webservice的方式,相当于做个记录。1.Axis2Axi
- 简介使用RecyclerView实现网格布局,实现手机界面应用列表 效果效果如下图: 详细代码XML布局文件在布局中使用
- 主内存和工作内存Java 内存模型规定了所有的变量都存储在主内存中, 每条线程有自己的工作内存线程的工作内存中保存了被该线程使用的变量的主内
- 微服务的特点决定了功能模块的部署是分布式的,大部分功能模块都是运行在不同的机器上,彼此通过服务调用进行交互,前后台的业务流会经过很多个微服务
- 综述在Retrofit2.0使用详解这篇文章中详细介绍了retrofit的用法。并且在retrofit中我们可以通过ResponseBody
- 第一部分: 使用idea 打包工程jar 1.准备好一份 开发好的 可执行的 含有main方法的&nbs
- 要获取Java中的当前时间戳:Timestamp timestamp = new Timestamp(System.currentTimeM
- 1. 父工程构建1.1 Maven项目搭建环境版本JDK1.8Maven3.6+Maven模板maven-archetype-size删除父
- android大家都有很多需要用户上传头像的需求,有的是选方形,有的是圆角矩形,有的是圆形。首先我们要做一个处理图片的自定义控件,把传入的图