js 代理与反射 Proxy

《JavaScript高级程序设计》读书笔记

代理基础

创建空代理

const target = {
    id: 'target'
};
const handler = {};
const proxy = new Proxy(target, handler);

可撤销代理

revoke 撤销函数是幂等的。

const target = {
    id: 'target'
};
const handle = {
    get() {
        return 'iiiiiii';
    }
}
const {proxy, revoke} = Proxy.revocable(target, handle);
revoke();

实用反射API

反射方法使用与颗粒度的对象控制与操作

  1.  反射API并不局限于捕获处理程序
  2. 大多数反射API方法在Object 类型上有对应的方法

1. 状态标记

很多反射方法返回称作状态标记的布尔值,表示意图执行的操作成功与否。

const o = {}
try {
    Object.defineProperty(o, 'foo', 'bar');
    console.log('success');
} catch(e) {
    console.log('fail');
}

// 重构后
if(Reflect.defineProperty(o, 'foo', {value:'bar'})) {
    console.log('success');
}else {
    console.log('fail');
}

以下反射方法会提供状态标记:

  • Reflect.defineProperty()
  • Reflect.preventExtensions()
  • Reflect.setPrototypeOf()
  • Reflect.set()
  • Reflect.deleteProperty()

2. 用一等函数代替操作符

以下反射方法提供只有通过操作符才能完成的操作

  • Reflect.get() 可以替代对象属性访问操作符
  • Reflect.set() 可以替代=赋值操作符
  • Reflect.has() 可以替代in 操作符或with()
  • Reflect.deleteProperty() 可以替代delete 操作符
  • Reflect.constructor() 可以替代new 操作符

3. 安全地应用函数

在通过apply 方法调用函数时,被调用的函数可能也定义了自己的apply 属性(虽然可能性很小)。为了绕过这个问题,如下:

// 方法一:使用定义在Function 原型上的apply 方法
Function.prototype.apply.call(myFunc, thisVal, argumentList);
// 方法二
Reflect.apply(myFunc, thisVal, argumentList);

代理另一个代理

在一个目标对象之上构建多层拦截网

const target = {
    id: 'target'
};
const firstProxy = new Proxy(target, {
    get() {
        console.log('first Proxy');
        return Reflect.get(...arguments);
    }
});
const secondProxy = new Proxy(firstProxy, {
    get() {
        console.log('second Proxy');
        return Reflect.get(...arguments);
    }
});
console.log(secondProxy.id);
// second Proxy
// first Proxy
// target

代理的问题与不足

  • 代理中的this
  • 代理与内部槽位

代理捕获器与反射方法

get()

get() 捕获器会在获取属性值的操作中被调用。对应反射API为 Reflect.get()

const myTarget = {};
const proxy = new Proxy(myTarget, {
    get(target, property, receiver) {
        console.log('get()');
        return Reflect.get(...arguments);
    }
});
proxy.foo;    // get()
  1. 返回值:返回值无限制
  2. 拦截的操作
    1. proxy.property
    2. proxy[property]
    3. Object.create(proxy)[property]
    4. Reflect.get(proxy, property, receiver)
  3. 捕获器处理程序参数
    1. target:目标对象
    2. property:引用的目标对象上的字符串键属性
    3. receiver:代理对象或继承代理对象的对象
  4. 捕获器不变式
    如果target.property 不可写且不可配置,则处理程序返回的值必须与target.property 匹配。
    如果target.property 不可配置且[[Get]] 特性为undefined,处理程序的返回值必须是undefined

set()

set() 捕获器会在设置属性值的操作中被调用。对应的反射API为Reflect.set()

const myTarget = {};
const proxy = new Proxy(myTarget, {
    set(target, property, value, receiver) {
        console.log('set()');
        return Reflect.set(...arguments)
    }
});
proxy.foo = 'bar';
// set()
  1. 返回值:返回 true 表示成功;返回 false 表示失败,严格模式下会抛出 TypeError。
  2. 拦截的操作
    1. proxy.property = value
    2. proxy[property] = value
    3. Object.create(proxy)[property] = value
    4. Reflect.set(proxy, property, receiver)
  3. 捕获器处理程序参数
    1. target:目标对象
    2. property:引用的目标对象上的字符串键属性
    3. value:要赋给属性的值
    4. receiver:接收最初赋值的对象
  4. 捕获器不变式
    如果target.property 不可写且不可配置,则不能修改目标属性的值。
    如果 target.property 不可配置且[[Set]]特性为 undefined,则不能修改目标属性的值。

has()

