温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

JavaScript getter setter金字塔​​​​​​​怎么实现

发布时间:2022-08-17 16:30:10 来源:亿速云 阅读:153 作者:iii 栏目:开发技术

这篇文章主要介绍“JavaScript getter setter金字塔怎么实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“JavaScript getter setter金字塔怎么实现”文章能帮助大家解决问题。

    引言

    函数是JavaScript的基石。 它是一种灵活的、抽象的,可作为其他抽象的基础, 如:Promise、Iterables、Observables等。 

    函数

    JavaScript的基础是一级值,如: numbers、strings、objects、booleans等。 虽然,您可以只使用这些值和控制流(if/else/for等)来编写程序,但很快您可能需要编写一个新的函数来改进您的程序。

    JavaScript中必须通过函数进行抽象,像异步I/O操作等通常都必须使用callback回调函数。 JavaScript中的函数,与【函数式编程】中的纯函数不同,建议最好将它们简单理解为“流程”,因为它只得惰性的可重用代码块,具有可选的输入(参数)和可选的输出(返回值)

    与硬编码相比,函数有以下几个重要的好处:

    • 懒惰/可重用性, 函数内的代码必须是惰性的(即除非被调用,否则不会执行)才能使其可重用

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

    getters

    getter是一种不用传递任何参数但需要有返回值的函数。
    函数定义: () => X

    JavaScript getter setter金字塔​​​​​​​怎么实现

    getter是一种不用传递任何参数但需要有返回值的函数。 JavaScript中有许多这样的getter, 例如: Math.random()、Date.now()等

    getter作为值的抽象也很有用,例如:user 与 getUser

    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()user对象就不会被白白创建。 同时,我们利用了函数的另一好处:实现的灵活性, 因为我们可以通过多种不同的方式来返回对象。

    getter还允许我们对副作用有一个钩子,当执行getter时,我们可以触发有用的副作用函数,比如console.log输出日志 或是 触发Analytics事件等,例如:

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

    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与Promise一起搭配使用,因为Promise是不可重用的计算,因此,将Promise的构造函数包装在一个getter中(如我们熟知的"工厂函数"或"thunk")使其可重用。

    setters

    setters是有一个或多个输入参数,但没有返回值的函数。
    函数定义: X => ()

    JavaScript getter setter金字塔​​​​​​​怎么实现

    setters是有一个或多个输入参数,但没有返回值的函数。 在JavaScript运行时或DOM中有许多这样的setters, 比如: console.log(x), document.write(x)等。

    与getter不同,setter通常不用抽象。 因为函数没有返回值,这意味着函数仅用于发送数据或执行JavaScript命令等。 例如, 上文中的getter getTen是数字10的抽象,我们可以将它作为值进行传递,但如果将函数setTen作为值传递是没有意义的,因为它没有返回值。

    换句话说,setter可以是其他setter的简单包装器。例如:简单包装下setter console.log:

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

    getter-getters

    getter-getters是一种不需要输入参数且返回一个getter的函数。
    函数定义: () => (() => X)

    JavaScript getter setter金字塔​​​​​​​怎么实现

    “getter-getter”: 一种特殊类型的getter,其返回值是一个getter.

    对getter的原始需求是使用getter的返回值进行迭代。
    例如,我们想显示二次幂的数字序列,我们可以使用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是全局声明的,若我们想重新迭代序列,则必须要重置变量i, 从而泄漏了getter的实现细节。

    若想将上述代码变为可重用且不包含全局变量,我们需要将getter封装在另一个函数中,这个包装函数也是一个getter.

    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

    可以看出,getter-getter是一种特殊类型的getter, 它继承了getter的所有优点,例如:

    • 实现的灵活性

    • 用于副作用的钩子(hook)

    • 惰性,其惰性体现在函数在初始化时,外部函数启用延迟初始化,而内部函数启用值时延迟初始化。

    function getGetNext() {
      // ???? LAZY INITIALIZATION
      let i = 2;
    
      return function getNext() {
        // ???? LAZY ITERATION
        const next = i;
        i = i * 2;
        return next;
      }
    }

    setter-setter

    setter-setter是一种以setter作为输入且无返回值的函数。 函数定义: (X => ()) => ()

    JavaScript getter setter金字塔​​​​​​​怎么实现

    setter-setter是一种特殊的setter函数,其中传递的参数也是setter。虽然基本的setter不是抽象函数,但setter-setter是能够表示可在代码库中传递的值的抽象。 例如下列中,通过这个setter来表示数字10:

    function setSetTen(setTen) {
      setTen(10)
    }

    注意: setter没有返回值。

    通过简单地重构,让上述示例代码的可读性更强:

    function setTenListener(cb) {
      cb(10)
    }

    cb,即callback缩写,名为“回调”,它表明在JavaScript中如何使用setter-setter

    在JavaScript中,有大量回调的案例。 顾名思义,cb代表“回调”,通过上述示例代码来说明,cb在setter-setter如何使用。

    下面两个示例,在功能上是等价的,但是调用方式不同。

    setSetTen(console.log);
    
    // compare with...
    
    console.log(getTen());

    setter-setter具有与getter-getter相同的好处:

    • 实现的灵活性

    • 用于副作用的钩子(hook)

    • 惰性,其惰性体现在函数在初始化时,外部函数启用延迟初始化,而内部函数启用值时延迟初始化。

    同时,它还有两个getter-getter所没有的新特性:

    • 控制反转

    • 异步性

    在上面的示例中,使用getter的代码指示何时使用“console.log”使用getter。
    然而,当使用setter-setter时,setter-seter本身决定何时调用“console.log”。 这种控制反转允许setter-setter拥有比getter更大的能力,例如通过向回调函数传递许多值, 例如:

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

    控制反转还可以使setter决定何时向回调传递值,例如: 异步传递。 试想一下,若将“setSetTen”的更换为“setTenListener”呢?

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

    setter-setter在JavaScript异步编程中的很常见,但回调函数不一定是异步的。
    例如,下面的“setSetTen”示例中,它是同步的:

    function setSetTen(setTen) {
      setTen(10)
    }
    
    console.log('before');
    setSetTen(console.log);
    console.log('after');
    
    // (Log shows:)
    // before
    // 10
    // after

    iterables

    iterable是省略了实现细节的getter-getter, 其返回值是一个描述型的对象值或完成时的对象。

    An iterable is (with some details omitted:) a getter-getter of an object that describes either a value or completion

    函数定义: () => (() => ({done, value}))

    getter-getter可以重启序列值,但它没有通知序列何时结束的约定。
    Iterables是一种特殊的getter-getter,它的返回值中始终有2个属性对象:

    • done,布尔值,表示是否完成

    • value,任意值,实际传递的值(在done的值不为true时)

    完成指示符done,能够使iterable的代码在执行时,知道后续GET将返回无效数据,因此iterable知道何时停止迭代。
    在下面的示例中,我们可以根据完成指示符生成一个偶数范围为40到48的有限序列:

    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 Iterables除了简单的() => (() => ({done, value}))模式之外,还有更多的约定。它们在每个getter上添加了一个包装器对象:

    • 外部getter包含对象:{[Symbol.iterator]:f}

    • 内部getter包含对象: {next:g}

    以下是与上一个示例代码功能等价的ES6 Iterable代码:

    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 iterator = oddNums[Symbol.iterator]();
    for (let result = iterator.next(); !result.done; result = iterator.next()) {
      console.log(result.value);
    }

    注意,两个示例间实现的差异点:

    -function getGetNext() {
    +const oddNums = {
    +  [Symbol.iterator]: () => {
         let i = 40;
    -  return function getNext() {
    +    return {
    +      next: () => {
             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()) {
    +let iterator = oddNums[Symbol.iterator]();
    +for (let result = iterator.next(); !result.done; result = iterator.next()) {
      console.log(result.value);
    }

    ES6提供了语法糖for let以便快速迭代对象:

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

    为了方便创建Iterables,ES6还提供了生成器函数语法糖 function*

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

    使用生产端语法糖和**消费端语法糖*,自2015年以来,可迭代函数是JavaScript中可完成值序列的易于使用的抽象。请注意,调用生成器函数将返回可迭代函数,生成器函数本身不是可迭代函数:

    自2015年后,更易于使用iterables来抽象可序列化对象值。 需要注意的是,调用生成器函数将返回一个可迭代对象,生成器函数本身不可迭代。

    function* oddNums() {
      let i = 40;
      while (true) {
        if (i <= 48) {
          yield i;
          i += 2;
        } else {
          return;
        }
      }
    }
    
    for (let x of oddNums()) {
      console.log(x);
    }

    promises

    Promise是一个(省略了一些细节)特殊的setter, 它包含2个setter, 并且还有额外的保障。
    函数定义: (X => (), Err => ()) => ()

    JavaScript getter setter金字塔​​​​​​​怎么实现

    虽然setter-setter功能强大,但由于控制反转,它们可能变得非常不可预测。

    • 它们可以是同步或异步的

    • 也可以随时间传递零或一个或多个值。

    Promise是一种特殊的setter,它为返回值提供一些特殊的保证:

    • 内部setter(“回调”)从不同步调用

    • 内部setter最多调用一次

    • 提供了一个可选的第二setter,用于传递错误值

    将下面的setter与等价的Promise代码比较,可以看出:

    • Promise只提供一次值,且不会在两个console.log输出

    • Promise的返回值是异步返回的:

    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

    promise的实现:

    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以后,可以使用async - await语法糖来编写Promise。 在函数前使用async关键字,在需要使用Promise值的位置使用await来接收值。

    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

    语法糖async - await也可用于创建Promise, 因为async函数将返回一个Promise对象,该Promise对象将返回return的值。

    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

    Observables

    observable是(省略了一些细节:)一个包含有3个setter的setter函数,并带有额外的保障。
    函数定义: (X => (), Err => (), () => ()) => ()

    JavaScript getter setter金字塔​​​​​​​怎么实现

    GetterSetterFunctionValue

    像Iterables一样,GetterSetterFunctionValue是一种特殊类型的getter-getter,它增加了发送完成信号的能力,Observable也是一种setter-setter,它也增加了完成能力。
    JavaScript中的典型setter-setter,如: element.addEventListener不会通知事件流是否完成,因此很难连接事件流或执行其他与完成相关的逻辑。

    与JavaScript规范中标准化的可伸缩性不同,可观测性是在几个库中找到的松散约定,如:

    • RxJS

    • most.js

    • xstream

    • Bacon.js

    尽管正在考虑将proposal-observable作为一个TC39方案,但该方案仍在不断修改。
    因此在本文中,让我们假设遵循Fantasy Observable规范(其中RxJS、most.js和xstream等最受欢迎库都遵循这一规范)。

    Observables the dual of Iterables,有以下特性:

    可迭代:

    • 它是一个对象

    • 可迭代,有一个“iterate”属性方法,即:Symbol.iterator

    • “iterate” 方法是迭代器对象的 getter 

    • 迭代器对象有一个名为next的 getter

    可观察:

    • 它是一个对象

    • 有可观察method,即:subscribe

    • 可观察method是Observer对象的setter

    • Observer有一个名为nextsetter

    Observer对象还可以包含另外两个方法:

    • complete,表示成功完成,相当于iterator中的“done”指示符

    • error,表示失败完成,相当iterator中执行时引发异常

    与Promise一样,Observable为返回值增加了一些保障:

    • 一旦调用了complete,则不会调用error

    • 一旦调用了error,就不会调用complete

    • 一旦调用了complete或error,则不会调用next

    在下面的示例中,Observable用于异步返回有限的数字序列

    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'),
    });
    
    // (Log shows:)
    // 40
    // 42
    // 44
    // 46
    // 48
    // done

    与setter-setter一样,Observable具有控制反转的能力,因此消费端(oddNums.subscribe)无法暂停或取消传入的数据流。
    大多数Observable的实现都添加了一个重要的细节,允许消费者取消订阅: unsubscribe

    “subscribe”函数将返回一个对象subscription,即“unsubscribe”。 消费端可以使用该方法取消订阅。
    因此,“subscribe”不再是setter,因为它是一个具有输入(观察者)和输出(订阅)的函数。

    下面,我们在前面的示例中添加了一个订阅对象:

    const oddNums = {
      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);
    
    // (Log shows:)
    // 40
    // 42

    async iterables

    异步可迭代函数(省略了一些细节)类似于yield promise的可迭代函数
    函数定义: () => (() => Promise<{done, value}>)

    JavaScript getter setter金字塔​​​​​​​怎么实现

    Iterables可以表示任何无限或有限的值序列,但它们有一个限制:

    • 只要使用者调用next()方法,返回值就必须是同步的。

    AsyncIterables扩展了Iterables的功能,它通过允许延后返回值,而不是在调用时立即返回。
    AsyncIterables通过使用Promise实现值的异步传递,因为Promise表示单个异步值。每次调用迭代器的next()(内部getter函数),都会创建并返回一个Promise。 在下面的示例中,我们以oddNums可迭代为例,其在迭代时每次返回一个Promise对象:

    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;
        }
      }
    }

    要使用Asynciteable,我们可以使用async - await语法糖,来遍历&获取异步迭代器的值:

    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

    虽然上例中通过async - await语法糖(ES6语法)能够编写出可读性较好的代码。但是,在ES2018中,异步迭代是通过{done,value}(Promise)对象来迭代的。

    比较一下ES6&ES2018异步迭代器的函数定义:

    • ES6异步迭代器: () => (() => {done, value: Promise<X>})

    • ES2018异步迭代器: () => (() => Promise<{done, value}>)

    可以看出,ES2018异步可迭代函数返回的不是一个可迭代函数,它将返回一个Promise对象,但在许多方面与ES6异步迭代器相似。
    这一细节的原因是,异步可伸缩性还需要允许异步发送完成(done),因此Promise必须包裹{done,value}对象。

    因为AsyncIterables不是Iterables,所以它们使用不同的符号。

    • Iterables 使用Symbol.iterator

    • AsyncIterables 使用'Symbol.asyncItrator'

    在下面的示例中,我们使用ES2018 AsyncIterable实现了一个前例等价的功能:

    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();

    像Iterables有语法糖function*for&ndash;let&ndash;of,以及Promises有async&ndash;await语法糖一样,ES2018中的AsyncIterable有两个语法糖功能:

    • 生产端:async function*

    • 消费端:for&ndash;await&ndash;let&ndash;of

    在下面的示例中,我们使用这两种功能创建异步数字序列,并使用for-await循环遍历它们:

    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();

    尽管它们是新特性,但Babel、TypeScript、Firefox、Chrome、Safari 和 Node.js中都已经支持了。

    AsyncIterables可以方便地结合基于Promise的API(如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

    在本文中,抽象的列举了一些特殊的JavaScript函数示例。 根据定义,它们的功能的不可能比函数更强大。因此,函数是最强大、最灵活的抽象。完全灵活性的缺点是不可预测性,这些抽象提供的是“保证”,基于这些“保证”,让您可以编写更有组件、更可预测的代码。

    另一方向, 函数可以作为JavaScript的值,允许将它进行传递和操纵。
    在本文中,我们将Iterable、Observable、AsyncIterable作为值进行传递,并在传递过程中对其进行操作。

    最常见的操作之一是map映射,它在数组中经常使用到,但也与其他抽象相关。 在下面的示例中,我们为AsyncIterables创建map操作符,并使用它用来创建异步迭代器,以获取数据库的用户信息。

    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();

    上面的示例代码,没有被抽象至Getter - Setter金字塔中。
    若使用getter - setter来实现,需要更多的代码,可读性也不好。
    因此,在不牺牲可读性的情况下,使用运算符和新的语法糖,用更少的代码编写函数做更多的事情,以处理这种特殊的场景。

    JavaScript getter setter金字塔​​​​​​​怎么实现

    关于“JavaScript getter setter金字塔怎么实现”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注亿速云行业资讯频道,小编每天都会为大家更新不同的知识点。

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI