JAVASCRIPT GETTER-SETTER PYRAMID阅读笔记
JAVASCRIPT GETTER-SETTER PYRAMID阅读笔记

JAVASCRIPT GETTER-SETTER PYRAMID阅读笔记

原文:

https://staltz.com/javascript-getter-setter-pyramid.html

Functions

与硬编码相比。函数提供一些好处:

  • 惰性/可复用

在函数里的代码是惰性的(他不会执行除非被调用)

  • 实现灵活

函数的使用者并不关心函数内部是如何实现的,因此这意味着可以灵活地以各种方式实现函数。

Getter:

() => X

Getter没有输入值,并返回一个X值的函数

Getter是一种不需要参数传递,但是会返回一个值的函数,在JavaScript中有很多getter,比如Math.radom(),Date.now()等等,Getter 作为值的抽象也很有用。比如:

const user = {name: 'Alice', age: 30};
console.log(user.name); // Alice


function getUser() {
  return {name: 'Alice', age: 30};
}
console.log(getUser().name); // Alice

通过使用 getter 来表示一个值,我们继承了函数的好处,比如惰性:如果我们不调用 getUser(),那么用户对象就不会白白创建。

我们也得到了实现灵活的好处,因为我们可以通过多种不同的方式计算返回对象,或者通过创建一个普通对象,或者通过返回一个类的实例,或者通过使用原型上的属性等。使用硬编码的值,我们将没有这种灵活性。

Getter 还提供了可以执行副作用的hook。每当执行 getter 时,我们都可以触发有用的副作用,例如 console.log 或触发 Analytics 事件,例如:

function getUser() {
  Analytics.sendEvent('User object is now being accessed');
  return {name: 'Alice', age: 30};
}

getter 上的计算也可以是抽象的,因为函数可以作为 JavaScript 中的一等值传递。例如,考虑这个以 getter 作为参数的加法函数,并返回一个数字的 getter,而不是直接返回一个数字。

function add(getX, getY) {
  return function getZ() {
    const x = getX();
    const y = getY();
    return x + y;
  }
}

当 getter 返回不可预测的值时,这种抽象计算的好处更加明显,例如使用 getter Math.random:

const getTen = () => 10;
const getTenPlusRandom = add(getTen, Math.random);

console.log(getTenPlusRandom()); // 10.948117215055046
console.log(getTenPlusRandom()); // 10.796721274448556
console.log(getTenPlusRandom()); // 10.15350303918338
console.log(getTenPlusRandom()); // 10.829703269933633

getter 与 Promises 一起使用也很常见,因为已知 Promise 不是可重用的计算,因此将 Promise 构造函数包装在 getter(也称为“工厂”或“thunk”)中使其可重用。

const p = function() {
  return new Promise(){}
}

GETTER GETTERS

() => (() => X)

getter-getter 是一个没有输入参数和返回一个 getter 的函数。

getter-getter的出现是为了解决遍历序列的问题。例如,如果我们想显示数字序列是 2 的幂,我们可以使用 getter getNextPowerOfTwo()

let i = 2;
function getNextPowerOfTwo() {
  const next = i;
  i = i * 2;
  return next;
}

console.log(getNextPowerOfTwo()); // 2
console.log(getNextPowerOfTwo()); // 4
console.log(getNextPowerOfTwo()); // 8
console.log(getNextPowerOfTwo()); // 16
console.log(getNextPowerOfTwo()); // 32
console.log(getNextPowerOfTwo()); // 64
console.log(getNextPowerOfTwo()); // 128

上面代码的问题是变量 i 是全局声明的,如果我们想重新启动序列,我们必须以正确的方式操作这个变量,泄露 getter 的实现细节。

为了使上面的代码可重用并且没有全局变量,需要做的是将 getter 包装在另一个函数中。而且这个包装函数也是一个getter。

getter-getters会继承getter的所以优点。比如:例如:(1)实现的灵活性,(2)副作用的钩子,(3)惰性。这次的惰性体现在初始化步骤中。外部函数可以延迟变量初始化,而内部函数可以延迟遍历值。

function getGetNext() {
  let i = 2;
  return function getNext() {
    const next = i;
    i = i * 2;
    return next;
  }
}

let getNext = getGetNext();
console.log(getNext()); // 2
console.log(getNext()); // 4
console.log(getNext()); // 8
getNext = getGetNext(); // 🔷 restart!
console.log(getNext()); // 2
console.log(getNext()); // 4
console.log(getNext()); // 8
console.log(getNext()); // 16
console.log(getNext()); // 32

Iterables

() => (() => ({done, value}))

可迭代函数是特殊化的getter-getter,返回一个对象形式包含value和done的x值。

getter-getter 能够表示可重新启动的值序列,但它们没有约定来表示序列的结束。

返回的迭代对象的done值,可以让外部知道何时终止迭代。

