JavaScript Proxy:揭秘对象行为的魔法

前言

在JavaScript的世界里,对象是构建程序的基本积木。有没有想过,如果我们能在不改变对象本身的情况下,拦截并自定义它的各种操作,比如属性访问、赋值、删除等,那会带来多大的可能性?JavaScript的内置对象Proxy就是这样一个强大的工具,它能让你像魔法一样控制对象的行为。

在这篇文章中,我们将深入探讨Proxy的工作原理、使用方法以及在实际开发中的应用场景。无论你是前端新手还是有经验的开发者,这篇文章都将帮助你理解并掌握这个强大的特性。

一、什么是Proxy?

Proxy是JavaScript ES6引入的一个内置对象,它允许你创建一个代理对象,用来拦截并可能重定义对目标对象的各种操作。简单来说,Proxy就像是一个中间人,所有对目标对象的操作都会先经过这个中间人,你可以在这个中间过程中做一些手脚。

1.1 Proxy的基本语法

创建一个Proxy非常简单,只需要使用new Proxy()构造函数,并传入两个参数:目标对象和处理器对象。

const target = { name: "目标对象", value: 42 };
const handler = {
  // 在这里定义各种拦截方法(陷阱)
};
const proxy = new Proxy(target, handler);

在上面的代码中:

  • target 是你要代理的目标对象
  • handler 是一个包含各种拦截方法(也称为"陷阱")的对象
  • proxy 是返回的代理对象,所有对它的操作都会被handler拦截处理

1.2 为什么需要Proxy?

在JavaScript发展的早期,我们无法直接拦截对象的基本操作。要实现类似的功能,开发者通常需要手动定义getter和setter方法,或者使用Object.defineProperty(),但这些方法都有局限性。

Proxy的出现解决了这些问题,它提供了一种更优雅、更强大的方式来拦截对象操作。通过Proxy,我们可以实现:

  • 数据验证和过滤
  • 对象属性的访问控制
  • 响应式编程
  • 虚拟属性和计算属性
  • 日志记录和性能监控
  • … 以及更多高级功能

二、Proxy的工作原理

2.1 拦截机制

Proxy的核心是它的拦截机制。当我们对代理对象执行某种操作时,JavaScript引擎会检查处理器对象中是否定义了对应的拦截方法(陷阱)。如果有,就会调用该方法;如果没有,就会默认转发给目标对象。

这种拦截机制使得我们可以在不修改目标对象的情况下,自定义对象的行为。这是一种非侵入式的扩展方式,非常适合用于库和框架的开发。

2.2 Proxy与目标对象的关系

需要注意的是,代理对象和目标对象是两个不同的对象,但它们共享相同的底层数据。当我们通过代理对象修改属性时,实际上修改的是目标对象的属性;反之,直接修改目标对象的属性也会反映到代理对象上。

const target = { name: "目标对象" };
const handler = {};
const proxy = new Proxy(target, handler);

proxy.value = 42; // 通过代理对象设置属性
console.log(target.value); // 输出: 42,目标对象也被修改了

target.status = "active"; // 直接修改目标对象
console.log(proxy.status); // 输出: "active",代理对象也能访问到

三、Proxy陷阱详解

Proxy提供了一系列拦截方法,称为"陷阱"(Traps),每种陷阱对应一种对象操作。下面我们来详细了解这些陷阱的用法。

3.1 get陷阱

get陷阱用于拦截属性访问操作,它在读取代理对象的属性值时触发。

const target = { name: "小明", age: 25 };
const handler = {
  get(target, prop, receiver) {
    console.log(`访问属性: ${prop}`);
    // 可以做一些额外的处理,比如属性不存在时提供默认值
    return prop in target ? target[prop] : `属性${prop}不存在`;
  }
};
const proxy = new Proxy(target, handler);