如果 target.property 不可配置且[[Set]]特性为 undefined,则不能修改目标属性的值。

const myTarget = {};
const proxy = new Proxy(myTarget, {
    has(target, property) {
        console.log('has()');
        return Reflect.has(...arguments)
    }
});
'foo' in proxy;
// has()
  1. 返回值:必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值
  2. 拦截的操作
    1. property in proxy
    2. property in Object.create(proxy)
    3. with(proxy) {(property);}
    4. Reflect.has(proxy, property)
  3. 捕获器处理程序参数
    1. target:目标对象
    2. property:引用的目标对象上的字符串键属性
  4. 捕获器不变式
    如果 target.property 存在且不可配置,则处理程序必须返回 true。
    如果 target.property 存在且目标对象不可扩展,则处理程序必须返回 true。

defineProperty()

defineProperty()捕获器会在 Object.defineProperty()中被调用。对应的反射 API 方法为
Reflect.defineProperty()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
    defineProperty(target, property, descriptor) {
        console.log('defineProperty()');
        return Reflect.defineProperty(...arguments)
    }
});
Object.defineProperty(proxy, 'foo', { value: 'bar' });
// defineProperty()
  1. 返回值:必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值。
  2. 拦截的操作
    1. Object.defineProperty(proxy, property, descriptor)
    2. Reflect.defineProperty(proxy, property, descriptor)
  3. 捕获器处理程序参数
    1. target:目标对象
    2. property:引用的目标对象上的字符串键属性
    3. descriptor:包含可选的 enumerable、 configurable、 writable、 value、 get 和 set
      定义的对象。
  4. 捕获器不变式
    如果目标对象不可扩展,则无法定义属性。
    如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性。
    如果目标对象有一个不可配置的属性,则不能添加同名的可配置属性。

getOwnPropertyDescriptor()

getOwnPropertyDescriptor()捕获器会在 Object.getOwnPropertyDescriptor()中被调用。对应的反射 API 方法为 Reflect.getOwnPropertyDescriptor()

const myTarget = {};
const proxy = new Proxy(myTarget, {
    getOwnPropertyDescriptor(target, property) {
        console.log('getOwnPropertyDescriptor()');
        return Reflect.getOwnPropertyDescriptor(...arguments)
    }
});
Object.getOwnPropertyDescriptor(proxy, 'foo');
// getOwnPropertyDescriptor()
  1. 返回值:必须返回对象,或者在属性不存在时返回 undefined。
  2. 拦截的操作
    1. Object.getOwnPropertyDescriptor(proxy, property)
    2. Reflect.getOwnPropertyDescriptor(proxy, property)
  3. 捕获器处理程序参数
    1. target:目标对象
    2. property:引用的目标对象上的字符串键属性
  4. 捕获器不变式
    如果自有的 target.property 存在且不可配置,则处理程序必须返回一个表示该属性存在的
    对象。
    如果自有的 target.property 存在且可配置,则处理程序必须返回表示该属性可配置的对象。
    如果自有的 target.property 存在且 target 不可扩展,则处理程序必须返回一个表示该属性存
    在的对象。
    如果 target.property 不存在且 target 不可扩展,则处理程序必须返回 undefined 表示该属
    性不存在。
    如果 target.property 不存在,则处理程序不能返回表示该属性可配置的对象。

deleteProperty()

deleteProperty()捕获器会在 delete 操作符中被调用。对应的反射 API 方法为 Reflect.
deleteProperty()。
 

const myTarget = {};
const proxy = new Proxy(myTarget, {
    deleteProperty(target, property) {
        console.log('deleteProperty()');
        return Reflect.deleteProperty(...arguments)
    }
});
delete proxy.foo
// deleteProperty()
  1. 返回值:必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。
  2. 拦截的操作
    1. delete proxy.property
    2. delete proxy[property]
    3. Reflect.deleteProperty(proxy, property)
  3. 捕获器处理程序参数
    1. target:目标对象
    2. property:引用的目标对象上的字符串键属性
  4. 捕获器不变式
    如果自有的 target.property 存在且不可配置,则处理程序不能删除这个属性。

ownKeys()