function getGetNext() {
  let i = 40;
  return function getNext() {
    if (i <= 48) {
      const next = i;
      i += 2;
      return {done: false, value: next};
    } else {
      return {done: true};
    }
  }
}

let getNext = getGetNext();
for (let result = getNext(); !result.done; result = getNext()) {
  console.log(result.value);
}

ES6 的迭代器还有进一步约定这个getter-getters的实现,他们会加一个包装对象给每一个getter

  • 外部 getter f 会变成一个对象 { [Symbol.iteration]: f }
  • 内部 getter g 会变成一个对象{ next: g }

这是上述例子符合es6规范的代码:

const oddNums= {
  [Symbol.iterator]: () => {
    let i = 40;
    return {
     next: () => {
       if (i <= 48) {
          const next = i;
          i += 2;
          return {done: false, value: next};
        } else {
          return {done: true};
        }
     }
   }
  }
}

let getOddNum = oddNums[Symbol.iterator]();
for (let result = getOddNum.next(); !result.done; result = getOddNum.next()) {
  console.log(result.value);
}

以这种形式实现就能用到ES6 提供语法糖 for-let-of能更方便地使用 Iterables:(省略next调用并当done为true的时候终止循环)

for (let x of oddNums) {
  console.log(x);
}

为了轻松创建 Iterables,ES6 还提供了生成迭代对象函数语法糖函数*:(即省略掉[Symbol.iterator]对象函数和return的next对象函数,并简化 return{ value: any, done: boolean}, 直接yield后面是value,结束语句return即可)

function* oddNums() {
  let i = 40;
  while (true) {
    if (i <= 48) {
      const value= i;
      i += 2;
      yield value;
    } else {
      return;
    }
  }
}
}

for (let x of oddNums()) {
  console.log(x);
}

自es2015,有了创建迭代对象的语法糖函数function*和迭代迭代对象的语法糖for…of就变得更方便的创建迭代。

const oddNums= {
  [Symbol.iterator](){
    let i = 40;
    let outside, error
    return {
     next(outside){
      try{
          if (i <= 48) {
          const next = i ;
          i += 2;
          outside = outside
          if(error) throw(error)
          return {done: false, value: next};
        } else {
          return {done: true};
        }
      }catch(e){
        console.log(e)
      } 
     },
     error(str){
       error = str
       this.next()
    }
   }
  }
}

let getOddNum = oddNums[Symbol.iterator]();
for (let result = getOddNum.next(); !result.done; result = getOddNum.next()) {
  console.log(result.value);
}

Setters:

X => ()

setter 是一个以 X 作为输入但没有输出的函数

Setter 是一种函数,接受参数,但不返回输出值。 JavaScript 运行时和 DOM 中有许多原生的 setter,例如 console.log(x)、document.write(x) 等。

与 getter 不同,setter 通常不是抽象,因为如果函数没有值,则意味着该函数仅用于发送数据或命令 JavaScript 运行时。

对于我们的例子,getTen是一个对于10的抽象,我们可以传递getter作为一个值,但是传一个setTen是没有意义的,因为你不会从调用函数中拿到什么值。

这就是说setters可以是其他 setter 的简单包装器。考虑 console.log 设置器的这个包装器:

function fancyConsoleLog(str) {
  console.log('⭐ ' + str + ' ⭐');
}

Setter Setters

(X => ()) => ()

setter-setter 是一个以 setter 作为输入但没有输出的函数。

虽然基本的 setter 不是抽象函数(console.log),但 setter-setter 能在代码库当中作为如何利用呈现这个值的抽象函数(fn(cb){cb(val)})。

例如,考虑如何通过这个 setter-setter 来表示数字 10:

function setSetTen(setTen) {
  setTen(10)
}

注意这里没有返回值,因为setter永远不会返回值。通过简单地重命名一些参数,上面的示例可能更具可读性:

function setTenListener(cb) {
  cb(10)
}

顾名思义,cb 代表“回调”,并说明了 setter-setter 在 JavaScript 中是如何常见的,因为有大量的回调用例。

调用一个setter-setter的抽象函数,他的相反方法是调用一个getter

setter(setter)

setter(getter())

下面的这两个示例在功能上是等效的,但调用方式却大不相同。

setSetTen(console.log);

// compare with...

console.log(getTen());

setter-setter 的好处与 getter 相同——惰性、实现灵活性、副作用挂钩——但有两个 getter 没有的新属性:控制反转(函数里面决定如何调用那个传进来的函数,setter作为参数传进来,再setter里面再调用)和异步性。而getter就需要先调用转化为值之后再传给setter。

上面的示例中,使用 getter 的代码指示 getter 何时与 console.log 一起使用。但是,当使用 setter-setter 时,是 setter-setter 本身决定何时调用 console.log。这种控制反转允许 setter-setter 比 getter 拥有更多的权力,例如通过向setter内部发送许多值给传递进来的setter:

