Skip to content

JS高级特性(二) 代理与反射、异步编程

🕒 Posted at: 2020-11-12 ( 3 years ago )
Javascript
Javascript高级特性:代理与反射、异步编程

代理与反射

ECMAScript 6新增的代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力。

代理

代理对象,它是一种特殊的对象,用于拦截对目标对象的访问。代理对象包含一个handler对象,该对象包含一组拦截器,用于拦截对目标对象的操作。

javascript
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操作符的操作

下面是一些常见的捕获器和它们的参数:

  1. get(target, property, receiver): 拦截对属性的读取操作。

    • target: 目标对象(即被代理的对象)。
    • property: 被获取的属性名称。
    • receiver: Proxy 或继承 Proxy 的对象。
  2. set(target, property, value, receiver): 拦截对属性的设置操作。

    • target: 目标对象。
    • property: 被设置的属性名称。
    • value: 被设置的属性的新值。
    • receiver: 最初被调用的对象。

get拦截器简单示例

javascript
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 的方法通常比直接使用对象方法更加灵活和通用。

javascript
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 捕获器中的作用

javascript
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 值正确地指向了继承的对象:

javascript
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。

异步编程

回调函数

回调函数是一种特殊的函数,它用于处理异步操作的结果。回调函数通常作为参数传递给异步操作的函数,当异步操作完成后,回调函数会被调用。

javascript
function fetchData(callback) {
  setTimeout(() => {
    callback('data');
  }, 1000);
}

fetchData(data => {
  console.log(data); // 'data'
});

Promise

Promise是一种特殊的对象,它用于表示异步操作的结果。Promise对象有三种状态:pendingfulfilled(resolved)、rejected。当异步操作完成后,Promise对象的状态会从pending变为fulfilledrejected,并且会调用then方法中注册的回调函数。

javascript

const delay = (ms) => new Promise((resolve) => setTimeout(() => {
    resolve(ms);
}, ms));

delay(1000).then((ms) => {
    console.log(ms); // 1000
});

then、catch、finally 方法

Promise对象有三个方法:thencatchfinallythen方法用于注册fulfilled状态的回调函数,catch方法用于注册rejected状态的回调函数,finally方法用于注册无论Promise对象的状态如何都会执行的回调函数。

javascript
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

asyncawait是ES2017新增的特性,它们用于简化异步编程。async函数用于定义异步函数,await用于等待异步操作的结果。

javascript
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对象的状态变为fulfilledrejected

async函数的错误处理

javascript
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状态的值。

javascript
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对象的结果会被忽略。

javascript

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对象的状态变为fulfilledrejected时,新的Promise对象的状态变为fulfilledrejected,并且返回第一个Promise对象的结果。

javascript
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,并且返回一个指定的值。

javascript
Promise.resolve('data').then((data) => {
    console.log(data); // 'data'
});

Promise.reject

Promise.reject方法用于返回一个新的Promise对象,该Promise对象的状态为rejected,并且返回一个指定的值。

javascript
Promise.reject('error').catch((error) => {
    console.log(error); // 'error'
});
Copyright © RyChen 2024