30. 说说你对Proxy和Reflect的理解?
问题解析
Proxy 和 Reflect 是 ES6 引入的元编程特性,它们提供了拦截和操作对象行为的能力。面试中主要考察它们的基本用法、拦截器(trap)机制、使用场景以及与 Object.defineProperty 的区别。
核心概念
1. Proxy(代理)
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
// 基本语法
const proxy = new Proxy(target, handler);
// target:要代理的目标对象
// handler:包含拦截器的对象
// 示例
const target = {
name: 'Alice',
age: 25
};
const handler = {
get(target, prop) {
console.log(`Getting ${prop}`);
return target[prop];
},
set(target, prop, value) {
console.log(`Setting ${prop} = ${value}`);
target[prop] = value;
return true;
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // Getting name -> Alice
proxy.age = 26; // Setting age = 26
2. Reflect(反射)
Reflect 是一个内置对象,提供拦截 JavaScript 操作的方法。它的方法与 Proxy 的 handler 方法一一对应。
// Reflect 提供了一系列静态方法
const obj = { name: 'Bob' };
// 替代 Object 方法
Reflect.get(obj, 'name'); // 'Bob'
Reflect.set(obj, 'age', 30); // true
Reflect.has(obj, 'name'); // true
Reflect.deleteProperty(obj, 'age'); // true
Reflect.ownKeys(obj); // ['name']
// 替代 new 操作符
class Person {
constructor(name) {
this.name = name;
}
}
const person = Reflect.construct(Person, ['Alice']);
// 替代 Function.prototype.apply/call
function greet(greeting) {
return `${greeting}, ${this.name}`;
}
Reflect.apply(greet, person, ['Hello']); // 'Hello, Alice'
详细解答
1. Proxy 的拦截器(Traps)
const target = {
name: 'Alice',
_private: 'secret',
greet() {
return `Hello, ${this.name}`;
}
};
const handler = {
// 读取属性时触发
get(target, prop, receiver) {
if (prop.startsWith('_')) {
throw new Error('Private property access denied');
}
console.log(`GET: ${String(prop)}`);
return Reflect.get(target, prop, receiver);
},
// 设置属性时触发
set(target, prop, value, receiver) {
if (prop.startsWith('_')) {
throw new Error('Cannot modify private property');
}
if (prop === 'age' && (value < 0 || value > 150)) {
throw new Error('Invalid age');
}
console.log(`SET: ${String(prop)} = ${value}`);
return Reflect.set(target, prop, value, receiver);
},
// 判断属性是否存在时触发
has(target, prop) {
if (prop.startsWith('_')) return false;
return Reflect.has(target, prop);
},
// 删除属性时触发
deleteProperty(target, prop) {
if (prop.startsWith('_')) {
throw new Error('Cannot delete private property');
}
console.log(`DELETE: ${String(prop)}`);
return Reflect.deleteProperty(target, prop);
},
// 获取属性描述符时触发
getOwnPropertyDescriptor(target, prop) {
if (prop.startsWith('_')) return undefined;
return Reflect.getOwnPropertyDescriptor(target, prop);
},
// 定义属性时触发
defineProperty(target, prop, descriptor) {
console.log(`DEFINE: ${String(prop)}`);
return Reflect.defineProperty(target, prop, descriptor);
},
// 获取所有属性键时触发
ownKeys(target) {
return Reflect.ownKeys(target).filter(key => !String(key).startsWith('_'));
}
};
const proxy = new Proxy(target, handler);
// 使用示例
console.log(proxy.name); // GET: name -> Alice
proxy.age = 25; // SET: age = 25
console.log('name' in proxy); // true
console.log('_private' in proxy); // false
2. 函数代理
function sum(a, b) {
return a + b;
}
const proxySum = new Proxy(sum, {
apply(target, thisArg, args) {
console.log(`Called with args: ${args}`);
if (args.some(arg => typeof arg !== 'number')) {
throw new TypeError('All arguments must be numbers');
}
return Reflect.apply(target, thisArg, args);
},
construct(target, args) {
throw new Error('Cannot instantiate a function');
}
});
console.log(proxySum(1, 2)); // Called with args: 1,2 -> 3
// proxySum('a', 'b'); // TypeError
// 构造函数代理
class Person {
constructor(name) {
this.name = name;
}
}
const ProxyPerson = new Proxy(Person, {
construct(target, args, newTarget) {
console.log(`Creating instance with args: ${args}`);
const instance = Reflect.construct(target, args, newTarget);
instance.createdAt = new Date();
return instance;
}
});
const person = new ProxyPerson('Alice');
console.log(person.createdAt); // Date object
3. 数组代理
const array = [1, 2, 3];
const proxyArray = new Proxy(array, {
get(target, prop) {
if (prop === 'last') {
return target[target.length - 1];
}
if (prop === 'first') {
return target[0];
}
return Reflect.get(target, prop);
},
set(target, prop, value) {
if (prop === 'length') {
if (value < 0) throw new Error('Length cannot be negative');
}
console.log(`Setting index ${prop} to ${value}`);
return Reflect.set(target, prop, value);
}
});
console.log(proxyArray.first); // 1
console.log(proxyArray.last); // 3
proxyArray.push(4); // Setting index 3 to 4
深入理解
1. Proxy 与 Object.defineProperty 的区别
// Object.defineProperty - Vue 2.x 使用的方式
const obj1 = {};
Object.defineProperty(obj1, 'name', {
get() {
return this._name;
},
set(value) {
this._name = value;
},
enumerable: true,
configurable: true
});
// 缺点:
// 1. 只能拦截已存在的属性
// 2. 无法监听数组索引和长度的变化(需要特殊处理)
// 3. 无法监听新增属性
// 4. 需要递归遍历所有属性
// Proxy - Vue 3.x 使用的方式
const obj2 = {};
const proxy2 = new Proxy(obj2, {
get(target, prop) {
return target[prop];
},
set(target, prop, value) {
target[prop] = value;
return true;
}
});
// 优点:
// 1. 可以拦截新增属性
// 2. 可以监听数组变化
// 3. 支持更多拦截操作
// 4. 不需要递归遍历
// 对比示例
const arr = [];
// Object.defineProperty 方式
const arrProxy1 = [];
Object.defineProperty(arrProxy1, 'push', {
value: function(...args) {
console.log('Array modified');
return Array.prototype.push.apply(this, args);
}
});
// Proxy 方式
const arrProxy2 = new Proxy([], {
set(target, prop, value) {
console.log(`Setting ${prop} = ${value}`);
return Reflect.set(target, prop, value);
}
});
arrProxy2.push(1); // Setting 0 = 1, Setting length = 1
2. 可撤销代理
const { proxy, revoke } = Proxy.revocable({ data: 'secret' }, {
get(target, prop) {
return target[prop];
}
});
console.log(proxy.data); // 'secret'
revoke(); // 撤销代理
// console.log(proxy.data); // TypeError: Cannot perform 'get' on a proxy that has been revoked
3. 嵌套代理
function createDeepProxy(target, handler) {
const cache = new WeakMap();
function createProxy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (cache.has(obj)) {
return cache.get(obj);
}
const proxy = new Proxy(obj, {
...handler,
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
return createProxy(value);
}
});
cache.set(obj, proxy);
return proxy;
}
return createProxy(target);
}
const nested = {
level1: {
level2: {
level3: 'deep value'
}
}
};
const deepProxy = createDeepProxy(nested, {
get(target, prop) {
console.log(`Accessing: ${prop}`);
return Reflect.get(target, prop);
}
});
console.log(deepProxy.level1.level2.level3);
// Accessing: level1
// Accessing: level2
// Accessing: level3
// 'deep value'
最佳实践
1. 数据验证
function createValidator(schema) {
return new Proxy({}, {
set(target, prop, value) {
const validator = schema[prop];
if (validator) {
const isValid = validator(value);
if (!isValid) {
throw new Error(`Invalid value for ${String(prop)}: ${value}`);
}
}
return Reflect.set(target, prop, value);
}
});
}
const userSchema = {
name: (v) => typeof v === 'string' && v.length >= 2,
age: (v) => typeof v === 'number' && v >= 0 && v <= 150,
email: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)
};
const user = createValidator(userSchema);
user.name = 'Alice'; // OK
user.age = 25; // OK
user.email = 'alice@example.com'; // OK
// user.age = -1; // Error: Invalid value for age: -1
2. 私有属性保护
function withPrivateProperties(obj) {
const privatePrefix = '_';
return new Proxy(obj, {
get(target, prop) {
if (typeof prop === 'string' && prop.startsWith(privatePrefix)) {
throw new Error(`Access to private property ${prop} is denied`);
}
return Reflect.get(target, prop);
},
set(target, prop, value) {
if (typeof prop === 'string' && prop.startsWith(privatePrefix)) {
throw new Error(`Modification of private property ${prop} is denied`);
}
return Reflect.set(target, prop, value);
},
has(target, prop) {
if (typeof prop === 'string' && prop.startsWith(privatePrefix)) {
return false;
}
return Reflect.has(target, prop);
},
ownKeys(target) {
return Reflect.ownKeys(target).filter(
key => typeof key !== 'string' || !key.startsWith(privatePrefix)
);
},
getOwnPropertyDescriptor(target, prop) {
if (typeof prop === 'string' && prop.startsWith(privatePrefix)) {
return undefined;
}
return Reflect.getOwnPropertyDescriptor(target, prop);
}
});
}
const myObj = withPrivateProperties({
public: 'visible',
_private: 'hidden'
});
console.log(myObj.public); // 'visible'
// console.log(myObj._private); // Error
console.log(Object.keys(myObj)); // ['public']
3. 响应式系统(简化版 Vue3)
function reactive(target) {
const deps = new Map();
function getDep(key) {
if (!deps.has(key)) {
deps.set(key, new Set());
}
return deps.get(key);
}
return new Proxy(target, {
get(target, key) {
const dep = getDep(key);
if (activeEffect) {
dep.add(activeEffect);
}
return Reflect.get(target, key);
},
set(target, key, value) {
const result = Reflect.set(target, key, value);
const dep = getDep(key);
dep.forEach(effect => effect());
return result;
}
});
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
// 使用
const state = reactive({ count: 0 });
watchEffect(() => {
console.log('Count:', state.count);
});
state.count++; // 触发 effect,输出: Count: 1
state.count++; // 触发 effect,输出: Count: 2
4. 日志和调试
function createLogger(target, name = 'Object') {
return new Proxy(target, {
get(target, prop) {
const value = Reflect.get(target, prop);
console.log(`[${name}] GET ${String(prop)} =`, value);
return value;
},
set(target, prop, value) {
const oldValue = target[prop];
console.log(`[${name}] SET ${String(prop)}:`, oldValue, '->', value);
return Reflect.set(target, prop, value);
},
apply(target, thisArg, args) {
console.log(`[${name}] CALL with args:`, args);
return Reflect.apply(target, thisArg, args);
}
});
}
const loggedArray = createLogger([], 'MyArray');
loggedArray.push(1); // [MyArray] GET push = function
// [MyArray] SET 0: undefined -> 1
// [MyArray] SET length: 0 -> 1
面试要点
- Proxy 的作用:创建对象的代理,拦截和自定义基本操作
- Reflect 的作用:提供一组静态方法,对应 Proxy 的拦截器
- 与 Object.defineProperty 的区别:Proxy 可以拦截更多操作,支持新增属性,性能更好
- 常用拦截器:get、set、has、deleteProperty、apply、construct 等
- 使用场景:数据验证、私有属性、响应式系统、日志记录
常见面试题
// 面试题 1:实现一个只读代理
function readonly(target) {
return new Proxy(target, {
set() {
console.warn('Cannot modify readonly object');
return true;
},
deleteProperty() {
console.warn('Cannot delete property from readonly object');
return true;
}
});
}
const readonlyObj = readonly({ name: 'Alice' });
// readonlyObj.name = 'Bob'; // Warning
// 面试题 2:实现一个缓存代理
function createCacheProxy(fn) {
const cache = new Map();
return new Proxy(fn, {
apply(target, thisArg, args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Cache hit');
return cache.get(key);
}
const result = Reflect.apply(target, thisArg, args);
cache.set(key, result);
return result;
}
});
}
const expensiveFn = createCacheProxy((n) => {
console.log('Computing...');
return n * n;
});
expensiveFn(5); // Computing... 25
expensiveFn(5); // Cache hit 25
// 面试题 3:Proxy 能否代理 null 或 undefined?
// 不能,会抛出 TypeError
// new Proxy(null, {}); // TypeError
// 面试题 4:如何实现链式调用的代理?
const chainable = new Proxy({}, {
get(target, prop) {
if (prop === 'getValue') {
return () => target._value;
}
return (value) => {
target._value = value;
return chainable;
};
}
});
const result = chainable.setA(1).setB(2).setC(3).getValue();
console.log(result); // 3 (最后设置的值)
// 面试题 5:Proxy 的性能考虑
// 1. Proxy 比 Object.defineProperty 性能好
// 2. 但 Proxy 仍然有开销,不适合高频调用场景
// 3. 可以使用缓存优化嵌套 Proxy
// 4. 生产环境注意内存泄漏(WeakMap 缓存)