Search K
Appearance
Appearance
📊 SEO元描述:2024年最新ES6 Proxy代理教程,详解Proxy基本用法、handler方法、Proxy vs Object.defineProperty对比、实际应用场景。包含完整实战案例,适合JavaScript开发者掌握元编程技术。
核心关键词:ES6 Proxy代理2024、Proxy基本用法、handler方法、Proxy拦截器、JavaScript元编程、Object.defineProperty对比
长尾关键词:Proxy怎么用、Proxy和Object.defineProperty区别、JavaScript代理模式、Proxy实际应用、元编程技术详解
通过本节ES6 Proxy代理教程,你将系统性掌握:
为什么需要Proxy?这是ES6元编程能力提升的核心问题。Proxy提供了拦截和自定义对象操作的能力,让我们可以重新定义对象的基本行为,也是ES6+现代JavaScript的重要元编程工具。
💡 核心原则:Proxy让对象行为变得可控制,是实现高级JavaScript模式的强大工具
Proxy通过在目标对象和外部访问之间建立一个拦截层,可以对外部访问进行过滤和改写。
// 🎉 Proxy基本用法详解
console.log('=== Proxy基本概念 ===');
// 1. 基本的Proxy创建
const target = {
name: '张三',
age: 25
};
const handler = {
get(target, property, receiver) {
console.log(`访问属性: ${property}`);
return target[property];
},
set(target, property, value, receiver) {
console.log(`设置属性: ${property} = ${value}`);
target[property] = value;
return true;
}
};
const proxy = new Proxy(target, handler);
console.log('通过代理访问:');
console.log('姓名:', proxy.name); // 触发get拦截器
proxy.age = 26; // 触发set拦截器
console.log('年龄:', proxy.age);
// 2. 属性访问拦截和验证
console.log('\n=== 属性访问拦截 ===');
const user = {
_name: '李四',
_email: 'lisi@example.com',
_age: 30
};
const userProxy = new Proxy(user, {
get(target, property) {
// 私有属性拦截
if (property.startsWith('_')) {
throw new Error(`无法访问私有属性: ${property}`);
}
// 属性名转换
if (property === 'name') {
return target._name;
}
if (property === 'email') {
return target._email;
}
if (property === 'age') {
return target._age;
}
return target[property];
},
set(target, property, value) {
// 私有属性保护
if (property.startsWith('_')) {
throw new Error(`无法设置私有属性: ${property}`);
}
// 数据验证
if (property === 'age' && (typeof value !== 'number' || value < 0)) {
throw new Error('年龄必须是非负数');
}
if (property === 'email' && !value.includes('@')) {
throw new Error('邮箱格式不正确');
}
// 属性映射
if (property === 'name') {
target._name = value;
} else if (property === 'email') {
target._email = value;
} else if (property === 'age') {
target._age = value;
} else {
target[property] = value;
}
return true;
}
});
console.log('用户姓名:', userProxy.name);
userProxy.age = 31;
console.log('更新后年龄:', userProxy.age);
try {
console.log('尝试访问私有属性:', userProxy._name);
} catch (error) {
console.log('访问错误:', error.message);
}
try {
userProxy.age = -5;
} catch (error) {
console.log('验证错误:', error.message);
}
// 3. 默认值和动态属性
console.log('\n=== 默认值和动态属性 ===');
const config = new Proxy({}, {
get(target, property) {
// 提供默认值
if (!(property in target)) {
if (property === 'timeout') return 5000;
if (property === 'retries') return 3;
if (property === 'debug') return false;
return undefined;
}
return target[property];
},
set(target, property, value) {
// 类型转换
if (property === 'timeout' || property === 'retries') {
target[property] = Number(value);
} else if (property === 'debug') {
target[property] = Boolean(value);
} else {
target[property] = value;
}
return true;
}
});
console.log('默认超时时间:', config.timeout); // 5000
console.log('默认重试次数:', config.retries); // 3
console.log('默认调试模式:', config.debug); // false
config.timeout = '10000';
config.debug = 'true';
console.log('设置后的超时时间:', config.timeout, typeof config.timeout); // 10000 'number'
console.log('设置后的调试模式:', config.debug, typeof config.debug); // true 'boolean'
// 4. 数组操作拦截
console.log('\n=== 数组操作拦截 ===');
function createObservableArray(arr) {
return new Proxy(arr, {
set(target, property, value) {
if (property === 'length') {
console.log(`数组长度变更: ${target.length} -> ${value}`);
} else if (!isNaN(property)) {
console.log(`数组元素变更: [${property}] = ${value}`);
}
target[property] = value;
return true;
},
get(target, property) {
// 拦截数组方法
if (property === 'push') {
return function(...items) {
console.log(`数组push操作: 添加 ${items.length} 个元素`);
return Array.prototype.push.apply(target, items);
};
}
if (property === 'pop') {
return function() {
console.log('数组pop操作: 移除最后一个元素');
return Array.prototype.pop.call(target);
};
}
return target[property];
}
});
}
const observableArray = createObservableArray([1, 2, 3]);
console.log('初始数组:', observableArray);
observableArray.push(4, 5);
console.log('push后:', observableArray);
observableArray[0] = 10;
console.log('修改后:', observableArray);
const popped = observableArray.pop();
console.log('pop结果:', popped, '数组:', observableArray);
// 5. 函数调用拦截
console.log('\n=== 函数调用拦截 ===');
function createLoggingFunction(fn, name) {
return new Proxy(fn, {
apply(target, thisArg, argumentsList) {
console.log(`调用函数 ${name},参数:`, argumentsList);
const startTime = Date.now();
try {
const result = target.apply(thisArg, argumentsList);
const endTime = Date.now();
console.log(`函数 ${name} 执行完成,耗时: ${endTime - startTime}ms,结果:`, result);
return result;
} catch (error) {
console.log(`函数 ${name} 执行出错:`, error.message);
throw error;
}
}
});
}
const add = createLoggingFunction((a, b) => a + b, 'add');
const multiply = createLoggingFunction((a, b) => a * b, 'multiply');
console.log('函数调用测试:');
const sum = add(3, 4);
const product = multiply(5, 6);Proxy提供了13种handler方法,用于拦截不同的对象操作。
// 🎉 常用handler方法详解
console.log('=== handler方法详解 ===');
// 1. get和set - 属性访问拦截
const propertyHandler = {
get(target, property, receiver) {
console.log(`GET: ${property}`);
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
console.log(`SET: ${property} = ${value}`);
return Reflect.set(target, property, value, receiver);
}
};
// 2. has - in操作符拦截
const hasHandler = {
has(target, property) {
console.log(`HAS: 检查属性 ${property}`);
// 隐藏私有属性
if (property.startsWith('_')) {
return false;
}
return property in target;
}
};
const hasTarget = { name: '张三', _secret: '秘密' };
const hasProxy = new Proxy(hasTarget, hasHandler);
console.log('has拦截测试:');
console.log('name in proxy:', 'name' in hasProxy); // true
console.log('_secret in proxy:', '_secret' in hasProxy); // false
// 3. deleteProperty - delete操作拦截
const deleteHandler = {
deleteProperty(target, property) {
console.log(`DELETE: 尝试删除属性 ${property}`);
// 保护某些属性不被删除
if (property === 'id') {
console.log('ID属性不能被删除');
return false;
}
delete target[property];
return true;
}
};
const deleteTarget = { id: 1, name: '李四', temp: '临时' };
const deleteProxy = new Proxy(deleteTarget, deleteHandler);
console.log('\ndelete拦截测试:');
delete deleteProxy.temp; // 成功删除
delete deleteProxy.id; // 删除失败
console.log('删除后的对象:', deleteTarget);
// 4. ownKeys - Object.keys()等操作拦截
const ownKeysHandler = {
ownKeys(target) {
console.log('OWNKEYS: 获取对象键');
// 过滤私有属性
return Object.keys(target).filter(key => !key.startsWith('_'));
},
getOwnPropertyDescriptor(target, property) {
// 配合ownKeys使用,确保属性可枚举
if (property.startsWith('_')) {
return undefined;
}
return Object.getOwnPropertyDescriptor(target, property);
}
};
const ownKeysTarget = {
name: '王五',
age: 30,
_password: '123456',
_token: 'abc123'
};
const ownKeysProxy = new Proxy(ownKeysTarget, ownKeysHandler);
console.log('\nownKeys拦截测试:');
console.log('Object.keys():', Object.keys(ownKeysProxy));
console.log('for...in遍历:');
for (const key in ownKeysProxy) {
console.log(' ', key);
}
// 5. apply - 函数调用拦截
const applyHandler = {
apply(target, thisArg, argumentsList) {
console.log(`APPLY: 调用函数,参数:`, argumentsList);
// 参数验证
if (argumentsList.some(arg => typeof arg !== 'number')) {
throw new Error('所有参数必须是数字');
}
// 调用原函数
const result = target.apply(thisArg, argumentsList);
console.log(`APPLY: 函数返回值:`, result);
return result;
}
};
const originalSum = (a, b, c = 0) => a + b + c;
const sumProxy = new Proxy(originalSum, applyHandler);
console.log('\napply拦截测试:');
try {
const result1 = sumProxy(1, 2, 3);
console.log('正常调用结果:', result1);
const result2 = sumProxy(1, '2', 3); // 会抛出错误
} catch (error) {
console.log('参数验证错误:', error.message);
}
// 6. construct - new操作符拦截
const constructHandler = {
construct(target, argumentsList, newTarget) {
console.log(`CONSTRUCT: 构造函数调用,参数:`, argumentsList);
// 参数预处理
const processedArgs = argumentsList.map(arg =>
typeof arg === 'string' ? arg.trim() : arg
);
// 调用原构造函数
const instance = new target(...processedArgs);
// 实例后处理
instance.createdAt = new Date();
return instance;
}
};
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const PersonProxy = new Proxy(Person, constructHandler);
console.log('\nconstruct拦截测试:');
const person = new PersonProxy(' 赵六 ', 28);
console.log('创建的实例:', person);
// 7. 综合应用:响应式对象
console.log('\n=== 响应式对象实现 ===');
function createReactive(target, callback) {
return new Proxy(target, {
get(target, property, receiver) {
// 依赖收集(简化版)
console.log(`依赖收集: ${property}`);
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
const oldValue = target[property];
const result = Reflect.set(target, property, value, receiver);
// 值变化时触发回调
if (oldValue !== value) {
console.log(`响应式更新: ${property} ${oldValue} -> ${value}`);
callback(property, value, oldValue);
}
return result;
},
deleteProperty(target, property) {
const oldValue = target[property];
const result = Reflect.deleteProperty(target, property);
if (result) {
console.log(`响应式删除: ${property}`);
callback(property, undefined, oldValue);
}
return result;
}
});
}
const reactiveData = createReactive(
{ count: 0, message: 'hello' },
(property, newValue, oldValue) => {
console.log(`🔄 数据变化通知: ${property} 从 ${oldValue} 变为 ${newValue}`);
// 这里可以触发视图更新等操作
}
);
console.log('响应式对象测试:');
reactiveData.count++; // 触发响应式更新
reactiveData.message = 'world'; // 触发响应式更新
delete reactiveData.count; // 触发响应式删除理解Proxy和Object.defineProperty的差异对于选择合适的方案很重要。
// 🎉 Proxy vs Object.defineProperty对比
console.log('=== Proxy vs Object.defineProperty 对比 ===');
// 1. Object.defineProperty实现响应式
function definePropertyReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`defineProperty GET: ${key}`);
return val;
},
set(newVal) {
console.log(`defineProperty SET: ${key} = ${newVal}`);
val = newVal;
},
enumerable: true,
configurable: true
});
}
const definePropertyObj = {};
definePropertyReactive(definePropertyObj, 'name', '张三');
definePropertyReactive(definePropertyObj, 'age', 25);
console.log('Object.defineProperty测试:');
console.log('姓名:', definePropertyObj.name);
definePropertyObj.age = 26;
// 2. Proxy实现响应式
const proxyObj = new Proxy({}, {
get(target, property) {
console.log(`Proxy GET: ${property}`);
return target[property];
},
set(target, property, value) {
console.log(`Proxy SET: ${property} = ${value}`);
target[property] = value;
return true;
}
});
proxyObj.name = '李四';
proxyObj.age = 30;
console.log('\nProxy测试:');
console.log('姓名:', proxyObj.name);
proxyObj.age = 31;
// 3. 数组处理对比
console.log('\n=== 数组处理对比 ===');
// Object.defineProperty对数组的限制
const definePropertyArray = [1, 2, 3];
// 需要重写数组方法
const arrayMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
arrayMethods.forEach(method => {
const original = Array.prototype[method];
Object.defineProperty(definePropertyArray, method, {
value: function(...args) {
console.log(`defineProperty 数组方法: ${method}`);
return original.apply(this, args);
},
enumerable: false,
writable: true,
configurable: true
});
});
// Proxy对数组的完整支持
const proxyArray = new Proxy([1, 2, 3], {
get(target, property) {
console.log(`Proxy 数组 GET: ${property}`);
return target[property];
},
set(target, property, value) {
console.log(`Proxy 数组 SET: ${property} = ${value}`);
target[property] = value;
return true;
}
});
console.log('Object.defineProperty数组测试:');
definePropertyArray.push(4);
console.log('\nProxy数组测试:');
proxyArray.push(4);
proxyArray[0] = 10;
// 4. 动态属性对比
console.log('\n=== 动态属性对比 ===');
// Object.defineProperty需要预先定义
const definePropertyDynamic = {};
// 无法拦截新属性的添加
definePropertyDynamic.newProp = 'new value'; // 不会被拦截
console.log('defineProperty动态属性:', definePropertyDynamic.newProp);
// Proxy可以拦截所有属性操作
const proxyDynamic = new Proxy({}, {
get(target, property) {
console.log(`Proxy 动态 GET: ${property}`);
return target[property];
},
set(target, property, value) {
console.log(`Proxy 动态 SET: ${property} = ${value}`);
target[property] = value;
return true;
}
});
proxyDynamic.newProp = 'new value'; // 会被拦截
console.log('Proxy动态属性:', proxyDynamic.newProp);
// 5. 性能对比测试
console.log('\n=== 性能对比测试 ===');
function performanceTest() {
const iterations = 100000;
// Object.defineProperty性能测试
const definePropertyTestObj = {};
definePropertyReactive(definePropertyTestObj, 'value', 0);
console.time('Object.defineProperty');
for (let i = 0; i < iterations; i++) {
definePropertyTestObj.value = i;
}
console.timeEnd('Object.defineProperty');
// Proxy性能测试
const proxyTestObj = new Proxy({ value: 0 }, {
set(target, property, value) {
target[property] = value;
return true;
}
});
console.time('Proxy');
for (let i = 0; i < iterations; i++) {
proxyTestObj.value = i;
}
console.timeEnd('Proxy');
}
// 注释掉性能测试以避免控制台输出过多
// performanceTest();
// 6. 对比总结
console.log('\n=== 对比总结 ===');
console.log(`
Object.defineProperty:
✅ 兼容性好(ES5)
✅ 性能较好
❌ 只能监听已存在的属性
❌ 数组支持有限
❌ 需要递归遍历对象
Proxy:
✅ 功能强大,支持13种拦截操作
✅ 可以监听动态添加的属性
✅ 对数组支持完整
✅ 不需要修改原对象
❌ 兼容性较差(ES6)
❌ 性能略低
❌ 无法polyfill
`);通过本节ES6 Proxy代理教程的学习,你已经掌握:
A: Proxy的性能略低于直接属性访问,但在大多数场景下可以接受。在性能敏感的热点代码路径中,或者需要兼容旧浏览器时,可能不适合使用。
A: 可以在handler方法中添加console.log,或使用浏览器开发工具的断点调试。注意Proxy可能会影响某些调试工具的表现。
A: 可以使用Proxy.revocable()创建可撤销的代理。撤销后,任何对代理的操作都会抛出TypeError。
A: 需要在get handler中递归创建Proxy,确保嵌套对象也被代理。这在实现深度响应式系统时很重要。
A: Reflect提供了与Proxy handler方法对应的静态方法,通常在Proxy handler中使用Reflect来执行默认行为,保证操作的正确性。
"掌握Proxy是理解现代JavaScript元编程的关键一步。它不仅为我们提供了强大的对象行为控制能力,还是现代前端框架响应式系统的基础。在实际开发中合理使用Proxy,让你的代码更智能、更灵活!"