function setSetTen(setTen) {
  setTen(10)
  setTen(10)
  setTen(10)
  setTen(10)
}

控制反转还允许 setter-setter 决定何时向回调传递值,例如异步传递。回想一下 setSetTen 的另一个名称可能是 setTenListener:

function setTenListener(cb) {
  setTimeout(() => { cb(10); }, 1000);
}

Promise

(X => (), Err => ()) => ()

Promise是接受两个setter的setter-setter函数,并有一些额外保证。

虽然 setter-setter 功能强大,但由于控制反转,它们可能非常不可预测。它们可以是同步的或异步的,并且可以随时间传递零个或一个或多个值。

Promise 是一种特殊的 setter-setter,它为值的传递提供一些保证。

  • 内部setter不会同步调用。
  • 内部setter只会调用一次。
  • 提供一个可选的第二个setter来传递错误信息

对比:

function setSetTen(setTen) {
  setTen(10)
  setTen(10)
}

console.log('before setSetTen');
setSetTen(console.log);
console.log('after setSetTen');
/ (Log shows:)
// before setSetTen
// 10
// 10
// after setSetTen

const tenPromise = new Promise(function setSetTen(setTen) {
  setTen(10);
  setTen(10);
});

console.log('before Promise.then');
tenPromise.then(console.log);
console.log('after Promise.then');

// (Log shows:)
// before Promise.then
// after Promise.then
// 10

Promise能很方便的表示一个异步的不和重用值的函数,es2017提供能生成Promise和使用promise的语法糖,async-await,async会返回一个由Promise包裹的值,而await能解开Promise的值

async function getTenPromise() {
  return 10;
}
const tenPromise = getTenPromise();

console.log('before Promise.then');
tenPromise.then(console.log);
console.log('after Promise.then');

// (Log shows:)
// before Promise.then
// after Promise.then
// 10

async function main() {
  console.log('before await');
  const ten = await new Promise(function setSetTen(setTen) {
    setTen(10);
  });
  console.log(ten);
  console.log('after await');
}

main();

// (Log shows:)
// before await
// 10
// after await

OBSERVABLES

Observables

(X => (), Err => (), () => ()) => ()

Observable是接受三个setter的setter-setter函数,并有一些额外的保证。

就像 Iterables 是一种特殊类型的 getter-getter 增加了结束的标志,Observables 也是一种增加了用来结束的 setter-setter。JavaScript 中典型的 setter-setter,如 element.addEventListener,不通知事件流是否完成,因此这使得连接事件流或执行其他与完成相关的逻辑变得困难。

不像Iterables,有标准化的js定义,Observables在TC39有过提议,但还没被接受。

Observables 是 Iterables 的对偶,这可以通过一些对称性看出

  • Iterable
    • 是对象
    • 有“iterate” 方法, a.k.a. Symbol.iterator
    • “iterate” 方法是一个Iterator对象里的一个getter方法
    • 返回的Iterator 对象会有一个next方法的getter
  • Observable
    • 是对象
    • 有“observe” 方法, a.k.a. subscribe
    • “observe” 方法是一个Observer 对象里的一个setter方法
    • 返回的Observer对象会有一个next方法的setter

function setter-setter

function setSetTen(next, err, complete) {
   let x = 40;
    let clock = setInterval(() => {
      if (x <= 48) {
        next(x);
        x += 2;
      } else {
        complete();
        clearInterval(clock);
      }
    }, 1000);

   return function() {
     clearInterval(clock)
   }
}

将传进来的三个函数转化为observe对象:

const oddNums = {
  subscribe: (observer) => {
    let x = 40;
    let clock = setInterval(() => {
      if (x <= 48) {
        observer.next(x);
        x += 2;
      } else {
        observer.complete();
        clearInterval(clock);
      }
    }, 1000);
  }
};

oddNums.subscribe({
  next: x => console.log(x),
  complete: () => console.log('done'),
});

与 setter-setter 一样,Observables 会导致控制反转,因此调用代码(oddNums.subscribe)无法暂停或取消传入的数据流。

大多数 Observable 实现添加了一个重要的细节,以允许从消费者到生产者的取消传输订阅。

  subscribe: (observer) => {
    let x = 40;
    let clock = setInterval(() => {
      if (x <= 48) {
        observer.next(x);
        x += 2;
      } else {
        observer.complete();
        clearInterval(clock);
      }
    }, 1000);
    // 🔷 Subscription:
    return {
      unsubscribe: () => {
        clearInterval(clock);
      }
    };
  }
};

const subscription = oddNums.subscribe({
  next: x => console.log(x),
  complete: () => console.log('done'),
});

// 🔷 Cancel the incoming flow of data after 2.5 seconds
setTimeout(() => {
  subscription.unsubscribe();
}, 2500);