console.log(proxy.name); // 输出: "访问属性: name" 和 "小明"
console.log(proxy.gender); // 输出: "访问属性: gender" 和 "属性gender不存在"

3.2 set陷阱

set陷阱用于拦截属性设置操作,它在给代理对象的属性赋值时触发。

const target = { name: "小明", age: 25 };
const handler = {
  set(target, prop, value, receiver) {
    console.log(`设置属性: ${prop} = ${value}`);
    // 可以做一些验证,比如年龄必须是数字且在合理范围内
    if (prop === 'age' && (typeof value !== 'number' || value < 0 || value > 150)) {
      throw new Error('年龄必须是0-150之间的数字');
    }
    target[prop] = value;
    return true; // 必须返回true表示设置成功
  }
};
const proxy = new Proxy(target, handler);

proxy.name = "小红"; // 输出: "设置属性: name = 小红"
proxy.age = 26; // 输出: "设置属性: age = 26"
// proxy.age = 200; // 抛出错误: "年龄必须是0-150之间的数字"

3.3 deleteProperty陷阱

deleteProperty陷阱用于拦截删除属性操作,它在使用delete操作符删除代理对象的属性时触发。

const target = { name: "小明", age: 25, _id: "user123" };
const handler = {
  deleteProperty(target, prop) {
    console.log(`尝试删除属性: ${prop}`);
    // 可以保护某些关键属性不被删除
    if (prop.startsWith('_')) {
      throw new Error(`不能删除私有属性: ${prop}`);
    }
    delete target[prop];
    return true;
  }
};
const proxy = new Proxy(target, handler);

delete proxy.age; // 输出: "尝试删除属性: age",删除成功
// delete proxy._id; // 抛出错误: "不能删除私有属性: _id"

3.4 has陷阱

has陷阱用于拦截in操作符,它在检查属性是否存在于对象中时触发。

const target = { name: "小明", age: 25 };
const handler = {
  has(target, prop) {
    console.log(`检查属性是否存在: ${prop}`);
    // 可以隐藏某些属性,使其在in操作符中不可见
    if (prop === 'age') {
      return false;
    }
    return prop in target;
  }
};
const proxy = new Proxy(target, handler);

console.log('name' in proxy); // 输出: "检查属性是否存在: name" 和 true
console.log('age' in proxy); // 输出: "检查属性是否存在: age" 和 false,但实际上age属性是存在的

3.5 apply陷阱

apply陷阱用于拦截函数调用,它在调用代理函数时触发。

function add(a, b) {
  return a + b;
}

const handler = {
  apply(target, thisArg, args) {
    console.log(`调用函数: ${target.name}(${args.join(', ')})`);
    // 可以在函数调用前后做一些处理
    console.log('计算开始...');
    const result = target.apply(thisArg, args);
    console.log('计算结束...');
    return result;
  }
};

const proxyAdd = new Proxy(add, handler);

console.log(proxyAdd(1, 2)); // 输出一系列日志,并返回3

3.6 construct陷阱

construct陷阱用于拦截new操作符,它在使用new创建代理对象的实例时触发。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const handler = {
  construct(target, args, newTarget) {
    console.log(`创建实例: ${target.name}(${args.join(', ')})`);
    // 可以在创建实例前后做一些处理,比如参数验证
    if (args.length < 2) {
      throw new Error('需要提供name和age两个参数');
    }
    // 使用new操作符创建实例
    return new target(...args);
  }
};

const ProxyPerson = new Proxy(Person, handler);

const person = new ProxyPerson('小明', 25); // 创建成功
console.log(person.name); // 输出: "小明"
// const person2 = new ProxyPerson('小红'); // 抛出错误

3.7 其他陷阱

