JS-Proxy-Reflect

147 阅读3分钟

监听对象-Object.defineProperty

案例

  • 通过Object.defineProperty存储属性描述符来对属性的操作进行监听

    const obj = {
        name: "zzy",
        age: 22
    };
    
    // 监听对象中某一个key
    Object.defineProperty(obj, "name", {
        get: function () {
            console.log('被访问'); // 被访问
            return 'bbb'
        },
        set: function (value) {
            console.log('被设置', value); // 被设置 aaa
        },
    });
    console.log(obj.name); // bbb
    obj.name = "aaa";
    
    // 监听对象中所有的key
    Object.keys(obj).forEach(key => {
        let value = obj[key]
        Object.defineProperty(obj, key, {
            get() {
                console.log(`${key}被访问`);
                return value
            },
            set(newValue) {
                value = newValue
                console.log(`${key}的value被修改为${newValue}`);
            }
        })
    })
    obj.name = 'lll'
    obj.age = 23
    console.log(obj.name);
    console.log(obj.age);
    
    

缺点

  • Object.defineProperty设计初衷不是为了监听截止一个对象中的所有属性
    • 在定义属性时,只是为了定义普通的属性,但是后面强行将数据属性描述符变为了访问属性描述符
  • Object.defineProperty无法监听新增属性删除属性等丰富的操作

监听对象-Proxy(ES6)

  • ES6,新增了一个Proxy类,帮助我们创建一个代理
    • 监听一个对象操作之前,先创建一个代理对象Proxy对象
    • 之后对该对象的所有操作,都通过代理对象完成代理对象可以监听我们想要对原对象进行的操作

捕获器(trap)(13个)

set函数

  • 属性设置
  • target:目标对象(被监听的对象)
  • propertyKey:将被设置的属性key
  • value:新属性值
  • receiver:如果遇到 setterreceiver则为setter调用时的this值。

get函数

  • 属性读取
  • target:目标对象(被监听的对象)
  • propertyKey:将被设置的属性key
  • receiver:如果target对象中指定了getterreceiver则为getter调用时的this值。

has函数

  • 是否含有(in)
  • target:目标对象(被监听的对象)
  • propertyKey:将被设置的属性key

deleteProperty函数

  • 删除delete
  • target:目标对象(被监听的对象)
  • propertyKey:将被设置的属性key

getPrototypeOf

  • 获取对象原型(Object.getPrototypeOf)
  • target:目标对象(被监听的对象)

setPrototypeOf

  • 设置对象原型(Object.getPrototypeOf)
  • target:目标对象(被监听的对象)
  • prototype:新原型对象

isExtensible

  • 是否可以扩展(Object.isExtensible)
  • target:目标对象(被监听的对象)

preventExtensions

  • 阻止扩展(Object.preventExtensions)
  • target:目标对象(被监听的对象)

getOwnPropertyDescriptor

  • 获取属性描述符扩展(Object.getOwnPropertyDescriptor)
  • target:目标对象(被监听的对象)
  • propertyKey:将被设置的属性key

defineProperty

  • 定义属性描述符扩展(Object.defineProperty)
  • target:目标对象(被监听的对象)
  • propertyKey:将被设置的属性key
  • attributes:属性描述

ownKeys

  • 获取属性名(Object.getOwnPropertyNames)

  • 获取属性Symbol(Object.getOwnPropertySymbols)

  • target:目标对象(被监听的对象)

  • ArrayLike:属性

apply(监听函数对象)

  • 函数调用
  • target:目标函数(被监听的函数)
  • thisArg:绑定的this
  • argArray:参数

construct(监听函数对象)

  • new操作
  • target:目标函数(被监听的函数)
  • argArray:参数
  • newTarget:新目标函数(与target是同一个)

案例

  • new Proxy对象,传入需要监听的对象以及一个处理对象(handler)

    const p = new Proxy(target, handler)
    
  • 直接操作Proxy对象,而不是原对象,可以通过handler监听

监听普通对象

// 监听普通对象
const obj = {
    name: 'zzy',
    age: 22
}

const objProxy = new Proxy(obj, {
    // 获取值时的捕获器
    get(target, key) {
        console.log(`监听到`, target, `的${key}属性被访问`);
        return target[key]
    },
    // 设置值时的捕获器
    set(target, key, newValue) {
        console.log(`监听到`, target, `的${key}属性被修改为${newValue}`);
        target[key] = newValue
    },
    // 监听in的捕获器
    has(target, key) {
        console.log(`监听到`, target, `的${key}属性执行in操作`);
        return key in target
    },
    // 监听delete的捕获器
    deleteProperty(target, key) {
        console.log(`监听到`, target, `的${key}属性被删除`);
        delete target[key]
    }
})

// get
console.log(objProxy.name, objProxy.age); // zzy 22
// set
objProxy.name = 'lll'
objProxy.age = 23
// get
console.log(objProxy.name, objProxy.age); // lll 23
// in
console.log('name' in objProxy);
// delete
delete objProxy.age

监听函数对象

// 监听函数对象
function foo() {}

