第 32 章 Proxy 与 Reflect

第 32 章 Proxy 与 Reflect

Proxy 和 Reflect 是 ES6 引入的两个高级特性。Proxy 就像一个"拦截器",可以拦截对对象的各种操作;Reflect 是 Object 的"替代者",提供了更合理的操作对象的方法。

32.1 Proxy

概念:代理器,拦截对象的所有操作

Proxy 就像一个"中间人"——你不能直接访问对象,而是通过 Proxy 来访问,Proxy 可以在访问过程中"动手脚"。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const target = { name: '小明', age: 18 };

const proxy = new Proxy(target, {
    // get 拦截读取操作
    get: function(target, property, receiver) {
        console.log('读取了属性:' + property);
        return target[property];
    },
    
    // set 拦截写入操作
    set: function(target, property, value, receiver) {
        console.log('设置了属性:' + property + ' = ' + value);
        target[property] = value;
        return true;
    }
});

console.log(proxy.name);  // 触发 get
proxy.age = 20;          // 触发 set

new Proxy(target, handler)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// target:被代理的目标对象
// handler:代理配置对象,包含各种拦截函数

const target = { message: 'Hello' };
const proxy = new Proxy(target, {
    // 拦截读取属性
    get(target, prop, receiver) {
        return target[prop];
    },
    // 拦截写入属性
    set(target, prop, value, receiver) {
        target[prop] = value;
        return true;
    }
});

get / set:属性读写拦截

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const user = {};

const proxy = new Proxy(user, {
    get: function(target, property, receiver) {
        if (!(property in target)) {
            throw new Error('属性 ' + property + ' 不存在');
        }
        return target[property];
    },
    
    set: function(target, property, value, receiver) {
        if (typeof value !== 'string') {
            throw new Error('属性值必须是字符串');
        }
        target[property] = value;
        return true;
    }
});

proxy.name = '小明'; // 成功
console.log(proxy.name); // 打印结果: 小明

proxy.age = 18; // 报错:属性值必须是字符串
console.log(proxy.name); // 报错:属性 age 不存在

has:in 运算符拦截

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const target = { name: '小明', age: 18 };

const proxy = new Proxy(target, {
    has: function(target, property) {
        console.log('检查属性是否存在:' + property);
        return property in target;
    }
});

console.log('name' in proxy); // 触发 has,打印结果: true
console.log('gender' in proxy); // 触发 has,打印结果: false

deleteProperty:delete 拦截

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const target = { name: '小明', age: 18 };

const proxy = new Proxy(target, {
    deleteProperty: function(target, property) {
        console.log('尝试删除属性:' + property);
        delete target[property];
        return true;
    }
});

delete proxy.name; // 触发 deleteProperty
console.log(target); // 打印结果: { age: 18 }

apply:函数调用拦截

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function sum(a, b) {
    return a + b;
}

const proxySum = new Proxy(sum, {
    apply: function(target, thisArg, args) {
        console.log('调用函数,参数:', args);
        return target.apply(thisArg, args);
    }
});

console.log(proxySum(3, 5)); // 打印结果: 8

construct:new 操作符拦截

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function Person(name) {
    this.name = name;
}

const ProxyPerson = new Proxy(Person, {
    construct: function(target, args, newTarget) {
        console.log('使用 new 创建对象');
        return new target(...args);
    }
});

const p = new ProxyPerson('小明'); // 触发 construct
console.log(p.name); // 打印结果: 小明

getOwnPropertyDescriptor / defineProperty / preventExtensions / ownKeys

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const target = { name: '小明', age: 18 };

const proxy = new Proxy(target, {
    getOwnPropertyDescriptor: function(target, prop) {
        console.log('获取属性描述符:' + prop);
        return Object.getOwnPropertyDescriptor(target, prop);
    },
    
    defineProperty: function(target, prop, descriptor) {
        console.log('定义属性:' + prop);
        return Object.defineProperty(target, prop, descriptor);
    },
    
    preventExtensions: function(target) {
        console.log('阻止扩展对象');
        return Object.preventExtensions(target);
    },
    
    ownKeys: function(target) {
        console.log('获取所有属性键');
        return Reflect.ownKeys(target);
    }
});

console.log(Object.keys(proxy)); // 触发 ownKeys

应用:响应式数据 / 数据验证 / 私有变量 / 观察者模式 / 链式调用

