代理与反射
ECMAScript 6新增的代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力。
代理
代理对象,它是一种特殊的对象,用于拦截对目标对象的访问。代理对象包含一个handler
对象,该对象包含一组拦截器,用于拦截对目标对象的操作。
const target = {
id: 'target'
};
const handler = {};
const proxy = new Proxy(target, handler);
// id属性会访问同一个值
console.log(target.id); // target
console.log(proxy.id); // target
// 给目标属性赋值会反映在两个对象上
// 因为两个对象访问的是同一个值
target.id = 'foo';
console.log(target.id); // foo
console.log(proxy.id); // foo
// 给代理属性赋值会反映在两个对象上
// 因为这个赋值会转移到目标对象
proxy.id = 'bar';
console.log(target.id); // bar
console.log(proxy.id); // bar
// hasOwnProperty()方法在两个地方
// 都会应用到目标对象
console.log(target.hasOwnProperty('id')); // true
console.log(proxy.hasOwnProperty('id')); // true
// Proxy.prototype是undefined
// 因此不能使用instanceof操作符
console.log(target instanceof Proxy); // TypeError: Function has non-object prototype 'undefined' in instanceof check
console.log(proxy instanceof Proxy); // TypeError: Function has non-object prototype 'undefined' in instanceof check
// 严格相等可以用来区分代理和目标
console.log(target === proxy); // false
拦截器 (捕获器 / handler)
拦截器是一组特殊的方法,用于拦截对目标对象的操作。拦截器包括以下方法:
get
:拦截对属性的访问set
:拦截对属性的赋值has
:拦截对in操作符的操作defineProperty
:拦截对Object.defineProperty
方法的操作getOwnPropertyDescriptor
:拦截对Object.getOwnPropertyDescriptor
方法的操作deleteProperty
:拦截对delete操作符的操作ownKeys
:拦截对Object.keys
的操作getPrototypeOf
:拦截对Object.getPrototypeOf
的操作setPrototypeOf
:拦截对Object.setPrototypeOf
的操作isExtensible
:拦截对Object.isExtensible
的操作preventExtensions
:拦截对Object.preventExtensions
的操作apply
:拦截对函数调用的操作construct
:拦截对new
操作符的操作
下面是一些常见的捕获器和它们的参数:
get(target, property, receiver)
: 拦截对属性的读取操作。target
: 目标对象(即被代理的对象)。property
: 被获取的属性名称。receiver
:Proxy
或继承Proxy
的对象。
set(target, property, value, receiver)
: 拦截对属性的设置操作。target
: 目标对象。property
: 被设置的属性名称。value
: 被设置的属性的新值。receiver
: 最初被调用的对象。
get拦截器简单示例
const target = {
id: 'target'
};
const handler = {
get() {
return 'handler';
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.id); // handler
反射
在 JavaScript 中,反射(Reflection)是一种与对象的结构和元数据进行交互的机制。它允许在运行时检查对象的类型和结构,以及动态地调用对象的方法和属性。JavaScript 的反射主要通过 Reflect
对象和 Proxy
对象实现。
Reflect
是一个内置的对象,它提供了一系列静态方法,用于执行对象默认操作的函数版本,类似于在 Object
上的方法。这些方法与 Proxy
处理器对象的方法相对应。Reflect
不是一个函数对象,所以它是不可构造的。
Reflect
的方法包括:
Reflect.apply(target, thisArgument, argumentsList)
:与Function.prototype.apply()
相似,用于调用一个函数。Reflect.construct(target, argumentsList[, newTarget])
:相当于使用new
操作符来调用构造函数。Reflect.get(target, propertyKey[, receiver])
:用于获取对象属性。Reflect.set(target, propertyKey, value[, receiver])
:用于设置对象属性。Reflect.defineProperty(target, propertyKey, attributes)
:与Object.defineProperty()
类似,用于定义或修改对象的属性。Reflect.deleteProperty(target, propertyKey)
:用于删除对象的属性。Reflect.has(target, propertyKey)
:用于检查对象是否包含某个属性,等同于propertyKey in target
操作。Reflect.ownKeys(target)
:返回一个包含所有自有(非继承)属性键的数组。
等等。Reflect
的方法通常比直接使用对象方法更加灵活和通用。
let target = {};
let handler = {
get(target, prop, receiver) {
// 使用 Reflect.get 来保证默认行为的正确性
return Reflect.get(target, prop, receiver) || 42;
},
set(target, prop, value, receiver) {
// 使用 Reflect.set 来保证默认行为的正确性
var success = Reflect.set(target, prop, value, receiver);
if (success) {
console.log(`Property ${prop} set to ${value}`);
}
return success;
}
};
let proxy = new Proxy(target, handler);
console.log(proxy.a); // 输出 42,因为 'a' 不是 target 的自有属性
proxy.a = 10; // 设置属性 'a' 的值为 10,并输出 "Property a set to 10"
console.log(proxy.a); // 输出 10,现在 'a' 是 target 的自有属性
捕获器的参数receiver
在 JavaScript 的 Proxy 中,receiver 参数代表的是最初被调用的对象,通常是代理对象本身,或者是继承自代理对象的对象。这个参数在处理原型链上的操作时特别有用,因为它允许正确地将方法调用或属性访问的上下文(this 值)指向原始的代理对象。
考虑到 JavaScript 中的 getter 和 setter 可以用来定义属性的访问行为,receiver 就成为了确保这些操作在代理对象的上下文中正确执行的关键。如果你在 getter 或 setter 中使用了 this 关键字,receiver 保证了 this 指向调用它的对象,通常是代理对象。
下面的代码展示了 receiver 参数在 get 捕获器中的作用
const person = {
name: 'Alice',
get name() {
return `My name is ${this._name}`;
},
set name(value) {
this._name = value;
}
};
const handler = {
get(target, prop, receiver) {
console.log(`Property ${prop} has been read.`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`Property ${prop} set to ${value}.`);
return Reflect.set(target, prop, value, receiver);
}
};
const proxy = new Proxy(person, handler);
console.log(proxy.name); // 输出: Property name has been read. My name is Alice
proxy.name = 'Bob'; // 输出: Property name set to Bob.
console.log(proxy.name); // 输出: Property name has been read. My name is Bob
在这个例子中,当我们通过代理对象访问 name 属性时,get 捕获器会被触发。Reflect.get 方法接受 receiver 参数,确保如果目标对象的 getter 依赖于 this 值,它将正确地指向代理对象,而不是目标对象。
如果我们创建了一个继承自代理对象的新对象,receiver 参数也会确保 this 值正确地指向了继承的对象:
const anotherPerson = Object.create(proxy);
anotherPerson.name = 'Carol';
console.log(anotherPerson.name); // 输出: Property name has been read. My name is Carol
console.log(proxy.name); // 输出: Property name has been read. My name is Bob
console.log(person.name); // 输出: My name is Bob
在这段代码中,anotherPerson 对象继承自 proxy。当我们设置 anotherPerson.name 时,set 捕获器中的 receiver 确保 this 在 person 对象的 setter 中指向 anotherPerson,所以属性 _name 被设置在 anotherPerson 上,而不是 proxy 或 person。同样,当我们读取 anotherPerson.name 时,get 捕获器确保 this 在 getter 中指向 anotherPerson。
异步编程
回调函数
回调函数是一种特殊的函数,它用于处理异步操作的结果。回调函数通常作为参数传递给异步操作的函数,当异步操作完成后,回调函数会被调用。
function fetchData(callback) {
setTimeout(() => {
callback('data');
}, 1000);
}
fetchData(data => {
console.log(data); // 'data'
});
Promise
Promise是一种特殊的对象,它用于表示异步操作的结果。Promise对象有三种状态:pending
、fulfilled
(resolved
)、rejected
。当异步操作完成后,Promise对象的状态会从pending
变为fulfilled
或rejected
,并且会调用then
方法中注册的回调函数。
const delay = (ms) => new Promise((resolve) => setTimeout(() => {
resolve(ms);
}, ms));
delay(1000).then((ms) => {
console.log(ms); // 1000
});
then、catch、finally 方法
Promise对象有三个方法:then
、catch
、finally
。then
方法用于注册fulfilled
状态的回调函数,catch
方法用于注册rejected
状态的回调函数,finally
方法用于注册无论Promise对象的状态如何都会执行的回调函数。
const delay = (ms) => new Promise((resolve, reject) => setTimeout(() => {
if (ms > 1000) {
reject(ms);
} else {
resolve(ms);
}
}, ms));
delay(1000).then((ms) => {
console.log(ms); // 1000
}).catch((ms) => {
console.log(ms); // 2000
}).finally(() => {
console.log('done');
});
async/await
async
和await
是ES2017新增的特性,它们用于简化异步编程。async
函数用于定义异步函数,await
用于等待异步操作的结果。
const delay = (ms) => new Promise((resolve) => setTimeout(() => {
resolve(ms);
}, ms));
async function fetchData() {
const ms = await delay(1000);
console.log(ms); // 1000
}
fetchData();
async
函数返回的是一个Promise对象,await
用于等待Promise对象的结果。await
只能在async
函数中使用,它会暂停async
函数的执行,直到Promise对象的状态变为fulfilled
或rejected
。
async
函数的错误处理
const delay = (ms) => new Promise((resolve, reject) => setTimeout(() => {
if (ms > 1000) {
reject(ms);
} else {
resolve(ms);
}
}, ms));
async function fetchData() {
try {
const ms = await delay(1000);
console.log(ms); // 1000
} catch (ms) {
console.log(ms); // 2000
}
}
fetchData();
async
函数的错误处理使用try...catch
语句,catch
语句用于捕获await
表达式中抛出的错误。
async
函数的返回值
async
函数返回的是一个Promise对象,async
函数的返回值会作为Promise对象的fulfilled
状态的值。
async function fetchData() {
return 'data';
}
fetchData().then((data) => {
console.log(data); // 'data'
});
Promise.all
Promise.all
方法用于等待多个Promise对象的结果。Promise.all
方法接受多个Promise对象组成的数组作为参数,返回一个新的Promise对象,当所有Promise对象的状态都变为fulfilled
时,新的Promise对象的状态变为fulfilled
,并且返回一个包含所有Promise对象的结果的数组;当有一个Promise对象的状态变为rejected
时,新的Promise对象的状态变为rejected
,并且返回第一个Rejected Promise对象的结果,其它Promise对象的结果会被忽略。
const delay = (ms) => new Promise((resolve) => setTimeout(() => {
resolve(ms);
}, ms));
Promise.all([delay(1000), delay(2000), delay(3000)]).then((results) => {
console.log(results); // [1000, 2000, 3000]
});
Promise.all([delay(1000), delay(2000), delay(3000), Promise.reject(4000),Promise.reject(5000)]).then((results) => {
console.log(results); // [1000, 2000, 3000]
}).catch((error) => {
console.log(error); // 4000
});
Promise.race
Promise.race
方法用于等待多个Promise对象的结果。Promise.race
方法接受多个Promise对象组成的数组作为参数,返回一个新的Promise对象,当有一个Promise对象的状态变为fulfilled
或rejected
时,新的Promise对象的状态变为fulfilled
或rejected
,并且返回第一个Promise对象的结果。
const delay = (ms) => new Promise((resolve) => setTimeout(() => {
resolve(ms);
}, ms));
Promise.race([delay(1000), delay(2000), delay(3000)]).then((result) => {
console.log(result); // 1000
});
Promise.resolve
Promise.resolve
方法用于返回一个新的Promise对象,该Promise对象的状态为fulfilled
,并且返回一个指定的值。
Promise.resolve('data').then((data) => {
console.log(data); // 'data'
});
Promise.reject
Promise.reject
方法用于返回一个新的Promise对象,该Promise对象的状态为rejected
,并且返回一个指定的值。
Promise.reject('error').catch((error) => {
console.log(error); // 'error'
});