• 响应式数据


    Object.defineproperty

    是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性

    Object.defineproperty可以接收三个参数

    Object.defineproperty(obj, prop, desc)
    
    • 1
    • obj : 第一个参数就是要在哪个对象身上添加或者修改属性
    • prop : 第二个参数就是添加或修改的属性名
    • desc : 配置项,一般是一个对象
    desc描述对象的配置项:

    writable :是否可重写
    value :当前值
    get: 读取时内部调用的函数
    set: 写入时内部调用的函数
    enumerable: 控制属性是否可以枚举
    configurable: 控制属性是否可以被删除/修改

    get 是获取值的时候的方法,类型为 function ,获取值的时候会被调用,不设置时为undefined
    set 是设置值的时候的方法,类型为 function ,设置值的时候会被调用,不设置时为undefined
    注: 当使用了getter或setter方法,不允许使用writable和value这两个属性,get或set不是必须成对出现




    Proxy

    Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)

    var proxy = new Proxy(target, handler);
    
    • 1
    • target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

    • handler: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 proxy 的行为。

    Proxy实例

    const person = {
      name: "superMan",
    };
    
    const proxy = new Proxy(man, {
      get(target, property, receiver) {
        console.log(`正在获取 ${property} 属性`)
        return target[property];
      },
    });
    
    console.log(proxy.name);
    console.log(proxy.age);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Proxy 构造函数接受 person 对象为代理目标,创建了一个代理对象。在创建代理对象时,我们定义了一个 get 捕获器,用于捕获代理目标属性读取的操作。 捕获器的作用就是用于拦截用户对目标对象属性读取的相关操作,在这些操作传播到目标对象之前,会先调用对应的捕获器函数,从而拦截并修改相应的行为。

    代理对象必须与被代理的对象具有相同特性(这里说的特性指的是,对象是否冻结,是否密封,是否可扩展),即被代理的对象不能修改,使用代理对象的set方法也会报错。再例如,被代理的对象被冻结,get方法不能返回与原值不同的值;getOwnPropertyDescriptor必须返回描述符对象。总之一句,被代理对象不能改的,代理对象同样不能改;代理对象的方法的返回值类型必须与被代理对象一致。

    注:控制器对象内方法的 this,控制器对象内方法的this指向控制器对象本身

    Proxy 原型

    Proxy 实例也可以作为其他对象的原型对象

    var proxy = new Proxy({}, {
      get: function(target, property) {
        return 35;
      }
    });
     
    let obj = Object.create(proxy);
    obj.time  // 35
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    捕获器

    在创建 Proxy 对象时,除了定义 get 捕获器 之外,我们还可以定义其他的捕获器。

    参数

    target:表示被代理的对象。
    propKey:表示属性名(或方法名)。
    receiver:表示该方法所在的代理对象。


    get(target, propKey, receiver)

    get方法用于拦截某个成员的读取操作(包括方法),上面已经简单给出了例子。下面给出代理数组的例子,使其可以用负数索引。

    let arr = [1, 2, 3, 4, 5];
    let proxy = new Proxy(arr, {
      get(target, prop) {
        let length = target.length,
          index = Number.parseInt(prop);
        if (index < 0) {
          return target[length + index]
        }
        if (index >= length) {
          return target[length - 1]
        }
        return target[index]
      }
    });
    console.log(proxy[-1]);   // 5
    console.log(proxy[10]);   // 5
    console.log(proxy[2]);    // 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    set(target, propKey, value, receiver)

    set方法用于拦截对象属性的设置。其中,value表示属性的目标值。比较实用的用法是,与 DOM 的数据绑定。

    需要注意的是!当存在二级属性时,如[{}, {}]{ a: {}, b: {} },对二级或以上的属性修改并不会触发set方法。

    如下例子:

    let listData = ['one', 'two', 'three'];
    let proxy = new Proxy(listData, {
      set(target, prop, value) {
        let dom = document.getElementsByClassName('list-item')[prop]
        dom ? dom.innerText = value : null;
        target[prop] = value;
      }
    })
    proxy[0] = 'changed-one';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    has(target, propKey)

    has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。

    注意,has​OwnProperty()for...in都不能触发has方法。

    注意,has方法只能返回布尔值,return非布尔值会自动转成布尔值。

    let arr = [1, 2, 3, 4];
    let proxyArr = new Proxy(arr, {
      has(target, prop) {
        return 'a'
      }
    })
    console.log('x' in proxyArr);    // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    deleteProperty(target, propKey)

    deleteProperty方法用于拦截delete操作,只能返回布尔值,非布尔值会被转成布尔值,代表属性是否被删除成功,省略默认返回false

    let obj = { a: 1, b: 2 }
    let proxyDel = new Proxy(obj, {
      deleteProperty(target, prop) {
        delete target[prop]
      }
    });
    console.log(delete proxyDel.a);   // false
    console.log(obj);   //  { b: 2 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    getOwnPropertyDescriptor(target, propKey)

    getOwnPropertyDescriptor方法拦截Object.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptors(),返回一个属性描述对象或者undefined

    注意,若返回值不是undefined,则返回值必须是包含[[configurable]]true的对象,该值为false或省略会导致报错;若其他描述符未定义,以默认值填充;若返回对象包含除描述符以外的属性,则该属性被忽略。

    //  undefined
    let proxy1 = new Proxy({}, {
      getOwnPropertyDescriptor(target, prop) {
        return undefined
      }
    });
    console.log(Object.getOwnPropertyDescriptor(proxy1, 'a'));
    // { value: undefined, writable: false, enumerable: false, configurable: true }
    let proxy2 = new Proxy({}, {
      getOwnPropertyDescriptor(target, prop) {
        return { configurable: true }
      }
    });
    console.log(Object.getOwnPropertyDescriptor(proxy2, 'a'));
    // 报错
    let proxy3 = new Proxy({}, {
      getOwnPropertyDescriptor(target, prop) {
        return { configurable: false }
      }
    });
    console.log(Object.getOwnPropertyDescriptor(proxy3, 'a'));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    defineProperty(target, propKey, propDesc)

    defineProperty方法拦截Object.defineProperty()Object.defineProperties()obj.prop = 'value'等修改/添加对象的属性的操作。该方法只能返回布尔值,非布尔值会转成布尔值,表示是否成功定义属性,返回fasle会导致报错。其中,propDesc表示属性的描述符。

    注意,如果对象被冻结(Object.freeze())或对象被密封(Object.seal())或对象不可扩展( Object.preventExtensions()),甚至是某个属性被密封([[configurable]]特性为false),对代理对象使用Object.defineProperty()都可能会报错。总之,和原对象的特性一致,不能被修改的被代理后同样不能被修改。

    let obj = { d: 4 }
    let proxyObj = new Proxy(obj, {
      defineProperty() {
        console.log('proxy-defineProperty');
        return true
      }
    })
    // 以下4种方法都会触发 defineProperty 方法
    Object.defineProperty(proxyObj, 'a', {});
    Object.defineProperties(proxyObj, { b: {} });
    proxyObj.c = 3;
    proxyObj.d = 5;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    apply(target, object, args)

    apply方法拦截函数的调用、callapplybind操作。其中,object表示函数上下文对象,args`表示函数的参数。

    let obj = { a: 10 }
    function fn() { }
    let proxyFn = new Proxy(fn, {
      apply(target, object, args) {
        console.log('proxyFn-apply');
      }
    })
    // 直接执行函数 会触发
    proxyFn();
    // apply 和 call 会触发
    proxyFn.apply(obj);
    proxyFn.call(obj);
    // bind 定义时不会触发 执行时触发
    let p = proxyFn.bind(obj);
    p();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    construct(target, args, receiver)

    construct方法用于拦截new命令。

    注意,被代理的函数不能是箭头函数,否则也会报错。

    注意,construct方法必须要return对象(包括函数、数组等),否则会报错。

    // 错误例子1
    let P1 = new Proxy(()=>{},{
      construct(){
        return {}
      }
    });
    let p1 = new P1();    // 报错,被代理函数不能是箭头函数
    // 错误例子2
    let P2 = new Proxy(function(){},{
      construct(){}
    });
    let p2 = new P2();    // 报错,construct必须返回对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    getPrototypeOf(target)

    getPrototypeOf方法主要用来拦截获取对象原型,返回值必须是null或对象。具体来说,拦截下面这些操作。

    Object.prototype.__proto__        // 只能在浏览器环境下执行
    Object.prototype.isPrototypeOf()        // 代理对象作为参数
    Object.getPrototypeOf()        // 代理对象作为参数
    Reflect.getPrototypeOf()        // 代理对象作为参数
    instanceof        // 代理对象在运算符的左边
    
    • 1
    • 2
    • 3
    • 4
    • 5

    下面给出简单例子:

    let proxy = new Proxy({},{
      getPrototypeOf(){
        console.log('proxy-getPrototypeOf');
        return null
      }
    });
    // 都会打印 'proxy-getPrototypeOf'
    let x1 = proxy.__proto__;   // 只能在浏览器下执行
    let x2 = {}.isPrototypeOf(proxy);
    let x3 = Object.getPrototypeOf(proxy);
    let x4 = Reflect.getPrototypeOf(proxy);
    let x5 = (proxy instanceof function(){});
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    setPrototypeOf(target, proto)

    setPrototypeOf方法主要用来拦截Object.setPrototypeOf()方法。和Object.setPrototypeOf()方法一样,必须返回被设置的对象,否则报错。其中,proto表示被设为原型的对象。

    注意,浏览器环境下可以通过__proto__设置原型对象,同样能触发setPrototypeOf方法。

    et proxy = new Proxy({}, {
      setPrototypeOf(target, proto) {
        console.log('proxy-setPrototypeOf')
        return target;
      }
    });
    // 都打印 'proxy-setPrototypeOf'
    Object.setPrototypeOf(proxy, {});
    proxy.__proto__ = {}  // 非浏览器环境下使用会报错复制代码
    isExtensible(target)
    isExtensible方法拦截Object.isExtensible()方法,必须返回布尔值,非布尔值会被转成布尔值,且返回值必须与源对象所对应的值相同,否则报错。
    
    // 错误示范
    let proxy = new Proxy({}, {
      isExtensible() {
        return false
      }
    });
    console.log(Object.isExtensible(proxy));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    preventExtensions(target)

    preventExtensions方法拦截Object.preventExtensions()方法,只能返回被设为不可扩展的被代理对象,否则报错。如下:

    let proxy = new Proxy({}, {
      preventExtensions(target) {
        console.log('proxy-preventExtensions')
        return Object.preventExtensions(target);
      }
    });
    Object.preventExtensions(proxy);  // proxy-preventExtensions
    // 错误示范
    let proxyErr = new Proxy({}, {
      preventExtensions(target) {
        return {}
      }
    });
    Object.preventExtensions(proxyErr);  // 报错
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ownKeys(target)

    ownKeys方法用来拦截对象自身所有属性的读取操作,必须返回数组或对象,否则报错。具体来说,拦截以下操作。

    Object.getOwnPropertyNames()
    Object.getOwnPropertySymbols()
    getOwnPropertyDescriptors()
    Object.entries()
    Object.keys()
    Object.values()
    `for...in循环`
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意,虽然可以返回非数组的对象,对于如Object.keys这种默认返回数组的方法,得到的结果会是一个空数组。

    注意,诸如Object.entries方法返回的数组不会含有Symbol属性名、不存在的属性名、不可遍历(enumerable)的属性名,与被代理对象的行为一致。

    Object.getOwnPropertySymbols及其他方法同理,与被代理对象一致。

    看下面简单例子:

    let proxy = new Proxy({ a: 1, b: 2 }, {
      ownKeys() {
        console.log('proxy-ownKeys');
        return ['a', 'c']
      }
    });
    // 都会打印 'proxy-ownKeys'
    let x1 = Object.entries(proxy);
    let x2 = Object.keys(proxy);
    let x3 = Object.values(proxy);
    for (let x4 in proxy) { }
    let x5 = Object.getOwnPropertyNames(proxy);
    let x6 = Object.getOwnPropertyDescriptors(proxy);
    let x7 = Object.getOwnPropertySymbols(proxy);
    // entries、keys、values 都会过滤不可能显示的属性,跟被代理对象一致
    console.log(x1);    // [ [ 'a', 1 ] ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Proxy.revocable()

    Proxy.revocable()方法与new Proxy()用法类似,都是依次传入被代理对象和控制器对象,返回一个包含proxy对象和revoke方法的对象。

    其中,proxy对象和上面描述的一致,revoke方法用于解除proxy对象的代理,一旦取消将无法继续使用。

    let proxyR = Proxy.revocable({ a: 1 }, {});
    console.dir(proxyR);        // { proxy: { a: 1 }, revoke: [Function] }
    console.log(proxyR.proxy);  // { a: 1 }
    proxyR.revoke();
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    新中新身份证阅读器驱动下载sdk DKQ-A16D
    Java基础(四)
    【数学基础】P问题-NP问题-NP-c问题-NP-hard问题常见辨析
    数组的定义
    音频基础学习——声音的本质、术语与特性
    四平巨元无线监控解决方案
    C++模板初阶
    LeetCode高频题76. 最小覆盖子串:欠账还债还款问题,子串考虑i开头的情况所有答案更新一波
    文心一言 VS 讯飞星火 VS chatgpt (136)-- 算法导论11.3 2题
    Altium Designer21使用说明-更新中
  • 原文地址:https://blog.csdn.net/Raccon_/article/details/128113817