ownKeys()捕获器会在 Object.keys()及类似方法中被调用。对应的反射 API 方法为 Reflect.
ownKeys()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
    ownKeys(target) {
        console.log('ownKeys()');
        return Reflect.ownKeys(...arguments)
    }
});
Object.keys(proxy);
// ownKeys()
  1. 返回值:必须返回包含字符串或符号的可枚举对象。
  2. 拦截的操作
    1. Object.getOwnPropertyNames(proxy)
    2. Object.getOwnPropertySymbols(proxy)
    3. Object.keys(proxy)
    4. Reflect.ownKeys(proxy)
  3. 捕获器处理程序参数
    1. target:目标对象
  4. 捕获器不变式
    返回的可枚举对象必须包含 target 的所有不可配置的自有属性。
    如果 target 不可扩展,则返回可枚举对象必须准确地包含自有属性键

getPrototypeOf()

getPrototypeOf()捕获器会在 Object.getPrototypeOf()中被调用。对应的反射 API 方法为
Reflect.getPrototypeOf()

const myTarget = {};
const proxy = new Proxy(myTarget, {
    getPrototypeOf(target) {
        console.log('getPrototypeOf()');
        return Reflect.getPrototypeOf(...arguments)
    }
});
Object.getPrototypeOf(proxy);
// getPrototypeOf()
  1. 返回值:必须返回对象或 null
  2. 拦截的操作
    1. Object.getPrototypeOf(proxy)
    2. Reflect.getPrototypeOf(proxy)
    3. proxy.__proto__
    4. Object.prototype.isPrototypeOf(proxy)
    5. proxy instanceof Object
  3. 捕获器处理程序参数
    1. target:目标对象
  4. 捕获器不变式
    如果 target 不可扩展,则 Object.getPrototypeOf(proxy)唯一有效的返回值就是 Object.
    getPrototypeOf(target)的返回值。

setPrototypeOf()

setPrototypeOf()捕获器会在 Object.setPrototypeOf()中被调用。对应的反射 API 方法为
Reflect.setPrototypeOf()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
    setPrototypeOf(target, prototype) {
        console.log('setPrototypeOf()');
        return Reflect.setPrototypeOf(...arguments)
    }
});
Object.setPrototypeOf(proxy, Object);
// setPrototypeOf()
  1. 返回值:必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值。
  2. 拦截的操作
    1. Object.setPrototypeOf(proxy)
    2. Reflect.setPrototypeOf(proxy)
  3. 捕获器处理程序参数
    1. target:目标对象
    2. prototype: target 的替代原型,如果是顶级原型则为 null。
  4. 捕获器不变式
    如果 target 不可扩展,则唯一有效的 prototype 参数就是 Object.getPrototypeOf(target)
    的返回值。

isExtensible()

isExtensible()捕获器会在 Object.isExtensible()中被调用。对应的反射 API 方法为
Reflect.isExtensible()。

const proxy = new Proxy(myTarget, {
    isExtensible(target) {
        console.log('isExtensible()');
        return Reflect.isExtensible(...arguments)
    }
});
Object.isExtensible(proxy);
// isExtensible()
  1. 返回值:必须返回布尔值,表示 target 是否可扩展。返回非布尔值会被转型为布尔值。
  2. 拦截的操作
    1. Object.isExtensible(proxy)
    2. Reflect.isExtensible(proxy)
  3. 捕获器处理程序参数
    1. target:目标对象
  4. 捕获器不变式
    如果 target 可扩展,则处理程序必须返回 true。
    如果 target 不可扩展,则处理程序必须返回 false。

preventExtensions()

preventExtensions()捕获器会在 Object.preventExtensions()中被调用。对应的反射 API
方法为 Reflect.preventExtensions()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
    preventExtensions(target) {
        console.log('preventExtensions()');
        return Reflect.preventExtensions(...arguments)
    }
});
Object.preventExtensions(proxy);
// preventExtensions()
  1. 返回值:必须返回布尔值,表示 target 是否已经不可扩展。返回非布尔值会被转型为布尔值。
  2. 拦截的操作
    1. Object.preventExtensions(proxy)
    2. Reflect.preventExtensions(proxy)
  3. 捕获器处理程序参数
    1. target:目标对象
  4. 捕获器不变式
    如果 Object.isExtensible(proxy)是 false,则处理程序必须返回 true。

apply()

apply()捕获器会在调用函数时中被调用。对应的反射 API 方法为 Reflect.apply()。

