Proxy 是 JavaScript 中的一个内置对象,用于定义自定义行为(也称为“陷阱”或“handlers”)来拦截并重新定义对象的基本操作。虽然我们无法直接查看 Proxy 的源码,因为它是 JavaScript 引擎的一部分,但我们可以模拟 Proxy 的基本功能来理解其原理。
1. Proxy 的基本原理
Proxy 通过一系列的陷阱(traps)来拦截对象的基本操作。常见的陷阱包括:
get:拦截对象属性的读取操作。set:拦截对象属性的赋值操作。has:拦截in操作符。deleteProperty:拦截delete操作符。ownKeys:拦截Object.getOwnPropertyNames和Object.getOwnPropertySymbols。getOwnPropertyDescriptor:拦截Object.getOwnPropertyDescriptor。defineProperty:拦截Object.defineProperty。apply:拦截函数调用。construct:拦截new操作符。
2. 模拟 Proxy 的实现
为了更好地理解 Proxy 的原理,我们可以用 JavaScript 模拟一个简单的 Proxy 实现。以下是一个基本的模拟实现,主要关注 get 和 set 陷阱。
**2.1. 基本的 Proxy 模拟
function createProxy(target, handler) {
return new Proxy(target, handler);
}
function createSimpleProxy(target, handler) {
const proxy = {};
// 模拟 get 陷阱
proxy.get = function (key) {
if (handler.get) {
return handler.get(target, key, proxy);
}
return target[key];
};
// 模拟 set 陷阱
proxy.set = function (key, value) {
if (handler.set) {
return handler.set(target, key, value, proxy);
}
target[key] = value;
return true;
};
// 模拟 has 陷阱
proxy.has = function (key) {
if (handler.has) {
return handler.has(target, key, proxy);
}
return key in target;
};
// 模拟 deleteProperty 陷阱
proxy.deleteProperty = function (key) {
if (handler.deleteProperty) {
return handler.deleteProperty(target, key, proxy);
}
delete target[key];
return true;
};
// 模拟 ownKeys 陷阱
proxy.ownKeys = function () {
if (handler.ownKeys) {
return handler.ownKeys(target, proxy);
}
return Reflect.ownKeys(target);
};
// 模拟 getOwnPropertyDescriptor 陷阱
proxy.getOwnPropertyDescriptor = function (key) {
if (handler.getOwnPropertyDescriptor) {
return handler.getOwnPropertyDescriptor(target, key, proxy);
}
return Reflect.getOwnPropertyDescriptor(target, key);
};
// 模拟 defineProperty 陷阱
proxy.defineProperty = function (key, descriptor) {
if (handler.defineProperty) {
return handler.defineProperty(target, key, descriptor, proxy);
}
return Reflect.defineProperty(target, key, descriptor);
};
// 模拟 apply 陷阱
proxy.apply = function (thisArg, args) {
if (handler.apply) {
return handler.apply(target, thisArg, args, proxy);
}
return Reflect.apply(target, thisArg, args);
};
// 模拟 construct 陷阱
proxy.construct = function (args) {
if (handler.construct) {
return handler.construct(target, args, proxy);
}
return Reflect.construct(target, args, new.target);
};
return proxy;
}
// 使用示例
const target = {
message: 'Hello, World!'
};
const handler = {
get: function (target, key, receiver) {
console.log(`Getting ${key}`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`Setting ${key} to ${value}`);
return Reflect.set(target, key, value, receiver);
}
};
const proxy = createSimpleProxy(target, handler);
console.log(proxy.get('message')); // Getting message
proxy.set('message', 'Hello, Vue 3!'); // Setting message to Hello, Vue 3!
console.log(proxy.get('message')); // Getting message
3. 详细解析
**3.1. createSimpleProxy 函数
createSimpleProxy 函数接受两个参数:target 和 handler。target 是要代理的目标对象,handler 是一个对象,包含各种陷阱函数。
**3.2. **模拟 get 陷阱
proxy.get = function (key) {
if (handler.get) {
return handler.get(target, key, proxy);
}
return target[key];
};
- 如果
handler中定义了get陷阱函数,则调用该函数。 - 否则,直接返回
target对象的属性值。
简化版 Reflect.get 内部实现
function ReflectGet(target, key, receiver) {
// 1. 检查 target 是否为对象
if (typeof target !== 'object' && typeof target !== 'function') {
throw new TypeError('Reflect.get called on non-object');
}
// 2. 获取属性描述符
const descriptor = Object.getOwnPropertyDescriptor(target, key);
// 3. 处理 getter 函数
if (descriptor && typeof descriptor.get === 'function') {
return descriptor.get.call(receiver);
}
// 4. 直接获取属性值
if (descriptor && descriptor.writable === false) {
return descriptor.value;
}
// 5. 检查原型链
const prototype = Object.getPrototypeOf(target);
if (prototype !== null) {
return ReflectGet(prototype, key, receiver);
}
// 6. 返回 undefined
return undefined;
}
详细步骤说明
-
检查
target是否为对象:- 如果
target不是对象或函数,抛出TypeError。
- 如果
-
获取属性描述符:
- 使用
Object.getOwnPropertyDescriptor(target, key)获取key属性的描述符。
- 使用
-
处理 getter 函数:
- 如果属性描述符存在且有
get方法,调用get.call(receiver)并返回结果。
- 如果属性描述符存在且有
-
直接获取属性值:
- 如果属性描述符存在且没有
get方法,直接返回descriptor.value。
- 如果属性描述符存在且没有
-
检查原型链:
- 如果
key属性不在target上,查找原型链上的属性描述符。 - 使用
Object.getPrototypeOf(target)获取target的原型,并递归调用ReflectGet。
- 如果
-
返回
undefined:- 如果属性不存在且没有找到原型链上的属性,返回
undefined。
- 如果属性不存在且没有找到原型链上的属性,返回
示例
const obj = {
get a() {
return this.value;
}
};
const receiver = { value: 10 };
console.log(ReflectGet(obj, 'a', receiver)); // 输出: 10
console.log(ReflectGet(obj, 'b')); // 输出: undefined
在这个示例中,ReflectGet 被用来获取 obj 对象的属性 a,并返回 10。对于不存在的属性 b,返回 undefined。
总结
Reflect.get 的内部实现涉及以下几个关键步骤:
- 检查
target是否为对象。 - 获取属性描述符。
- 处理 getter 函数。
- 直接获取属性值。
- 检查原型链。
- 返回
undefined。
通过这些步骤,Reflect.get 能够正确处理各种属性获取情况,包括 getter 函数和原型链上的属性。
**3.3. **模拟 set 陷阱
proxy.set = function (key, value) {
if (handler.set) {
return handler.set(target, key, value, proxy);
}
target[key] = value;
return true;
};
- 如果
handler中定义了set陷阱函数,则调用该函数。 - 否则,直接设置
target对象的属性值,并返回true表示操作成功。
简化版 Reflect.set 内部实现
function ReflectSet(target, key, value, receiver) {
// 1. 检查 target 是否为对象
if (typeof target !== 'object' && typeof target !== 'function') {
throw new TypeError('Reflect.set called on non-object');
}
// 2. 获取属性描述符
const descriptor = Object.getOwnPropertyDescriptor(target, key);
// 3. 处理 setter 函数
if (descriptor && typeof descriptor.set === 'function') {
descriptor.set.call(receiver, value);
return true;
}
// 4. 检查属性是否可写
if (descriptor && descriptor.writable === false) {
return false;
}
// 5. 检查 target 是否可扩展
if (!Object.isExtensible(target)) {
return false;
}
// 6. 设置属性值
target[key] = value;
return true;
}
详细步骤说明
-
检查
target是否为对象:- 如果
target不是对象或函数,抛出TypeError。
- 如果
-
获取属性描述符:
- 使用
Object.getOwnPropertyDescriptor(target, key)获取key属性的描述符。
- 使用
-
处理 setter 函数:
- 如果属性描述符存在且有
set方法,调用set.call(receiver, value)并返回true。
- 如果属性描述符存在且有
-
检查属性是否可写:
-
如果属性描述符存在但没有
set方法,检查属性是否可写:- 如果可写,设置
target[key] = value并返回true。 - 如果不可写,返回
false。
- 如果可写,设置
-
-
检查
target是否可扩展:- 如果
target是不可扩展的且key不存在,返回false。
- 如果
-
设置属性值:
- 如果
key不存在且target是可扩展的,添加新属性target[key] = value并返回true。
- 如果
示例
const obj = {};
const handler = {
set(target, key, value, receiver) {
console.log(`Setting ${key} to ${value}`);
return ReflectSet(target, key, value, receiver);
}
};
const proxy = new Proxy(obj, handler);
proxy.a = 1; // 输出: Setting a to 1
console.log(obj.a); // 输出: 1
在这个示例中,ReflectSet 被用来设置 proxy 对象的属性 a,并返回 true 表示设置成功。
总结
Reflect.set 的内部实现涉及以下几个关键步骤:
- 检查
target是否为对象。 - 获取属性描述符。
- 处理 setter 函数。
- 检查属性是否可写。
- 检查
target是否可扩展。 - 设置属性值。
通过这些步骤,Reflect.set 能够正确处理各种属性设置情况,包括 setter 函数、只读属性和不可扩展对象。
**3.4. **模拟 has 陷阱
proxy.has = function (key) {
if (handler.has) {
return handler.has(target, key, proxy);
}
return key in target;
};
- 如果
handler中定义了has陷阱函数,则调用该函数。 - 否则,使用
in操作符检查target对象是否包含该属性。
**3.5. **模拟 deleteProperty 陷阱
proxy.deleteProperty = function (key) {
if (handler.deleteProperty) {
return handler.deleteProperty(target, key, proxy);
}
delete target[key];
return true;
};
- 如果
handler中定义了deleteProperty陷阱函数,则调用该函数。 - 否则,使用
delete操作符删除target对象的属性,并返回true表示操作成功。
**3.6. **模拟 ownKeys 陷阱
proxy.ownKeys = function () {
if (handler.ownKeys) {
return handler.ownKeys(target, proxy);
}
return Reflect.ownKeys(target);
};
- 如果
handler中定义了ownKeys陷阱函数,则调用该函数。 - 否则,使用
Reflect.ownKeys获取target对象的所有自有属性键。
**3.7. **模拟 getOwnPropertyDescriptor 陷阱
proxy.getOwnPropertyDescriptor = function (key) {
if (handler.getOwnPropertyDescriptor) {
return handler.getOwnPropertyDescriptor(target, key, proxy);
}
return Reflect.getOwnPropertyDescriptor(target, key);
};
- 如果
handler中定义了getOwnPropertyDescriptor陷阱函数,则调用该函数。 - 否则,使用
Reflect.getOwnPropertyDescriptor获取target对象的属性描述符。
**3.8. **模拟 defineProperty 陷阱
proxy.defineProperty = function (key, descriptor) {
if (handler.defineProperty) {
return handler.defineProperty(target, key, descriptor, proxy);
}
return Reflect.defineProperty(target, key, descriptor);
};
- 如果
handler中定义了defineProperty陷阱函数,则调用该函数。 - 否则,使用
Reflect.defineProperty定义target对象的属性。
**3.9. **模拟 apply 陷阱
proxy.apply = function (thisArg, args) {
if (handler.apply) {
return handler.apply(target, thisArg, args, proxy);
}
return Reflect.apply(target, thisArg, args);
};
- 如果
handler中定义了apply陷阱函数,则调用该函数。 - 否则,使用
Reflect.apply调用target对象的方法。
**3.10. **模拟 construct 陷阱
proxy.construct = function (args) {
if (handler.construct) {
return handler.construct(target, args, proxy);
}
return Reflect.construct(target, args, new.target);
};
- 如果
handler中定义了construct陷阱函数,则调用该函数。 - 否则,使用
Reflect.construct调用target对象的构造函数。
4. 总结
Proxy:用于定义自定义行为来拦截并重新定义对象的基本操作。- 陷阱(Traps) :包括
get、set、has、deleteProperty等,用于拦截不同的操作。 - 模拟
Proxy:通过创建一个代理对象,并在该对象上定义各种陷阱函数,可以模拟Proxy的基本功能。