数据验证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function createValidator(schema) {
    return new Proxy({}, {
        set: function(target, property, value) {
            const validator = schema[property];
            if (validator && !validator(value)) {
                throw new Error('属性 ' + property + ' 的值无效');
            }
            target[property] = value;
            return true;
        }
    });
}

const user = createValidator({
    age: function(v) { return typeof v === 'number' && v >= 0; }
});

user.age = 18; // 成功
user.age = -5; // 报错:属性 age 的值无效

私有变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
function createPrivate(obj) {
    return new Proxy(obj, {
        get: function(target, prop) {
            if (prop.startsWith('_')) {
                throw new Error('不能访问私有属性 ' + prop);
            }
            return target[prop];
        },
        set: function(target, prop, value) {
            if (prop.startsWith('_')) {
                throw new Error('不能修改私有属性 ' + prop);
            }
            target[prop] = value;
            return true;
        }
    });
}

const user = createPrivate({ name: '小明', _secret: '123456' });
console.log(user.name); // 打印结果: 小明
console.log(user._secret); // 报错:不能访问私有属性 _secret

下一节,我们来学习 Reflect!

32.2 Reflect

概念:Object 的替代者,提供操作对象的方法

Reflect 是 ES6 引入的另一个新特性,它提供了一组操作对象的方法,和 Object 类似,但更合理、更一致。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Reflect 的方法
Reflect.get(target, property, receiver);
Reflect.set(target, property, value, receiver);
Reflect.has(target, property);
Reflect.deleteProperty(target, property);
Reflect.ownKeys(target);
Reflect.getOwnPropertyDescriptor(target, property);
Reflect.defineProperty(target, property, descriptor);
Reflect.preventExtensions(target);
Reflect.apply(target, thisArg, args);
Reflect.construct(target, args, newTarget);

Reflect.get / set / has / deleteProperty / ownKeys

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const target = { name: '小明', age: 18 };

// 获取属性
console.log(Reflect.get(target, 'name')); // 打印结果: 小明

// 设置属性
Reflect.set(target, 'age', 20);
console.log(target.age); // 打印结果: 20

// 检查属性
console.log(Reflect.has(target, 'name')); // 打印结果: true

// 删除属性
Reflect.deleteProperty(target, 'age');
console.log(target); // 打印结果: { name: '小明' }

// 获取所有属性键
console.log(Reflect.ownKeys(target)); // 打印结果: ['name']

Reflect.apply / construct / defineProperty

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// apply:调用函数
function greet(greeting, name) {
    return greeting + ', ' + name;
}
console.log(Reflect.apply(greet, null, ['Hello', '小明'])); // 打印结果: Hello, 小明

// construct:new 操作符
function Person(name) {
    this.name = name;
}
const p = Reflect.construct(Person, ['小明']);
console.log(p.name); // 打印结果: 小明

// defineProperty:定义属性
Reflect.defineProperty(target, 'age', { value: 18, writable: true });
console.log(target.age); // 打印结果: 18

Proxy + Reflect 实现透明代理

Proxy 和 Reflect 配合使用,可以实现"透明代理"——对代理对象的操作,都转发给目标对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const target = { name: '小明', age: 18 };

const proxy = new Proxy(target, {
    get: function(target, property, receiver) {
        // 使用 Reflect.get,确保 this 绑定正确
        return Reflect.get(target, property, receiver);
    },
    
    set: function(target, property, value, receiver) {
        // 使用 Reflect.set,确保 this 绑定正确
        return Reflect.set(target, property, value, receiver);
    },
    
    has: function(target, property) {
        return Reflect.has(target, property);
    },
    
    deleteProperty: function(target, property) {
        return Reflect.deleteProperty(target, property);
    }
});

console.log(proxy.name); // 打印结果: 小明
proxy.age = 20;
console.log(proxy.age); // 打印结果: 20
console.log('name' in proxy); // 打印结果: true

本章小结

本章我们学习了 Proxy 和 Reflect:

  1. Proxy:代理器,可以拦截对对象的所有操作(属性读写、函数调用、new 操作等)。
  2. Reflect:Object 的替代者,提供了更合理、更一致的操作对象的方法。
  3. 应用:数据验证、私有变量、响应式数据、链式调用等。

下一章,我们要学习 Symbol——JavaScript 的"独一无二"!