Async Iterables

() => (() => Promise<{done, value}>)

异步迭代函数就像一个yield promise的迭代。

Iterables 可以表示任何无限或有限的值序列,但它们有一个限制:一旦消费者调用 next() 方法,该值必须同步可用。AsyncIterables 通过允许“稍后”而不是在请求时立即传递值来扩展 Iterables 的功能。

AsyncIterables 使用 Promise 实现值的异步传递,因为 Promise 代表单个异步值。可以通过调用这个Promise后在value传递(yield就可以传递这个)。

在下面的示例中,我们采用oddNums Iterable 示例并使其产生延迟后解析的值的Promises:

function slowResolve(val) {
  return new Promise(resolve => {
    setTimeout(() => resolve(val), 1000);
  });
}

function* oddNums() {
  let i = 40;
function slowResolve(val) {
  return new Promise(resolve => {
    setTimeout(() => resolve(val), 1000);
  });
}

function* oddNums() {
  let i = 40;
  while (true) {
    if (i <= 48) {
      yield slowResolve(i); // 🔷 yield a Promise
      i += 2;
    } else {
      return;
    }
  }
}

要使用 AsyncIterable,我们可以在请求下一个 Promise 之前等待每个产生的 Promise:

async function main() {
  for (let promise of oddNums()) {
    const x = await promise;
    console.log(x);
  }
  console.log('done');
}

main();

// (Log shows:)
// 40
// 42
// 44
// 46
// 48
// done

上面的示例为 AsyncIterables 创建了一个很直观,但它实际上不是一个有效的 ES2018 AsyncIterables。我们上面所做的是一个 ES6 Iterable of Promises,但 ES2018 AsyncIterables 是一个 {done, value} 对象的 Promise 的 getter-getter。比较这两个:

  • Promise 的可迭代: () => (() => {done, value: Promise<X>})
  • ES2018 AsyncIterable: () => (() => Promise<{done, value}>)

这是因为需要允许返回的done也可能是异步的,所以是用Promise去返回整个{done, value}。

因为AsyncIterable不是有效的Iterable,所以是用另一个Symbol实现[Symbol.asyncIterator]

const oddNums = {
  [Symbol.asyncIterator]: () => {
    let i = 40;
    return {
      next: () => {
        if (i <= 48) {
          const next = i;
          i += 2;
          return slowResolve({done: false, value: next});
        } else {
          return slowResolve({done: true});
        }
      }
    };
  }
};

async function main() {
  let iter = oddNums[Symbol.asyncIterator]();
  let done = false;
  for (let promise = iter.next(); !done; promise = iter.next()) {
    const result = await promise;
    done = result.done;
    if (!done) console.log(result.value);
  }
  console.log('done');
}

main();

跟普通Iterable一样,也有生产端和消费端的语法糖

function* -> async function *

for..of -> await for..of

function sleep(period) {
  return new Promise(resolve => {
    setTimeout(() => resolve(true), period);
  });
}

// 🔷 Production side can use both `await` and `yield`
async function* oddNums() {
  let i = 40;
  while (true) {
    if (i <= 48) {
      await sleep(1000);
      yield i;
      i += 2;
    } else {
      await sleep(1000);
      return;
    }
  }
}

async function main() {
  // 🔷 Consumption side uses the new syntax `for await`
  for await (let x of oddNums()) {
    console.log(x);
  }
  console.log('done');
}

main();

这对于迭代fetch发送请求就非常方便了

async function* users(from, to) {
  for (let x = from; x <= to; x++) {
    const res = await fetch('http://jsonplaceholder.typicode.com/users/' + x);
    const json = await res.json();
    yield json;
  }
}

async function main() {
  for await (let x of users(1, 10)) {
    console.log(x);
  }
}

main();

Operators

Function也可以像值一样传递给函数,无论是上面的Observables or AsyncIterables,所以我们也能在别的函数里操纵他们来实现功能。

最常见的操作之一是 map,它在数组中很流行,但也与其他抽象相关。在下面的示例中,我们为 AsyncIterables 创建了 map 运算符,并使用它来创建数据库中用户名的 AsyncIterable。

async function* users(from, to) {
  for (let i = from; i <= to; i++) {
    const res = await fetch('http://jsonplaceholder.typicode.com/users/' + i);
    const json = await res.json();
    yield json;
  }
}

// 🔷 Map operator for AsyncIterables
async function* map(inputAsyncIter, f) {
  for await (let x of inputAsyncIter) {
    yield f(x);
  }
}

async function main() {
  const allUsers = users(1, 10);
  // 🔷 Pass `allUsers` around, create a new AsyncIterable `names`
  const names = map(allUsers, user => user.name);
  for await (let name of names) {
    console.log(name);
  }
}

main();

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注