除了上面介绍的常用陷阱外,Proxy还提供了其他一些陷阱,用于拦截对象的各种操作:

  • getPrototypeOf: 拦截获取原型对象的操作,如Object.getPrototypeOf()
  • setPrototypeOf: 拦截设置原型对象的操作,如Object.setPrototypeOf()
  • getOwnPropertyDescriptor: 拦截获取属性描述符的操作,如Object.getOwnPropertyDescriptor()
  • defineProperty: 拦截定义属性的操作,如Object.defineProperty()
  • ownKeys: 拦截获取对象所有属性键的操作,如Object.keys()Object.getOwnPropertyNames()
  • preventExtensions: 拦截阻止对象扩展的操作,如Object.preventExtensions()
  • isExtensible: 拦截检查对象是否可扩展的操作,如Object.isExtensible()

四、实际应用场景

Proxy在实际开发中有广泛的应用,下面我们来看看一些常见的应用场景。

4.1 数据验证

使用Proxy的set陷阱,我们可以轻松实现数据验证功能,确保对象的属性值符合特定的规则。

const userValidator = {
  set(target, prop, value) {
    // 姓名必须是字符串且长度在2-20之间
    if (prop === 'name' && (!value || typeof value !== 'string' || value.length < 2 || value.length > 20)) {
      throw new Error('姓名必须是2-20个字符的字符串');
    }
    // 年龄必须是数字且在0-150之间
    if (prop === 'age' && (typeof value !== 'number' || value < 0 || value > 150)) {
      throw new Error('年龄必须是0-150之间的数字');
    }
    // 邮箱必须符合邮箱格式
    if (prop === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
      throw new Error('请输入有效的邮箱地址');
    }
    target[prop] = value;
    return true;
  }
};

const user = new Proxy({}, userValidator);

user.name = '小明'; // 成功
user.age = 25; // 成功
user.email = 'xiaoming@example.com'; // 成功
// user.age = 200; // 抛出错误

4.2 响应式编程

Proxy是实现响应式编程的绝佳工具,它可以在数据变化时自动触发相应的回调函数。这也是很多现代前端框架(如Vue 3)实现响应式系统的核心机制。

function reactive(obj, callback) {
  return new Proxy(obj, {
    set(target, prop, value) {
      const oldValue = target[prop];
      // 如果值没有变化,不触发回调
      if (oldValue === value) {
        return true;
      }
      // 更新值
      target[prop] = value;
      // 触发回调,通知数据变化
      callback(prop, oldValue, value);
      return true;
    },
    deleteProperty(target, prop) {
      const hadProperty = prop in target;
      const oldValue = target[prop];
      delete target[prop];
      // 如果属性存在并被成功删除,触发回调
      if (hadProperty) {
        callback(prop, oldValue, undefined, 'delete');
      }
      return true;
    }
  });
}

// 使用示例
const state = reactive({ count: 0 }, (key, oldVal, newVal, type = 'update') => {
  console.log(`属性 ${key}${oldVal} 变为 ${newVal} (${type})`);
  // 这里可以更新UI或执行其他副作用
});

state.count += 1; // 输出: "属性 count 从 0 变为 1 (update)"
delete state.count; // 输出: "属性 count 从 1 变为 undefined (delete)"

4.3 属性保护

使用Proxy,我们可以实现对象属性的保护,比如只读属性、私有属性等。

const user = {
  name: '小明',
  age: 25,
  _password: 'secret123', // 私有属性,约定以下划线开头
  __config: { debug: false } // 内部配置,约定以双下划线开头
};

const handler = {
  get(target, prop) {
    // 禁止访问内部配置
    if (prop.startsWith('__')) {
      throw new Error(`无法访问内部属性: ${prop}`);
    }
    // 私有属性需要特殊方法才能访问
    if (prop.startsWith('_')) {
      console.warn(`警告: ${prop} 是私有属性,建议通过方法访问`);
    }
    return target[prop];
  },
  set(target, prop, value) {
    // 内部配置为只读
    if (prop.startsWith('__')) {
      throw new Error(`无法修改内部属性: ${prop}`);
    }
    // 可以为某些属性设置只读
    if (prop === 'name') {
      throw new Error('姓名为只读属性');
    }
    target[prop] = value;
    return true;
  },
  deleteProperty(target, prop) {
    // 禁止删除任何属性
    throw new Error('不能删除对象的属性');
  }
};

