003-响应式原理-目录
- Vue2 缺陷
- Reflect 反射函数
- Reflect是什么
- Reflect的作用
- Reflect的方法
- Reflect方法案例
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.has(target, name)
- Reflect.deleteProperty(target, name)
- Reflect.construct(target, args)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
- Reflect.apply(target, thisArg, args)
- Reflect.defineProperty(target, name, desc)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.ownKeys(target)
- Reflect 与 Object 对比
- Reflect的理念
- Reactive的实现
Vue2 使用的是 Object.defineProperty
Vue3 使用的是 Proxy
Vue2 缺陷
对象只能劫持设置好的数据。新增的数据需要 Vue.Set(xxx)。
数组只能操作七种方法,修改某一项值无法劫持。
数组的length修改也无法劫持。
Reflect 反射函数
Reflect是什么
Reflect是一个内置的对象,它提供了拦截JavaScript操作
的方法。它不是一个函数对象,因此不可构造。Reflect对象提供了一些静态方法来操作对象,例如Reflect.get
、Reflect.set
、Reflect.deleteProperty
等。这些方法可以用于读取、修改或删除对象的属性
,并返回相应的结果。
在Vue3
中,Reflect也被用作一个修饰符(decorator),用于监听DOM元素上的属性变化并将其反映到Vue实例的数据上。当DOM元素的属性发生变化时,Vue会捕获这些变化,并更新相应的数据,从而保持数据和视图的同步。
Reflect在Vue3中扮演着重要的角色,它提供了一种机制来拦截和操作JavaScript对象,从而实现响应式数据绑定和视图更新
。
Reflect的作用
- 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
- 修改某些Object方法的返回结果,让其变得更合理。
- 让Object操作都变成函数行为。
- Reflect对象的方法与Proxy对象的方法一一对应。
Reflect的方法
1.Reflect.get(target, name, receiver)
// 查找并返回target对象的name属性,receiver绑定this
2.Reflect.set(target, name, value, receiver)
// 设置target对象的name属性等于value
3.Reflect.has(obj, name)
// 方法对应name in obj里面的in运算符
4.Reflect.deleteProperty(obj, name)
// 方法等同于delete obj[name],用于删除对象的属性。
5.Reflect.construct(target, args)
// 等同于new target(...args),调用构造函数的方法。
6.Reflect.getPrototypeOf(obj)
// 读取对象的__proto__属性,对应Object.getPrototypeOf
7.Reflect.setPrototypeOf(obj, newProto)
// 设置目标对象的原型 对应Object.setPrototypeOf
8.Reflect.apply(func, thisArg, args)
// 等同于Function.prototype.apply.call(func, thisArg, args)
9.Reflect.defineProperty(target, propertyKey, attributes)
// 等同于Object.defineProperty
10.Reflect.getOwnPropertyDescriptor(target, propertyKey)
// 等同于Object.getOwnPropertyDescriptor
11.Reflect.isExtensible (target)
// 对应Object.isExtensible 表示当前对象是否可扩展。
12.Reflect.preventExtensions(target)
// 对应Object.preventExtensions 让一个对象变为不可扩展
13.Reflect.ownKeys (target)
// 返回对象的所有属性,可以返回Symbol类型
Reflect方法案例
Reflect.get(target, name, receiver)
获取对象身上某个属性的值,类似于 target[name]
。如果没有该属性,则返回undefined
。
var obj1 = { x: 1, y: 2 };
Reflect.get(obj1, "x"); // 1
Reflect.set(target, name, value, receiver)
将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
var obj2 = {};
Reflect.set(obj2, "prop", "value"); // true
Reflect.has(target, name)
判断一个对象是否存在某个属性,和 in
运算符 的功能完全相同。
const obj3 = {x: 0};
Reflect.has(obj3, "x"); // true
Reflect.deleteProperty(target, name)
作为函数的delete操作符,相当于执行 delete target[name]
。
var obj4 = { x: 1, y: 2 };
Reflect.deleteProperty(obj4, "x"); // true
obj4; // { y: 2 }
Reflect.construct(target, args)
对构造函数进行 new 操作,相当于执行 new target(...args)
。
const obj5 = Reflect.construct(Date, [2021, 3, 1]);
Reflect.getPrototypeOf(target)
返回指定对象的原型.类似于 Object.getOwnPropertyDescriptor()
。
var obj6 = {};
Reflect.getPrototypeOf(obj6); // 等同于Object.prototype
Reflect.setPrototypeOf(target, prototype)
设置对象原型的函数. 返回一个 Boolean
, 如果更新成功,则返回true。如果 target
不是 Object
,或 prototype
既不是对象也不是null
,抛出一个 TypeError 异常。
var obj7 = {};
Reflect.setPrototypeOf(obj7, null); // true
Reflect.apply(target, thisArg, args)
对一个函数进行调用操作,同时可以传入一个数组作为调用参数。
var obj8 = {};
Reflect.apply(Math.floor, obj8, [1,88]) // 1;
Reflect.defineProperty(target, name, desc)
Reflect.defineProperty
方法基本等同于Object.defineProperty
,直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,不同的是,Object.defineProperty
返回此对象。而Reflect.defineProperty会返回布尔值
.
const obj9 = {};
Reflect.defineProperty(obj9, 'property', {
value: 666,
writable: false
}); // true
Reflect.getOwnPropertyDescriptor(target, name)
如果对象中存在该属性,如果指定的属性存在于对象上,则返回其属性描述符对象(property descriptor)
,否则返回 undefined
。类似于 Object.getOwnPropertyDescriptor()
。
const obj10 = {x: "hello"};
Reflect.getOwnPropertyDescriptor(obj10, "x");
// {value: "hello", writable: true, enumerable: true, configurable: true}
Reflect.isExtensible(target)
判断一个对象是否是可扩展的(是否可以在它上面添加新的属性),类似于 Object.isExtensible()
。返回表示给定对象是否可扩展的一个Boolean 。(Object.seal
或 Object.freeze
方法都可以标记一个对象为不可扩展。)
var obj11 = {};
Reflect.isExtensible(obj11); // true
Reflect.preventExtensions(target)
让一个对象变的不可扩展,也就是永远不能再添加新的属性。
var obj12 = {};
Reflect.isExtensible(obj12); // true
Reflect.preventExtensions(obj12);
Reflect.isExtensible(obj12); // false
Reflect.ownKeys(target)
返回一个包含所有自身属性(不包含继承属性)
的数组。(类似于 Object.keys()
, 但不会受enumerable
影响, Object.keys
返回所有可枚举属性的字符串数组).
const obj13 = {z: 3, y: 2, x: 1};
Reflect.ownKeys(obj13); // [ "z", "y", "x" ]
Reflect 与 Object 对比
Methods | Object | Reflect |
---|---|---|
defineProperty() | Object.defineProperty() 返回传递给函数的对象。如果未在对象上成功定义属性,则返回TypeError。 | 如果在对象上定义了属性,则Reflect.defineProperty() 返回true,否则返回false。 |
defineProperties() | Object.defineProperties() 返回传递给函数的对象。如果未在对象上成功定义属性,则返回TypeError。 | - |
set() | - | 如果在对象上成功设置了属性,则Reflect.set() 返回true,否则返回false。如果目标不是Object,则抛出TypeError。 |
get() | - | Reflect.get() 返回属性的值。如果目标不是Object,则抛出TypeError。 |
deleteProperty() | - | 如果属性从对象中删除,则Reflect.deleteProperty() 返回true,否则返回false。 |
getOwnPropertyDescriptor() | 如果传入的对象参数上存在Object.getOwnPropertyDescriptor() ,则会返回给定属性的属性描述符,如果不存在,则返回undefined。 | 如果给定属性存在于对象上,则Reflect.getOwnPropertyDescriptor() 返回给定属性的属性描述符。如果不存在则返回undefined,如果传入除对象(原始值)以外的任何东西作为第一个参数,则返回TypeError。 |
getOwnPropertyDescriptors() | Object.getOwnPropertyDescriptors() 返回一个对象,其中包含每个传入对象的属性描述符。如果传入的对象没有拥有的属性描述符,则返回一个空对象。 | - |
getPrototypeOf() | Object.getPrototypeOf() 返回给定对象的原型。如果没有继承的原型,则返回null。在 ES5 中为非对象抛出TypeError,但在 ES2015 中强制为非对象。 | Reflect.getPrototypeOf() 返回给定对象的原型。如果没有继承的原型,则返回 null,并为非对象抛出TypeError。 |
setPrototypeOf() | 如果对象的原型设置成功,则Object.setPrototypeOf() 返回对象本身。如果设置的原型不是Object或null,或者被修改的对象的原型不可扩展,则抛出TypeError。 | 如果在对象上成功设置了原型,则Reflect.setPrototypeOf() 返回 true,否则返回 false(包括原型是否不可扩展)。如果传入的目标不是Object,或者设置的原型不是Object或null,则抛出TypeError。 |
isExtensible() | 如果对象是可扩展的,则 Object.isExtensible() 返回 true,否则返回 false。如果第一个参数不是对象(原始值),则在ES5 中抛出TypeError 。在 ES2015 中,它将被强制为不可扩展的普通对象并返回false 。 | 如果对象是可扩展的,则Reflect.isExtensible() 返回true,否则返回false。如果第一个参数不是对象(原始值),则抛出TypeError。 |
preventExtensions() | Object.preventExtensions() 返回被设为不可扩展的对象。如果参数不是对象(原始值),则在 ES5 中抛出TypeError。在 ES2015 中,参数如为不可扩展的普通对象,然后返回对象本身。 | 如果对象已变得不可扩展,则Reflect.preventExtensions() 返回true,否则返回false。如果参数不是对象(原始值),则抛出TypeError。 |
keys() | Object.keys() 返回一个字符串数组,该字符串映射到目标对象自己的(可枚举)属性键。如果目标不是对象,则在 ES5 中抛出TypeError,但将非对象目标强制为 ES2015 中的对象 | - |
ownKeys() | - | Reflect.ownKeys() 返回一个属性名称数组,该属性名称映射到目标对象自己的属性键。如果目标不是Object,则抛出TypeError。 |
Reflect的理念
ES6认为,对属性内存的控制、原型链的修改、函数的调用等等,这些都属于底层实现,因此,需要将它们提取出来,形成一个正常的API,并高度聚合到某个对象中,于是就造就了Reflect对象。
通常 Object
会因为报错而阻塞程序,而 Reflect
返回操作的 boolean 状态值
,表示操作成功与否,返回 true 表示操作成功。
Reactive的实现
1.初始reactive
// 接收一些引用类型,可以加入泛型约束
export const reactive = <T extends object>(target: T) => {
// 会直接返回一个proxy对象
// proxy的第一个参数是target,第二个参数是handler对象
return new Proxy(target, {
// 可以设置拦截器,比如get、set、delete、ownKeys(遍历)、apply等
get(target, key, receiver) {
// Reflect反射函数
// receiver可以绑定this,从而使get保证对象的上下文正确
const res = Reflect.get(target, key, receiver)
return res
},
set(target, key, value, receiver) {
// set需要返回一个boolean,来判断是否成功。
const res = Reflect.set(target, key, value, receiver)
return res
}
})
}
2.effect,track,trigger
// 1.副作用函数-effect
// 使用一个全局变量active收集当前副作用函数
// 当依赖发生变化的时候,执行此副作用函数
// 从而实现依赖收集和依赖更新
let activeEffect;
// 接收一个匿名函数
export const effect = (fn: Function) => {
const _effect = function () {
activeEffect = _effect;
fn()
}
// 第一次也要执行
_effect()
}
// 2.依赖收集-track
// WeakMap接收一个对象
const targetMap = new WeakMap()
export const track = (target, key) => {
// 通过对象target【理解为key】去取map【理解为value】
let depsMap = targetMap.get(target)
// 第一层数据结构
// new Map()【理解为新建一个对象】
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
// 第二层数据结构
// new Set()【也是一个数据结构,理解为没有重复内容的数组】
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
// 收集依赖
deps.add(activeEffect)
}
// 3.更新-trigger
export const trigger = (target, key) => {
// 取到key对应的依赖
const depsMap = targetMap.get(target)
const deps = depsMap.get(key)
// set结构因为是数组,可以遍历并调用副作用函数
deps.forEach(effect => effect())
}
3.赋值触发
import {track,trigger} from './effect'
export const reactive = <T extends object>(target:T) => {
return new Proxy(target,{
get (target,key,receiver) {
const res = Reflect.get(target,key,receiver) as object
track(target,key)
return res
},
set (target,key,value,receiver) {
const res = Reflect.set(target,key,value,receiver)
trigger(target,key)
return res
}
})
}
4.测试html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
</div>
<script type="module">
import { reactive } from './reactive.js'
import { effect } from './effect.js'
const user = reactive({
name: "测试01",
sex: 0
})
effect(() => {
document.querySelector('#app').innerText = `${user.name} - ${user.sex}`
})
setTimeout(()=>{
user.name = '测试02'
setTimeout(()=>{
user.sex = 1
},1000)
},2000)
</script>
</body>
</html>
5.深层次对象的递归实现
import { track, trigger } from './effect'
const isObject = (target) => target != null && typeof target == 'object'
export const reactive = <T extends object>(target: T) => {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver) as object
track(target, key)
if (isObject(res)) {
return reactive(res)
}
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
trigger(target, key)
return res
}
})
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
</div>
<script type="module">
import { reactive } from './reactive.js'
import { effect } from './effect.js'
const user = reactive({
name: "测试01",
sex: 0,
abc:{
def:{
ghi:123
}
}
})
effect(() => {
document.querySelector('#app').innerText = `${user.name} - ${user.sex}-${user.abc.def.ghi}`
})
setTimeout(()=>{
user.name = '测试02'
setTimeout(()=>{
user.sex = 1
setTimeout(()=>{
user.abc.def.ghi = 666
},1000)
},1000)
},2000)
</script>
</body>
</html>
6.tsconfig.json
{
"compilerOptions": {
"target": "ES2016",
"module": "ESNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}