Proxy
Proxy
是 ES6
引入的一种用于定义自定义行为的对象包装器。通过 Proxy
,你可以拦截和重写对目标对象的基本操作,例如读取属性、设置属性、删除属性、函数调用等
Proxy 允许你创建一个代理对象,可以拦截并自定义 JavaScript 对象的基本操作。
复习Object.definedProperty
const obj = {
name: "obj",
age: 18,
};
Object.keys(obj).forEach((f) => {
let v = obj[f];
Object.defineProperty(obj, f, {
set(value) {
console.log("set");
v = value;
},
get() {
console.log("get");
return v;
},
});
});
obj.height = 188; // 监听不到
obj.age = 28; // 执行set
console.log(obj.age); // 执行get 28
- 首先 `Object.defineProperty` 设计的初衷不是为了去监听截止一个对象中所有的属性的,初衷其实是定义普通的属性
- 其次如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么 `Object.defineProperty` 是无能为力的
const p = new Proxy(target, handler)
-
代理对象 (
proxy
) :通过Proxy
创建的代理对象(上面代码的p),操作代理对象时,行为由处理器决定 -
目标对象 (
target
) :你想要代理的对象,可以是任何类型的对象(数组、函数、对象等) -
处理器 (
handler
) :定义拦截操作的对象,包含一组陷阱函数(比如get
、set
、has
、deleteProperty
等等)
const obj = {
name: "obj",
age: 18,
};
const p = new Proxy(obj, {
// 这个handler对象中放捕获器
set() {
console.log("set捕获器");
},
});
p.name = "p"; // 这时会打印set捕获器
get
和 set
handler.set(target,property,value,receiver)
target
:目标对象(侦听的对象);property
:将被设置的属性key
;value
:新属性值;receiver
:调用的代理对象handler.get(target,property,receiver)
target
:目标对象(侦听的对象);property
:被获取的属性key
;receiver
:调用的代理对象
const obj = {
name: "obj",
age: 18,
};
const p = new Proxy(obj, {
set(target, key, value, receiver) {
// {name: 'obj', age: 18} name p Proxy(Object){name: 'obj', age: 18}
console.log("set捕获器", target, key, value, receiver);
target[key] = value; // 不写赋值为undefined
},
get(target, key, receiver) {
console.log("get捕获器");
return target[key]; // 不写获取值为undefined
},
});
p.name = "p"; // 这时会打印set捕获器
console.log(p.name); // 这时会打印get捕获器
p.height = 188; // 可捕获到
console.log(p.height);
has
和 deleteProperty
-
has(target, property)
:in
操作符的捕捉器,拦截in
操作符,检查属性是否存在target
: 被代理的对象property
: 被检查的属性名
-
deleteProperty(target, property)
:拦截delete
操作符删除属性target
: 被代理的对象property
: 被检查的属性名
const obj = {
name: "obj",
age: 18,
};
const p = new Proxy(obj, {
has(target, key) {
console.log("has捕获器");
return key in target; // 不写都返回false
},
deleteProperty(target, key) {
console.log("deleteProperty捕获器");
delete target[key];
},
});
console.log("school" in p); // false
console.log("age" in p); // true
delete p.age;
console.log(p); // Proxy(Object) {name: 'p'}
apply
和 construct
-
apply(target, thisArg, argumentsList)
:拦截函数调用操作target
: 被代理的函数thisArg
: 函数的this
值argumentsList
: 函数调用时传入的参数列表
-
construct(target, argumentsList, newTarget)
:new
操作符的捕捉器target
: 被代理的构造函数argumentsList
: 构造函数调用时传入的参数列表newTarget
:new
操作符的目标
const Foo = function (name) {
this.name = name;
console.log("foo");
};
const p = new Proxy(Foo, {
apply(target, thisArg, args) {
console.log("apply捕获器", target, thisArg, args);
return target.apply(thisArg, args); // 不写不会执行函数调用
},
construct(target, args, newTarget) {
console.log("construct捕获器", target, args, newTarget);
return new target(...args);
},
});
p(); // apply捕获器执行
const f = new p("construct捕获器");
console.log(f); // Foo {name: 'construct捕获器'}
-
setPrototypeOf(target, prototype)
:拦截Object.setPrototypeOf()
操作target
: 被代理的对象prototype
: 新的原型对象
-
getPrototypeOf(target)
:拦截Object.getPrototypeOf()
操作target
: 被代理的对象
-
isExtensible(target)
:拦截对象是否可以添加新属性,即Object.isExtensible()
操作,target
: 被代理的对象
-
preventExtensions(target)
:拦截阻止向对象添加新属性,仍然可以将属性添加到对象原型中,即Object.preventExtensions()
操作,target
: 被代理的对象
-
getOwnPropertyDescriptor(target, property)
:拦截对属性描述符的请求,即Object.getOwnPropertyDescriptor()
target
: 被代理的对象property
: 请求的属性名
-
defineProperty(target, property, descriptor)
:拦截在对象上定义新属性,或者修改对象上现有的属性,并返回该对象,即Object.defineProperty()
操作target
: 被代理的对象property
: 被定义的属性名descriptor
: 属性描述符对象
-
ownKeys(target)
:拦截对象的自有属性键请求,如Object.keys()
和for...in
target
: 被代理的对象
const obj = {
name: "obj",
age: 18,
running() {
console.log("running");
},
};
const proxy = new Proxy(obj, {
setPrototypeOf(target, prototype) {
console.log("setPrototypeOf捕获器", target, prototype);
Object.setPrototypeOf(target, prototype);
return true;
},
getPrototypeOf(target) {
console.log("getPrototypeOf捕获器", target);
return Object.getPrototypeOf(target);
},
isExtensible(target) {
console.log("isExtensible捕获器", target);
return Object.isExtensible(target);
},
preventExtensions(target) {
console.log("preventExtensions捕获器", target);
return Object.preventExtensions(target);
},
getOwnPropertyDescriptor(target, prototype) {
console.log("getOwnPropertyDescriptor捕获器", target, prototype);
return Object.getOwnPropertyDescriptor(target, prototype);
},
defineProperty(target, prototype, description) {
console.log("defineProperty捕获器", target, prototype, description);
return Object.defineProperty(target, prototype, description);
},
ownKeys(target) {
console.log("ownKeys捕获器", target);
return Object.keys(target);
},
});
Object.setPrototypeOf(proxy, {});
console.log(Object.getPrototypeOf(proxy)); // {}
console.log(Object.isExtensible(proxy));
Object.preventExtensions(proxy); // 不能添加新属性
console.log(Object.getOwnPropertyDescriptor(proxy, "name")); // {value: 'obj', writable: true, enumerable: true, configurable: true}
console.log(Object.keys(proxy)); // ['name', 'age', 'running']
Object.defineProperty(proxy, "height", {
value: 188,
}); // 因为设置了preventExtensions,所以报错:Cannot define property height, object is not extensible
Reflect
Reflect
是一个内置的对象,它提供拦截 JavaScript 操作的方法,这些方法与一些操作符和语句的行为是一致的。Reflect
对象的方法可以被用于代替一些传统的操作,比如属性的获取、设置、删除,函数的调用等
Reflect 是一个内置对象,提供了一组静态方法,这些方法与一些操作符和语句的行为是一致的。
那么这个Reflect有什么用呢?
- 它主要提供了很多操作
JavaScript
对象的方法,有点像Object
中操作对象的方法 - 比如
Reflect.getPrototypeOf(target)
类似于Object.getPrototypeOf()
- 比如
Reflect.defineProperty(target, propertyKey, attributes)
类似于Object.defineProperty()
Reflect 的必要性
- 默认行为的一致性:
Reflect
对象提供了与大多数Proxy
traps 对应的方法,使得在进行对象操作时,可以保持一致的编程模式,且代码的可读性和可维护性更强。 - 更好的错误处理:
Reflect
方法返回一个布尔值,可以清晰地指示操作是否成功,这使得错误处理更加直观。 - 函数式编程风格:
Reflect
方法接受目标对象作为第一个参数,这允许你以函数式编程风格处理对象操作。 - 接收者(receiver)参数:
Reflect
方法通常接受一个接收者参数,这允许你在调用方法时明确指定this
的值,这在实现基于原型的继承和自定义this
绑定时非常有用。
以前有Object
可以做这些操作,为什么还需要有Reflect
这样的新增对象呢?
-
为了标准化对象操作的接口:
- 因为在早期的
ECMA
规范中没有考虑到对对象本身的操作如何设计会更加规范,所以将这些API
放到了Object
上面 - 但是
Object
作为一个构造函数,这些操作实际上放到它身上并不合适
- 因为在早期的
-
为了改善函数行为的一致性:
- 一些对象操作,比如
delete
或in
,在错误的情况下不会抛出错误,而是静默失败。通过Reflect
,这些操作会返回布尔值来反映操作是否成功,这让代码更具可预测性 - 所以在
ES6
中新增了Reflect
,让我们这些操作都集中到了Reflect
对象上
- 一些对象操作,比如
set
和 get
-
Reflect.set(target, propertyKey, value, receiver)
:为对象的某个属性设置值,类似于obj[key] = value
的操作,返回一个Boolean
,如果更新成功,则返回true
target
:要修改属性的对象propertyKey
:属性的键名value
:要设置的值receiver
(可选):用于设置代理对象上的属性,结合Proxy
使用中讲解
-
Reflect.get(target, propertyKey, receiver)
:获取对象身上某个属性的值,类似于target[name]
target
:要获取属性的对象propertyKey
:属性的键名receiver
(可选):若为Proxy
,则为代理对象的this
,结合Proxy
使用中讲解
const obj = {
name: "obj",
age: 18,
};
console.log(Reflect.set(obj, "height", "188")); // true
console.log(Reflect.get(obj, "height")); // 188
has
和 deleteProperty
-
Reflect.has(target, propertyKey)
:类似于in
运算符,检查对象是否具有某个属性target
:要检查的对象propertyKey
:属性的键名
-
Reflect.deleteProperty(target, propertyKey)
:类似于delete
运算符,删除对象上的属性,成功返回true
target
:要删除属性的对象propertyKey
:属性的键名
const obj = {
name: "obj",
age: 18,
};
console.log(Reflect.has(obj, "age")); // true
console.log(Reflect.deleteProperty(obj, "name")); // true
console.log(obj); // {age: 18}
apply
和 construct
-
Reflect.apply(target, thisArgument, argumentsList)
:对一个函数进行调用操作,同时可以传入一个数组作为调用参数,和Function.prototype.apply()
功能类似target
:要调用的函数thisArgument
:调用时作为this
的值argumentsList
:传递给目标函数的参数数组
-
Reflect.construct(target, argumentsList, newTarget)
:对构造函数进行new
操作,相当于执行new target(...args)
target
:构造函数argumentsList
:传递给构造函数的参数数组newTarget
(可选):指定构造函数的新newTarget
(用于继承场景)
function Person() { }
function Student(n) {
this.name = n;
console.log("Student", n, this); // Student name String{'this'}
};
Reflect.apply(Student, "this", ["name"]);
const stu = Reflect.construct(Student, ["condtruct"], Person)
console.log(stu); // Student {name: 'condtruct'}
console.log(stu.__proto__ === Person.prototype) // true
-
Reflect.setPrototypeOf(target, prototype)
:类似于Object.setPrototypeOf
,设置对象的原型,返回一个Boolean
, 如果更新成功,则返回true
target
:要设置原型的对象prototype
:新的原型对象
-
Reflect.getPrototypeOf(target)
:类似于Object.getPrototypeOf
,获取对象的原型target
:要获取原型的对象
-
Reflect.isExtensible(target)
:类似于Object.isExtensible
,检查对象是否可扩展,返回一个Boolean
target
:要检查的对象
-
Reflect.preventExtensions(target)
:类似于Object.preventExtensions
,禁止对象的扩展,返回一个Boolean
target
:禁止扩展的对象
-
Reflect.getOwnPropertyDescriptor(target, propertyKey)
:类似于Object.getOwnPropertyDescriptor
,获取对象某个自有属性的描述符,如果对象中存在该属性,则返回对应的属性描述符, 否则返回undefined
target
:要获取属性描述符的对象propertyKey
:属性的键名
-
Reflect.defineProperty(target, propertyKey, attributes)
:类似于Object.defineProperty
,在对象上定义属性,如果设置成功就会返回true
target
:要在其上定义属性的对象propertyKey
:属性的键名attributes
:属性的描述符对象
-
Reflect.ownKeys(target)
:返回一个包含所有自身属性(不包含继承属性)的数组。(类似于Object.keys()
, 但不会受enumerable
影响)target
:要获取键的对象
const obj = {
name: "obj",
age: 18,
};
console.log(Reflect.setPrototypeOf(obj, {})); // true
console.log(Reflect.getPrototypeOf(obj)); // {}
console.log(Reflect.isExtensible(obj)); // true
console.log(Reflect.preventExtensions(obj)); // true
console.log(Reflect.getOwnPropertyDescriptor(obj, "age")); // {value: 18, writable: true, enumerable: true, configurable: true}
console.log(Reflect.ownKeys(obj)); // ['name', 'age']
console.log(Reflect.defineProperty(obj, "height", { value: 188 })); // false,因为什么设了preventExtensions
console.log(obj); // {name: 'obj', age: 18}
Proxy和Reflect结合使用
const handler = {
get(target, prop, receiver) {
console.log(`Getting property "${prop}"`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`Setting property "${prop}" to ${value}`);
return Reflect.set(target, prop, value, receiver);
}
};
const obj = {
name: "John"
};
const proxyObj = new Proxy(obj, handler);
console.log(proxyObj.name); // Getting property "name"
proxyObj.age = 25; // Setting property "age" to 25
// 简单的观察者模式
const createObservable = (obj, onChange) => {
return new Proxy(obj, {
set(target, prop, value, receiver) {
Reflect.set(target, prop, value, receiver);
onChange();
return true;
}
});
};
const data = {
name: "John",
age: 25
};
const observableData = createObservable(data, () => {
console.log("Data changed:", observableData);
});
observableData.name = "Jane";
极简版的双向绑定
const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};
const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key === 'text') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
},
});
input.addEventListener('keyup', function(e) {
newObj.text = e.target.value;
});
receiver
receiver代表代理对象
const obj = {
name: 'wang.haoyu',
};
const proxy = new Proxy(obj, {
// get陷阱中target表示原对象 key表示访问的属性名
get(target, key, receiver) {
console.log(receiver === proxy);
return target[key];
},
});
// log: true
proxy.name;
receiver也会代表继承了proxy的对象
const obj = {
name: "obj",
age: 18,
};
const proxy = new Proxy(obj, {
set(target, key, newValue, receiver) {
console.log("set", receiver); // 只是打印 receiver 对象的引用,不涉及属性读取,不会触发get进入循环
console.log(receiver == proxy); // false
console.log(receiver == baz); // true
target[key] = newValue;
},
get(target, key, receiver) {
// console.log("get", receiver); // 相当于在外面写console.log(proxy),因此会进入循环,因为receiver是baz继承自 proxy,可使用Reflect解决
console.log(receiver == proxy); // false 对象引用的比较不会陷入循环
console.log(receiver == baz); // true
return target[key];
},
});
const baz = {
name: "baz",
// __proto__: proxy
};
// baz 对象的原型变成了 proxy,即 baz 继承了 proxy
Object.setPrototypeOf(baz, proxy);
baz.age = 30;
console.log(baz.age); // 30
receiver
存在的意义就是为了正确的传递上下文,receiver
不仅仅代表的是 Proxy
代理对象本身,同时也许他会代表继承 Proxy
的那个对象
receiver
的确是可以表示代理对象,但其实receiver
是执行 get
和 set
操作时的实际调用对象
- 在
get
捕获器中:receiver
是访问属性时的实际调用者对象,如果是通过某个对象(例如子对象)访问代理对象的属性,这个对象就是receiver
- 在
set
捕获器中:receiver
是设置属性时的对象,如果是在一个继承关系链中通过子对象进行设置操作,receiver
就是子对象 - 当使用
Object.setPrototypeOf
让一个对象继承另一个代理对象时,receiver
参数可以帮助区分是谁在调用(是原型对象还是继承者对象)
不传receiver
const obj = {
name: "obj",
age: 18,
set height(value) {
console.log("set", this);
},
get height() {
console.log("get", this); // {name: 'obj', age: 18}
return 170 + this.age;
},
};
const proxy = new Proxy(obj, {
set(target, key, newValue, receiver) {
console.log(receiver); // baz
Reflect.set(target, key, newValue);
},
get(target, key, receiver) {
console.log(target === obj); // true
return Reflect.get(target, key);
},
});
const baz = {
age: 28,
};
// baz 对象的原型变成了 proxy,即 baz 继承了 proxy
Object.setPrototypeOf(baz, proxy);
console.log(baz.height); // 188
// 传receiver
const obj = {
name: "obj",
age: 18,
set height(value) {
console.log("set", this);
},
get height() {
console.log("get", this); // baz:{age: 18, height: 198}
return 170 + this.age;
},
};
const proxy = new Proxy(obj, {
set(target, key, newValue, receiver) {
console.log(receiver);
Reflect.set(target, key, newValue, receiver);
},
get(target, key, receiver) {
console.log(target === obj); // true
console.log(receiver === baz); // true
return Reflect.get(target, key, receiver);
},
});
const baz = {
age: 28,
};
// baz 对象的原型变成了 proxy,即 baz 继承了 proxy
Object.setPrototypeOf(baz, proxy);
console.log(baz.height); // 198
Reflect中的receiver作用简单总结就是它可以修改属性访问中的 this
指向,让其指向传入的 receiver
对象
总结
- Proxy 中接受的 Receiver 形参表示代理对象本身或者继承与代理对象的对象。正确的调用者指向
- Reflect 中传递的 Receiver 实参表示修改执行原始操作时的 this 指向。