const protectedUser = new Proxy(user, handler);

console.log(protectedUser.name); // 输出: "小明"
// protectedUser.name = '小红'; // 抛出错误: "姓名为只读属性"
// delete protectedUser.age; // 抛出错误: "不能删除对象的属性"
// console.log(protectedUser.__config); // 抛出错误

4.4 延迟加载

Proxy可以帮助我们实现延迟加载(也称为惰性加载),即只有在真正需要访问属性时才计算或加载其值。这对于处理大型数据集或昂贵的计算操作非常有用。

const lazyObject = {
  // 这个属性的值计算起来很昂贵
  get expensiveData() {
    console.log('计算昂贵的数据...');
    // 模拟耗时操作
    setTimeout(() => {
      console.log('计算完成');
    }, 1000);
    return '昂贵的计算结果';
  }
};

// 使用Proxy实现延迟加载
const lazyProxy = new Proxy({}, {
  get(target, prop) {
    // 如果目标对象中没有该属性,但有对应的getter方法
    if (!(prop in target) && typeof lazyObject[`get_${prop}`] === 'function') {
      // 调用getter方法计算值并缓存
      target[prop] = lazyObject[`get_${prop}`]();
    }
    return target[prop];
  }
});

// 另一种延迟加载实现
const lazyLoaded = new Proxy({}, {
  get(target, prop) {
    // 检查是否已经加载过
    if (!(prop in target)) {
      console.log(`加载属性: ${prop}`);
      // 模拟从服务器或其他地方加载数据
      target[prop] = `加载的${prop}数据`;
    }
    return target[prop];
  }
});

console.log(lazyLoaded.name); // 输出: "加载属性: name" 和 "加载的name数据"
console.log(lazyLoaded.name); // 直接输出: "加载的name数据",不再重新加载

4.5 日志记录和性能监控

使用Proxy,我们可以轻松地为对象的操作添加日志记录和性能监控功能。

function createLoggedObject(target, name = 'Object') {
  const startTimeMap = new Map();
  
  return new Proxy(target, {
    get(target, prop) {
      const start = performance.now();
      const result = Reflect.get(target, prop);
      const end = performance.now();
      console.log(`[${name}] 访问属性 ${prop}: 耗时 ${(end - start).toFixed(3)}ms`);
      return result;
    },
    set(target, prop, value) {
      const start = performance.now();
      const result = Reflect.set(target, prop, value);
      const end = performance.now();
      console.log(`[${name}] 设置属性 ${prop}=${value}: 耗时 ${(end - start).toFixed(3)}ms`);
      return result;
    },
    // 其他陷阱也可以类似地添加日志
  });
}

// 使用示例
const data = createLoggedObject({ name: '小明', age: 25 }, 'UserData');

data.name; // 输出访问日志

4.6 代理链

我们可以创建多个代理,形成一个代理链,每个代理负责处理不同的关注点。这种方式符合单一职责原则,使代码更加清晰和可维护。

// 第一个代理:数据验证
function createValidationProxy(target, validators) {
  return new Proxy(target, {
    set(target, prop, value) {
      if (validators[prop] && !validators[prop](value)) {
        throw new Error(`属性 ${prop} 的值 ${value} 无效`);
      }
      return Reflect.set(target, prop, value);
    }
  });
}

// 第二个代理:日志记录
function createLoggingProxy(target) {
  return new Proxy(target, {
    get(target, prop) {
      console.log(`访问: ${prop}`);
      return Reflect.get(target, prop);
    },
    set(target, prop, value) {
      console.log(`设置: ${prop} = ${value}`);
      return Reflect.set(target, prop, value);
    }
  });
}

