André Staltz - JavaScript Getter-Setter Pyramid
The cornerstone of JavaScript is the function. It is a flexible abstraction that works as the basis for other abstractions, such as Promises, Iterables, Observables, and others.
JavaScript 的基石是函数。函数是其他抽象概念的基础,例如承诺(Promise)、可迭代对象(Iterables)、可观察对象(Observables)等等。
函数提供的好处:
- 惰性 / 可重用性(laziness / reusability)
- 实现灵活性(implementation flexibility)
函数是惰性的(除非被调用否则不会执行),所以才能被重用。函数的使用者并不关心函数的内部实现,这意味着可以灵活地使用多种方式实现函数。
getter 是一种函数,它不需要传递参数但返回值。 例如 Math.random()、Date.now() 等。
function getUser() {
return { name: "Jack", age: "25" };
}
console.log(getUser().name); // "Jack"
getter 内部的计算也可以是抽象的,因为在 JavaScript中函数是可以作为参数传递的。
function add(getX, getY) {
return function getZ() {
const x = getX();
const y = getY();
return x + y;
};
}
const getTen = () => 10;
const getTenPlusRandom = add(getTen, Math.random);
console.log(getTenPlusRandom()); // 10.9862
console.log(getTenPlusRandom()); // 10.5137
setter 是一种函数,它传递一个参数但没有返回值。 例如 console.log(x)、document.write(x) 等。
function fancyConsoleLog(str) {
console.log("⭐ " + str + " ⭐");
}
console.log(fancyConsoleLog("Hello World!")); // "⭐ Hello World! ⭐"
getter-getter 是一种特殊的 getter,它的返回值是另一个 getter。 例如,展示 2 的幂的数字序列 getGetNextPowerOfTwo():
function getGetNextPowerOfTwo() {
let i = 2;
return function getNextPowerOfTwo() {
const next = i;
i = i * 2;
return next;
};
}
let getNextPowerOfTwo = getGetNextPowerOfTwo();
console.log(getNextPowerOfTwo()); // 2
console.log(getNextPowerOfTwo()); // 4
console.log(getNextPowerOfTwo()); // 8
setter-setter 是一种特殊的 setter,它的传递参数是另一个 setter。 虽然 setter 不是抽象的,但是 setter-setter 是能够表示传递参数 setter 的参数值的抽象的。
setter-setter 提供的好处:
- 控制反转(inversion of control)
- 异步性(asynchronicity)
当使用 setter-setter 时,是它本身决定何时调用传递参数 setter 的。因此自然也可以是异步去调用。
function setSetTen(setTen) {
setTen(10);
}
setSetTen(console.log); // 10
Iterable 可迭代对象是一种特殊的 getter-getter,它的返回值是另一个会返回 done 和 value 属性的 getter。
使用可迭代对象(iterable)可以让我们知道何时停止迭代。
function getGetNextPowerOfTwoUntil16() {
let i = 2;
return function getNextPowerOfTwo() {
const next = i;
if (next > 16) return { done: true };
i = i * 2;
return { done: false, value: next };
};
}
let getNextPowerOfTwoUntil16 = getGetNextPowerOfTwoUntil16();
console.log(getNextPowerOfTwoUntil16()); // { done: false, value: 2 }
console.log(getNextPowerOfTwoUntil16()); // { done: false, value: 4 }
console.log(getNextPowerOfTwoUntil16()); // { done: false, value: 8 }
console.log(getNextPowerOfTwoUntil16()); // { done: false, value: 16 }
console.log(getNextPowerOfTwoUntil16()); // { done: true }
ES6 迭代器(iterator) 则是在此基础上封装得更加规范:
- getter-getter 封装成
{ [Symbol.iterator]: getter-getter } - getter 封装成
{ next: getter }
const getGetNextPowerOfTwoUntil16 = {
[Symbol.iterator]: () => {
let i = 2;
return {
next: () => {
const next = i;
if (next > 16) return { done: true };
i = i * 2;
return { done: false, value: next };
}
};
}
}
const getNextPowerOfTwoUntil16Iterator = getGetNextPowerOfTwoUntil16[Symbol.iterator]();
for (let result = getNextPowerOfTwoUntil16Iterator.next(); !result.done; result = getNextPowerOfTwoUntil16Iterator.next()) {
console.log(result.value); // 2 -> 4 -> 8 -> 16
}
// or use for-let-of
for (let value of getGetNextPowerOfTwoUntil16) {
console.log(value); // 2 -> 4 -> 8 -> 16
}
为了更方便地创建可迭代对象(iterable),ES6 提供了 生成器(generator) 语法糖。
function* getGetNextPowerOfTwoUntil16() {
let i = 2;
while (true) {
if (i > 16) return;
yield i;
i = i * 2;
}
}
for (let value of getGetNextPowerOfTwoUntil16()) {
console.log(value); // 2 -> 4 -> 8 -> 16
}
虽然 setter-setter 功能强大,但是由于控制反转(IoC),它将变得非常不可预测。setter-setter 可以是同步的也可以是异步的,并且在这过程中可能生产一个值或多个值。
Promise 承诺是一种特殊的 setter-setter,它为函数执行的异步性和值的生成约束了规范。
- setter 永远不会被同步调用
- setter 最多被调用一次
- 提供可选的第二个 setter 来处理错误
const setTenPromise = new Promise(function setSetTen(setTen) {
setTen(10);
});
console.log("before Promise.then");
console.log(setTenPromise.then(console.log));
console.log("after Promise.then");
// before Promise.then
// Promise {...}
// after Promise.then
// 10
为了更方便地创建 Promise,ES6 提供了 async-await 语法糖。
async function setTenPromise() {
return await 10;
}
console.log("before Promise.then");
console.log(await setTenPromise());
console.log("after Promise.then");
// before Promise.then
// 10
// after Promise.then
Observable 可观察对象是一种特殊的 setter-setter,它为函数执行的异步性和值的生成约束了规范。
可观察对象(Observable)不同于在 ES 规范中标准化的可迭代对象(Iterable),虽然 Observable 被认为是 TC39 的提议(proposal),但是这个提议还在不断变化中。
在下面例子中,我们假设可观察对象(Observable)采用的是 Fantasy Observable 规范,像 RxJS 这样的库也是采用这个规范。
可观察对象(Observable)和可迭代对象(Iterable)具有对称性:
Iterable 可迭代对象
- 是对象
- 拥有 iterate(迭代)方法,方法名为
Symbol.iterator(迭代器符号) - iterate(迭代)方法是 Iterator(迭代器对象)的 getter
- Iterator(迭代器对象)拥有名为
next方法的 getter
Observable 可观察对象
- 是对象
- 拥有 observe(观察)方法,方法名为
subscribe(订阅) - observe(观察)方法是 Observer(观察者对象)的 setter
- Observer(观察者对象)拥有名为
next方法的 setter
观察者对象(Observer) 除了 next 还拥有另外两个方法 complete 和 error,分别表示成功完成和失败抛错。complete 方法相当于迭代器对象(Iterator)的 done 方法,error 方法相当于迭代器对象(Iterator)抛错。
像 Promise 一样,可观察对象(Observable)中状态的变化具有不可逆性。一旦观察者对象(Observer)调用 complete 或 error 方法后,就不再允许调用 next 方法。
const setSetNextPowerOfTwoUntil16 = {
subscribe: (observer) => {
let i = 2;
try {
while (i <= 16) {
observer.next(i);
i = i * 2;
}
observer.complete();
} catch(err) {
observer.error(err);
}
}
}
const setNextPowerOfTwoUntil16 = setSetNextPowerOfTwoUntil16.subscribe;
setNextPowerOfTwoUntil16({
next: value => console.log(value),
complete: () => console.log("done")
}); // 2 -> 4 -> 8 -> 16 -> done
和 setter-setter 一样,可观察对象(Observable)会导致控制反转(IoC),因此消费端无法暂停或取消数据的产生。为了避免这种弊端,大多数可观察对象(Observable)的实现都添加了 订阅(Subscription) 这一对象来实现暂停或取消数据的产生。
subscribe 订阅方法返回一个对象,这个对象拥有 unsubscribe 方法,消费端可以使用该方法暂停或取消数据的产生。因此,订阅(Subscription)不再是 setter,因为它是一个既有参数传递又有返回值的函数。
const setSetNextPowerOfTwoUntil16 = {
subscribe: (observer) => {
let i = 2;
let powerOfTwoTimer = setInterval(() => {
if (i <= 16) {
observer.next(i);
i = i * 2;
} else {
observer.complete();
clearInterval(powerOfTwoTimer);
}
}, 1000);
return {
unsubscribe: () => {
clearInterval(powerOfTwoTimer);
}
};
}
}
const setNextPowerOfTwoUntil16 = setSetNextPowerOfTwoUntil16.subscribe;
const subscription = setNextPowerOfTwoUntil16({
next: value => console.log(value),
complete: () => console.log("done")
}); // 2 -> 4
setTimeout(() => {
subscription.unsubscribe();
}, 2500);
AsyncIterable 异步可迭代对象通过使用 Promise 实现迭代值的异步传递,每次调用迭代器(Iterator)的 next 方法时,都会创建并返回一个 Promise。
function delayResolve(value) {
return new Promise(resolve => {
setTimeout(() => resolve(value), 1000);
});
}
function* getGetNextPowerOfTwoUntil16() {
let i = 2;
while (true) {
if (i > 16) return;
yield delayResolve(i);
i = i * 2;
}
}
for (let valuePromise of getGetNextPowerOfTwoUntil16()) {
console.log(await valuePromise); // (1s) -> 2 -> (1s) -> 4 -> (1s) -> 8 -> (1s) -> 16
}
上面的代码其实是 ES6 的使用 Promise 实现的可迭代对象(Iterable of Promise),和 ES2018 的异步可迭代对象(AsyncIterable)还是有区别的:
- ES6 Iterable of Promise:
() => (() => { done, value: Promise<value> ) - ES2018 AsyncIterable:
() => (() => Promise<{ done, value }>)
下面我们来具体实现 ES2018 可迭代异步对象(AsyncIterable),我们使用 Symbol.asyncIterator 来定义迭代方法:
function delayResolve(value) {
return new Promise(resolve => {
setTimeout(() => resolve(value), 1000);
});
}
const getGetNextPowerOfTwoUntil16 = {
[Symbol.asyncIterator]: () => {
let i = 2;
return {
next: () => {
const next = i;
if (next > 16) return delayResolve({ done: true });
i = i * 2;
return delayResolve({ done: false, value: next });
}
};
}
}
const getNextPowerOfTwoUntil16Iterator = getGetNextPowerOfTwoUntil16[Symbol.asyncIterator]();
let done = false;
let value = undefined;
for (let result = getNextPowerOfTwoUntil16Iterator.next(); !done; result = getNextPowerOfTwoUntil16Iterator.next()) {
({ done, value } = await result);
if (!done) {
console.log(value);
} else {
console.log("done");
}
// (1s) -> 2 -> (1s) -> 4 -> (1s) -> 8 -> (1s) -> 16 -> (1s) -> done
}
// or use await for-let-of
for await (let value of getGetNextPowerOfTwoUntil16) {
console.log(value);
}
console.log("done");
// (1s) -> 2 -> (1s) -> 4 -> (1s) -> 8 -> (1s) -> 16 -> (1s) -> done