const fooProxy = new Proxy(foo, {
  // 监听apply的捕获器
  apply(target, thisArg, argArray) {
    console.log("对foo函数进行apply调用");
    return target.apply(thisArg, argArray);
  },
  // 监听new操作的捕获器
  construct(target, argArray, newTarget) {
    console.log("对foo函数进行new调用", newTarget);
    return new target(...argArray);
  },
});
// apply
fooProxy.apply({}, ["aa", "bb"]); // 对foo函数进行apply调用
// new
new fooProxy("a", "b"); // 对foo函数进行new调用 [Function: foo]

Reflect(ES6)

  • 早期ECMA没有考虑这种对对象本身操作如何设计会更加规范,所以将这些API放到了Object上,但是Object作为一个构造函数,这些操作实际上放到它身上并不合适

  • ES6,新增了一个API,它是一个对象reflect反射

    • 提供很多操作JS对象方法,类似Object中操作对象的方法
      • 比如Reflect.getPrototypeOf(target)
      • 比如Reflect.defineProperty(target, propertyKey, attributes)
    • in, delete操作符也被集中到了reflect对象上
  • 可以将Object原对象的操作,修改为Reflect来操作

常见方法(13个)(与proxy对应)

Reflect.set(target,propertyKey,value[,receiver])

  • 分配给函数属性,更新成功,返回true
  • target:目标对象
  • propertyKey:将被设置的属性key
  • value:新属性值
  • receiver:如果遇到 setterreceiver则为setter调用时的this值。

get(target,propertyKey[,receiver])函数

  • 属性读取/类似target[name]
  • target:目标对象
  • propertyKey:将被设置的属性key
  • receiver:如果target对象中指定了getterreceiver则为getter调用时的this值。

has(target,propertyKey)

  • 是否含有某个属性(in)
  • target:目标对象
  • propertyKey:将被设置的属性key

deleteProperty(target,propertyKey)

  • 删除delete
  • target:目标对象
  • propertyKey:将被设置的属性key

getPrototypeOf(target)

  • 获取对象原型(Reflect.getPrototypeOf)
  • target:目标对象

setPrototypeOf(target,prototype)

  • 设置对象原型(Reflect.getPrototypeOf)
  • target:目标对象
  • prototype:新原型对象

isExtensible(target)

  • 是否可以扩展(Reflect.isExtensible)
  • target:目标对象

preventExtensions(target)

  • 阻止扩展(Reflect.preventExtensions),返回boolean
  • target:目标对象

getOwnPropertyDescriptor(target,propertyKey)

  • 获取属性描述符扩展(Reflect.getOwnPropertyDescriptor)
  • target:目标对象
  • propertyKey:将被设置的属性key

defineProperty(target,propertyKey,attributes)

  • 定义属性描述符扩展(Reflect.defineProperty)
  • target:目标对象
  • propertyKey:将被设置的属性key
  • attributes:属性描述

ownKeys(target)

  • 返回一个包含所有自身属性(不包含继承属性)的数组(类似于Object.keys(),但不会受enumerable影响)
  • target:目标对象

apply(target,thisArg,argArray)

  • 函数调用/类似function.prototype.apply()
  • target:目标函数
  • thisArg:绑定的this
  • argArray:参数

Reflect.construct(target, argumentsList[, newTarget])

  • new操作/new target(...argArray)
  • target:目标函数
  • argArray:参数
  • newTarget:应使用其原型的构造函数

案例

  • const obj = {
        name: "zzy",
        age: 22,
    };
    
    const objProxy = new Proxy(obj, {
        get(target, key, receiver) {
            return Reflect.get(target, key);
        },
        set(target, key, newValue, receiver) {
            // 设置成功返回true
            const result = Reflect.set(target, key, newValue);
            // 根据是否设置成功
            if (result) {
            } else {
            }
        },
    });
    
    objProxy.name = "aaa";
    console.log(objProxy.name);
    

Receiver

  • 如果源对象(obj)getter/setter的访问器属性,就可以通过receiver改变访问器属性this指向为代理对象

  • receiver是创建出来的代理对象

    const obj = {
        _name: 'zzy',
        get name() {
            // 这里的this是obj对象
            return this._name
        },
        set name(newName) {
            this._name = newName
        }
    }
    
    const objProxy = new Proxy(obj, {
        get(target, key, receiver) {
            console.log('get被访问', key);
            // receiver是创建出来的代理对象(即objProxy),
            // receiver可以将原对象getter/setter的this指向改为代理对象
            return Reflect.get(target, key, receiver)
        },
        set(target, key, newValue, receiver) {
            console.log('set被访问', key);
            Reflect.set(target, key, newValue, receiver)
        }
    })
    
    objProxy.name ='aa'
    console.log(objProxy.name);
    

Construct

  • Reflect.construct(target, argumentsList[, newTarget])

  • 改变目标函数的原型

  • function Student(name, age) {
        this.name = name;
        this.age = age;
    }
    
    function Teacher() {}
    
    // 需求:执行Student函数的内容,但是创建出来的对象是Teacher 对象
    const teacher = Reflect.construct(Student, ['zzy', 22], Teacher)
    console.log(teacher); // Teacher { name: 'zzy', age: 22 }
    console.log(teacher.__proto__ === Teacher.prototype); // true