// 第三个代理:属性保护
function createProtectionProxy(target, readOnlyProps = []) {
  return new Proxy(target, {
    set(target, prop, value) {
      if (readOnlyProps.includes(prop)) {
        throw new Error(`属性 ${prop} 是只读的`);
      }
      return Reflect.set(target, prop, value);
    }
  });
}

// 创建代理链
const user = {};
const validators = {
  age: (value) => typeof value === 'number' && value >= 0 && value <= 150,
  name: (value) => typeof value === 'string' && value.length > 0
};

// 从内到外依次是:验证 -> 日志 -> 保护
const proxy = createProtectionProxy(
  createLoggingProxy(
    createValidationProxy(user, validators)
  ),
  ['name'] // name属性是只读的
);

// 使用代理链
proxy.name = '小明'; // 设置成功,输出日志
// proxy.name = '小红'; // 抛出错误:name是只读的
proxy.age = 25; // 设置成功,输出日志
// proxy.age = 200; // 抛出错误:age的值无效

五、常见使用误区和解决方法

虽然Proxy非常强大,但在使用过程中也有一些需要注意的地方,下面我们来看看一些常见的使用误区和解决方法。

5.1 忘记返回值

set陷阱中,如果忘记返回true,可能会导致一些意外的行为,特别是在严格模式下。

错误用法:

const handler = {
  set(target, prop, value) {
    target[prop] = value;
    // 忘记返回true
  }
};

正确用法:

const handler = {
  set(target, prop, value) {
    target[prop] = value;
    return true; // 必须返回true表示设置成功
  }
};

5.2 直接修改目标对象

在使用Proxy时,应该始终通过代理对象来操作数据,而不是直接修改目标对象。直接修改目标对象可能会绕过代理的拦截逻辑,导致预期外的行为。

错误用法:

const target = { count: 0 };
const handler = {
  set(target, prop, value) {
    console.log(`设置 ${prop} = ${value}`);
    target[prop] = value;
    return true;
  }
};
const proxy = new Proxy(target, handler);

target.count = 1; // 直接修改目标对象,绕过了代理的拦截逻辑

正确用法:

const target = { count: 0 };
const handler = {
  set(target, prop, value) {
    console.log(`设置 ${prop} = ${value}`);
    target[prop] = value;
    return true;
  }
};
const proxy = new Proxy(target, handler);

proxy.count = 1; // 通过代理对象修改,会触发拦截逻辑

5.3 递归陷阱

在陷阱中访问代理对象的属性可能会导致无限递归。为了避免这种情况,应该使用Reflect对象或直接访问目标对象。

错误用法:

const handler = {
  get(target, prop) {
    console.log(`访问 ${prop}`);
    return proxy[prop]; // 递归调用,会导致栈溢出
  }
};
const proxy = new Proxy({}, handler);

正确用法:

const handler = {
  get(target, prop) {
    console.log(`访问 ${prop}`);
    return target[prop]; // 直接访问目标对象
    // 或者使用Reflect:return Reflect.get(target, prop);
  }
};
const proxy = new Proxy({}, handler);

5.4 this指向问题

在使用Proxy时,需要注意方法中的this指向。默认情况下,通过代理对象调用的方法中的this指向的是代理对象,而不是目标对象。

示例:

const target = {
  name: '小明',
  getName() {
    return this.name;
  }
};
const handler = {};
const proxy = new Proxy(target, handler);

console.log(target.getName()); // 输出: "小明",this指向target
console.log(proxy.getName()); // 输出: "小明",this指向proxy,但proxy继承了target的属性

// 修改proxy的name属性
proxy.name = '小红';
console.log(proxy.getName()); // 输出: "小红"

如果需要确保this始终指向目标对象,可以在陷阱中使用bindcallapply方法来修改this的指向。

5.5 性能考虑