const myTarget = () => {};
const proxy = new Proxy(myTarget, {
    apply(target, thisArg, ...argumentsList) {
        console.log('apply()');
        return Reflect.apply(...arguments)
    }
});
proxy();
// apply()
  1. 返回值:返回值无限制。
  2. 拦截的操作
    1. proxy(...argumentsList)
    2. Function.prototype.apply(thisArg, argumentsList)
    3. Function.prototype.call(thisArg, ...argumentsList)
    4. Reflect.apply(target, thisArgument, argumentsList
  3. 捕获器处理程序参数
    1. target:目标对象。
    2. thisArg:调用函数时的 this 参数。
    3. argumentsList:调用函数时的参数列表
  4. 捕获器不变式
    target:必须是一个函数对象。

construct()

construct()捕获器会在 new 操作符中被调用。对应的反射 API 方法为 Reflect.construct()。

const myTarget = function() {};
const proxy = new Proxy(myTarget, {
    construct(target, argumentsList, newTarget) {
        console.log('construct()');
        return Reflect.construct(...arguments)
    }
});
new proxy;
// construct()
  1. 返回值:必须返回一个对象。
  2. 拦截的操作
    1. new proxy(...argumentsList)
    2. Reflect.construct(target, argumentsList, newTarget)
  3. 捕获器处理程序参数
    1. target:目标构造函数。
    2. argumentsList:传给目标构造函数的参数列表。
    3. newTarget:最初被调用的构造函数。
  4. 捕获器不变式
    target 必须可以用作构造函数。

代理模式

跟踪属性模式

监控对象何时被访问

const user = {
    name: 'Jake'
};
const proxy = new Proxy(user, {
    get(target, property, receiver) {
        console.log(`Getting ${property}`);
        return Reflect.get(...arguments);
    },
    set(target, property, value, receiver) {
        console.log(`Setting ${property}=${value}`);
        return Reflect.set(...arguments);
    }
});
proxy.name; // Getting name
proxy.age = 27; // Setting age=27

隐藏属性

const hiddenProperties = ['foo', 'bar'];
const targetObject = {
    foo: 1,
    bar: 2,
    baz: 3
};
const proxy = new Proxy(targetObject, {
    get(target, property) {
        if (hiddenProperties.includes(property)) {
            return undefined;
        } else {
            return Reflect.get(...arguments);
        }
    },
    has(target, property) {
        if (hiddenProperties.includes(property)) {
            return false;
        } else {
            return Reflect.has(...arguments);
        }
    }
});
// get()
console.log(proxy.foo); // undefined
console.log(proxy.bar); // undefined
console.log(proxy.baz); // 3
// has()
console.log('foo' in proxy); // false
console.log('bar' in proxy); // false
console.log('baz' in proxy); // true

属性验证

const target = {
    onlyNumbersGoHere: 0
};
const proxy = new Proxy(target, {
    set(target, property, value) {
        if (typeof value !== 'number') {
            return false;
        } else {
            return Reflect.set(...arguments);
        }
    }
});
proxy.onlyNumbersGoHere = 1;
console.log(proxy.onlyNumbersGoHere); // 1
proxy.onlyNumbersGoHere = '2';
console.log(proxy.onlyNumbersGoHere); // 1

函数与构造函数参数验证

function median(...nums) {
    return nums.sort()[Math.floor(nums.length / 2)];
}
const proxy = new Proxy(median, {
    apply(target, thisArg, argumentsList) {
        for (const arg of argumentsList) {
            if (typeof arg !== 'number') {
                throw 'Non-number argument provided';
            }
        }
        return Reflect.apply(...arguments);
    }
});
console.log(proxy(4, 7, 1)); // 4
console.log(proxy(4, '7', 1));
// Error: Non-number argument provided
类似地,可以要求实例化时必须给构造函数传参:
class User {
    constructor(id) {
        this.id_ = id;
    }
}
const proxy = new Proxy(User, {
    construct(target, argumentsList, newTarget) {
        if (argumentsList[0] === undefined) {
            throw 'User cannot be instantiated without id';
        } else {
            return Reflect.construct(...arguments);
        }
    }
});
new proxy(1);
new proxy();
// Error: User cannot be instantiated without id

数据绑定与可观察对象

const userList = [];
class User {
    constructor(name) {
        this.name_ = name;
    }
}
const proxy = new Proxy(User, {
    construct() {
        const newUser = Reflect.construct(...arguments);
        userList.push(newUser);
        return newUser;
    }
});
new proxy('John');
new proxy('Jacob');
new proxy('Jingleheimerschmidt');
console.log(userList); // [User {}, User {}, User{}]
const userList = [];
function emit(newValue) {
    console.log(newValue);
}
const proxy = new Proxy(userList, {
    set(target, property, value, receiver) {
        const result = Reflect.set(...arguments);
        if (result) {
            emit(Reflect.get(target, property, receiver));
        }
        return result;
    }
});
proxy.push('John');
// John
proxy.push('Jacob');
// Jacob
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞天巨兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值