Vue3-笔记003-响应式原理

Vue2 使用的是 Object.defineProperty
Vue3 使用的是 Proxy

Vue2 缺陷

对象只能劫持设置好的数据。新增的数据需要 Vue.Set(xxx)。
数组只能操作七种方法,修改某一项值无法劫持。
数组的length修改也无法劫持。

Reflect 反射函数

Reflect是什么

Reflect是一个内置的对象,它提供了拦截JavaScript操作的方法。它不是一个函数对象,因此不可构造。Reflect对象提供了一些静态方法来操作对象,例如Reflect.getReflect.setReflect.deleteProperty等。这些方法可以用于读取、修改或删除对象的属性,并返回相应的结果。
Vue3中,Reflect也被用作一个修饰符(decorator),用于监听DOM元素上的属性变化并将其反映到Vue实例的数据上。当DOM元素的属性发生变化时,Vue会捕获这些变化,并更新相应的数据,从而保持数据和视图的同步。
Reflect在Vue3中扮演着重要的角色,它提供了一种机制来拦截和操作JavaScript对象,从而实现响应式数据绑定和视图更新

Reflect的作用
  1. 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
  2. 修改某些Object方法的返回结果,让其变得更合理。
  3. 让Object操作都变成函数行为。
  4. 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.sealObject.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 对比
MethodsObjectReflect
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                        
  }
}
  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值