虽然Proxy非常强大,但它的性能开销比直接操作对象要大。因此,在性能敏感的场景中,应该谨慎使用Proxy,或者只在必要的地方使用。

优化建议:

  • 对于频繁访问的属性,可以考虑缓存结果
  • 尽量减少陷阱中的复杂逻辑
  • 在性能关键路径上,考虑使用其他更高效的方式
  • 使用Reflect对象来简化拦截逻辑,它通常比手动实现更高效

六、高级应用技巧

除了基本用法外,Proxy还有一些高级应用技巧,可以帮助我们解决更复杂的问题。

6.1 使用Reflect对象

Reflect是ES6引入的另一个内置对象,它提供了一组与Proxy陷阱对应的静态方法。使用Reflect可以简化Proxy的拦截逻辑,使其更加清晰和可维护。

const handler = {
  get(target, prop, receiver) {
    console.log(`访问: ${prop}`);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log(`设置: ${prop} = ${value}`);
    return Reflect.set(target, prop, value, receiver);
  },
  // 其他陷阱也可以使用对应的Reflect方法
};

6.2 代理数组

Proxy不仅可以代理普通对象,还可以代理数组。这使得我们可以拦截数组的各种操作,如元素访问、添加、删除等。

const arr = [1, 2, 3];
const handler = {
  get(target, prop, receiver) {
    console.log(`访问数组的 ${prop} 属性`);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log(`设置数组的 ${prop} 属性为 ${value}`);
    return Reflect.set(target, prop, value, receiver);
  }
};
const proxyArr = new Proxy(arr, handler);

proxyArr.push(4); // 会触发多个拦截操作
console.log(proxyArr[0]); // 输出: "访问数组的 0 属性" 和 1

6.3 代理函数

我们还可以代理函数,拦截函数的调用、new操作符等。

function greet(name) {
  return `Hello, ${name}!`;
}

const handler = {
  apply(target, thisArg, args) {
    console.log(`调用函数: ${target.name}(${args.join(', ')})`);
    return target.apply(thisArg, args);
  },
  construct(target, args, newTarget) {
    console.log(`使用new创建实例: ${args.join(', ')}`);
    return new target(...args);
  }
};

const proxyFunc = new Proxy(greet, handler);

console.log(proxyFunc('小明')); // 输出调用日志和 "Hello, 小明!"

6.4 虚拟属性

使用Proxy,我们可以实现虚拟属性,即这些属性并不实际存在于目标对象中,而是通过计算或其他方式动态生成的。

const user = {
  firstName: '张',
  lastName: '三'
};

const handler = {
  get(target, prop) {
    // 如果访问的是fullName属性,计算并返回全名
    if (prop === 'fullName') {
      return `${target.firstName}${target.lastName}`;
    }
    // 其他属性正常返回
    return Reflect.get(target, prop);
  },
  set(target, prop, value) {
    // 如果设置的是fullName属性,分解并设置firstName和lastName
    if (prop === 'fullName' && typeof value === 'string') {
      const parts = value.split(' ');
      if (parts.length >= 2) {
        target.firstName = parts[0];
        target.lastName = parts[1];
        return true;
      }
    }
    // 其他属性正常设置
    return Reflect.set(target, prop, value);
  },
  has(target, prop) {
    // 让虚拟属性在in操作符中也可见
    if (prop === 'fullName') {
      return true;
    }
    return Reflect.has(target, prop);
  }
};

const proxy = new Proxy(user, handler);

console.log(proxy.fullName); // 输出: "张三"
proxy.fullName = '李 四';
console.log(proxy.firstName); // 输出: "李"
console.log(proxy.lastName); // 输出: "四"
console.log('fullName' in proxy); // 输出: true

6.5 实现不可变对象

使用Proxy,我们可以实现真正的不可变对象,禁止任何对对象的修改。

function createImmutableObject(target) {
  // 递归地为嵌套对象创建代理
  if (typeof target === 'object' && target !== null) {
    // 为数组创建代理
    if (Array.isArray(target)) {
      const immutableArray = [];
      for (let i = 0; i < target.length; i++) {
        immutableArray[i] = createImmutableObject(target[i]);
      }
      target = immutableArray;
    }
    // 为普通对象创建代理
    else {
      const immutableObj = {};
      for (const key in target) {
        if (Object.prototype.hasOwnProperty.call(target, key)) {
          immutableObj[key] = createImmutableObject(target[key]);
        }
      }
      target = immutableObj;
    }
  }
  
  // 创建代理,禁止任何修改
  return new Proxy(target, {
    set() {
      throw new Error('Cannot modify immutable object');
    },
    deleteProperty() {
      throw new Error('Cannot delete properties from immutable object');
    },
    defineProperty() {
      throw new Error('Cannot define properties on immutable object');
    },
    setPrototypeOf() {
      throw new Error('Cannot change prototype of immutable object');
    }
  });
}

// 使用示例
const data = createImmutableObject({
  name: '小明',
  age: 25,
  address: {
    city: '北京',
    street: '朝阳区'
  },
  hobbies: ['读书', '运动']
});

// 尝试修改将抛出错误
// data.name = '小红';
// data.address.city = '上海';
// data.hobbies.push('音乐');

七、总结与展望

JavaScript的Proxy是一个非常强大的特性,它为我们提供了一种优雅、灵活的方式来拦截和自定义对象的行为。通过Proxy,我们可以实现数据验证、响应式编程、属性保护、延迟加载等各种高级功能。

Proxy的设计思想体现了JavaScript的元编程能力,它允许我们在运行时修改对象的行为,这为库和框架的开发提供了极大的便利。事实上,很多现代前端框架(如Vue 3、MobX等)都在内部使用了Proxy来实现其核心功能。

随着JavaScript语言的不断发展,我们可以期待Proxy在未来会有更多的应用场景和更强大的功能。作为开发者,掌握Proxy的使用技巧,将有助于我们编写更加灵活、高效和可维护的代码。

无论你是前端开发新手,还是有经验的老司机,我都希望这篇文章能帮助你更好地理解和应用JavaScript的Proxy特性。让我们一起探索这个神奇的对象,解锁更多JavaScript的编程技巧吧!

附录:Proxy陷阱速查表

为了方便查阅,下面是所有Proxy陷阱及其触发时机的速查表:

陷阱触发时机说明
getobj.prop 或 obj[prop]拦截属性访问
setobj.prop = value 或 obj[prop] = value拦截属性设置
deletePropertydelete obj.prop 或 delete obj[prop]拦截删除属性
hasprop in obj拦截 in 操作符
applytarget(…args) 或 target.apply/call()拦截函数调用
constructnew target(…args)拦截 new 操作符
getPrototypeOfObject.getPrototypeOf(obj)获取原型对象
setPrototypeOfObject.setPrototypeOf(obj, proto)设置原型对象
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor(obj, prop)获取属性描述符
definePropertyObject.defineProperty(obj, prop, desc)定义属性
ownKeysObject.keys(obj), Object.getOwnPropertyNames(obj) 等获取所有属性键
preventExtensionsObject.preventExtensions(obj)阻止对象扩展
isExtensibleObject.isExtensible(obj)检查对象是否可扩展

最后,创作不易请允许我插播一则自己开发的“数规规-排五助手”(有各种趋势分析)小程序广告,感兴趣可以微信小程序体验放松放松,程序员也要有点娱乐生活,搞不好就中个排列五了呢?

感兴趣的可以微信搜索小程序“数规规-排五助手”体验体验!或直接浏览器打开如下链接:

https://www.luoshu.online/jumptomp.html

可以直接跳转到对应小程序

如果觉得本文有用,欢迎点个赞👍+收藏🔖+关注支持我吧!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值