JavaScript getter setter金字塔怎么实现
导读:本文共14306.5字符,通常情况下阅读需要48分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要:这篇文章主要介绍“JavaScriptgettersetter金字塔怎么实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“JavaScriptgettersetter金字塔怎么实现”文章能帮助大家解决问题。引言函数是JavaScript的基石。 它是一种灵活的、抽象的,可作为其他抽象的基础, 如:Promise、Iterables、Observables等... ...
目录
(为您整理了一些要点),点击可以直达。这篇文章主要介绍“JavaScriptgettersetter金字塔怎么实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“JavaScriptgettersetter金字塔怎么实现”文章能帮助大家解决问题。
引言
函数是JavaScript的基石。 它是一种灵活的、抽象的,可作为其他抽象的基础, 如:Promise、Iterables、Observables等。
函数
JavaScript的基础是一级值,如: numbers、strings、objects、booleans等。 虽然,您可以只使用这些值和控制流(if/else/for等)来编写程序,但很快您可能需要编写一个新的函数来改进您的程序。
JavaScript中必须通过函数进行抽象,像异步I/O操作等通常都必须使用callback回调函数。 JavaScript中的函数,与【函数式编程】中的纯函数
不同,建议最好将它们简单理解为“流程”,因为它只得惰性的可重用代码块,具有可选的输入(参数)和可选的输出(返回值)
与硬编码相比,函数有以下几个重要的好处:
懒惰/可重用性, 函数内的代码必须是惰性的(即除非被调用,否则不会执行)才能使其可重用
实现的灵活性,函数的使用者并不关心函数内部是如何实现的,这意味着可以各种方式灵活的实现函数。
getters
getter是一种不用传递任何参数但需要有返回值的函数。
函数定义:() => X
getter是一种不用传递任何参数但需要有返回值的函数。 JavaScript中有许多这样的getter, 例如: Math.random()、Date.now()等
getter作为值的抽象也很有用,例如:user
与getUser
:
constuser={name:'Alice',age:30};console.log(user.name);//AlicefunctiongetUser(){ return{name:'Alice',age:30};}console.log(getUser().name);//Alice
通过使用getter, 我们可以利用函数的一个好处:惰性
. 即我们不调用getUser()
,user
对象就不会被白白创建。 同时,我们利用了函数的另一好处:实现的灵活性, 因为我们可以通过多种不同的方式来返回对象。
getter还允许我们对副作用有一个钩子,当执行getter时,我们可以触发有用的副作用函数,比如console.log
输出日志 或是 触发Analytics事件等,例如:
functiongetUser(){ Analytics.sendEvent('Userobjectisnowbeingaccessed'); return{name:'Alice',age:30};}
getter上的计算,也是可以抽象的。例如:
functionadd(getX,getY){ returnfunctiongetZ(){ constx=getX(); consty=getY(); returnx+y; }}
当getter返回不可预测的值时,这种抽象计算的好处更加明显,例如:使用getter添加Math.random
:
constgetTen=()=>10;constgetTenPlusRandom=add(getTen,Math.random);console.log(getTenPlusRandom());//10.948117215055046console.log(getTenPlusRandom());//10.796721274448556console.log(getTenPlusRandom());//10.15350303918338console.log(getTenPlusRandom());//10.829703269933633
比较常见的是getter与Promise一起搭配使用,因为Promise是不可重用的计算,因此,将Promise的构造函数包装在一个getter中(如我们熟知的"工厂函数"或"thunk")使其可重用。
setters
setters是有一个或多个输入参数,但没有返回值的函数。
函数定义:X => ()
setters是有一个或多个输入参数,但没有返回值的函数。 在JavaScript运行时或DOM中有许多这样的setters, 比如:console.log(x), document.write(x)
等。
与getter不同,setter通常不用抽象。 因为函数没有返回值,这意味着函数仅用于发送数据或执行JavaScript命令等。 例如, 上文中的gettergetTen
是数字10的抽象,我们可以将它作为值进行传递,但如果将函数setTen
作为值传递是没有意义的,因为它没有返回值。
换句话说,setter可以是其他setter的简单包装器。例如:简单包装下setterconsole.log
:
functionfancyConsoleLog(str){console.log('⭐'+str+'⭐');}
getter-getters
getter-getters是一种不需要输入参数且返回一个getter的函数。
函数定义:() => (() => X)
“getter-getter”: 一种特殊类型的getter,其返回值是一个getter.
对getter的原始需求是使用getter
的返回值进行迭代。
例如,我们想显示二次幂的数字序列,我们可以使用gettergetNextPowerOfTwo()
leti=2;functiongetNextPowerOfTwo(){constnext=i;i=i*2;returnnext;}console.log(getNextPowerOfTwo());//2console.log(getNextPowerOfTwo());//4console.log(getNextPowerOfTwo());//8console.log(getNextPowerOfTwo());//16console.log(getNextPowerOfTwo());//32console.log(getNextPowerOfTwo());//64console.log(getNextPowerOfTwo());//128
在上面示例代码中,变量i
是全局声明的,若我们想重新迭代序列,则必须要重置变量i
, 从而泄漏了getter的实现细节。
若想将上述代码变为可重用且不包含全局变量,我们需要将getter封装在另一个函数中,这个包装函数也是一个getter.
functiongetGetNext(){leti=2;returnfunctiongetNext(){constnext=i;i=i*2;returnnext;}}letgetNext=getGetNext();console.log(getNext());//2console.log(getNext());//4console.log(getNext());//8getNext=getGetNext();//????restart!console.log(getNext());//2console.log(getNext());//4console.log(getNext());//8console.log(getNext());//16console.log(getNext());//32
可以看出,getter-getter是一种特殊类型的getter, 它继承了getter的所有优点,例如:
实现的灵活性
用于副作用的钩子(hook)
惰性,其惰性体现在函数在初始化时,外部函数启用延迟初始化,而内部函数启用值时延迟初始化。
functiongetGetNext(){//????LAZYINITIALIZATIONleti=2;returnfunctiongetNext(){//????LAZYITERATIONconstnext=i;i=i*2;returnnext;}}
setter-setter
setter-setter是一种以setter作为输入且无返回值的函数。 函数定义:(X => ()) => ()
setter-setter
是一种特殊的setter函数,其中传递的参数也是setter。虽然基本的setter不是抽象函数,但setter-setter
是能够表示可在代码库中传递的值的抽象。 例如下列中,通过这个setter来表示数字10:
functionsetSetTen(setTen){setTen(10)}
注意: setter没有返回值。
通过简单地重构,让上述示例代码的可读性更强:
functionsetTenListener(cb){cb(10)}
cb
,即callback缩写,名为“回调”,它表明在JavaScript中如何使用setter-setter
。
在JavaScript中,有大量回调的案例。 顾名思义,cb
代表“回调”,通过上述示例代码来说明,cb在setter-setter
如何使用。
下面两个示例,在功能上是等价的,但是调用方式不同。
setSetTen(console.log);//comparewith...console.log(getTen());
setter-setter
具有与getter-getter
相同的好处:
实现的灵活性
用于副作用的钩子(hook)
惰性,其惰性体现在函数在初始化时,外部函数启用延迟初始化,而内部函数启用值时延迟初始化。
同时,它还有两个getter-getter
所没有的新特性:
控制反转
异步性
在上面的示例中,使用getter的代码指示何时使用“console.log”使用getter。
然而,当使用setter-setter
时,setter-seter
本身决定何时调用“console.log”。 这种控制反转允许setter-setter拥有比getter更大的能力,例如通过向回调函数传递许多值, 例如:
functionsetSetTen(setTen){setTen(10)setTen(10)setTen(10)setTen(10)}
控制反转还可以使setter
决定何时向回调传递值,例如:异步传递
。 试想一下,若将“setSetTen”的更换为“setTenListener”呢?
functionsetTenListener(cb){setTimeout(()=>{cb(10);},1000);}
setter-setter
在JavaScript异步编程中的很常见,但回调函数不一定是异步的。
例如,下面的“setSetTen”示例中,它是同步的:
functionsetSetTen(setTen){setTen(10)}console.log('before');setSetTen(console.log);console.log('after');//(Logshows:)//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的有限序列:
functiongetGetNext(){leti=40;returnfunctiongetNext(){if(i<=48){constnext=i;i+=2;return{done:false,value:next};}else{return{done:true};}}}letgetNext=getGetNext();for(letresult=getNext();!result.done;result=getNext()){console.log(result.value);}
ES6 Iterables除了简单的() => (() => ({done, value}))
模式之外,还有更多的约定。它们在每个getter上添加了一个包装器对象:
外部getter包含对象:
{[Symbol.iterator]:f}
内部getter包含对象:
{next:g}
以下是与上一个示例代码功能等价的ES6 Iterable
代码:
constoddNums={[Symbol.iterator]:()=>{leti=40;return{next:()=>{if(i<=48){constnext=i;i+=2;return{done:false,value:next};}else{return{done:true};}}}}}letiterator=oddNums[Symbol.iterator]();for(letresult=iterator.next();!result.done;result=iterator.next()){console.log(result.value);}
注意,两个示例间实现的差异点:
-functiongetGetNext(){+constoddNums={+[Symbol.iterator]:()=>{leti=40;-returnfunctiongetNext(){+return{+next:()=>{if(i<=48){constnext=i;i+=2;return{done:false,value:next};}else{return{done:true};}}+}}+}-letgetNext=getGetNext();-for(letresult=getNext();!result.done;result=getNext()){+letiterator=oddNums[Symbol.iterator]();+for(letresult=iterator.next();!result.done;result=iterator.next()){console.log(result.value);}
ES6提供了语法糖for let
以便快速迭代对象:
for(letxofoddNums){console.log(x);}
为了方便创建Iterables,ES6还提供了生成器函数语法糖function*
:
function*oddNums(){leti=40;while(true){if(i<=48){constnext=i;i+=2;yieldnext;}else{return;}}}
使用生产端语法糖和**消费端语法糖*,自2015年以来,可迭代函数是JavaScript中可完成值序列的易于使用的抽象。请注意,调用生成器函数将返回可迭代函数,生成器函数本身不是可迭代函数:
自2015年后,更易于使用iterables来抽象可序列化对象值。 需要注意的是,调用生成器函数将返回一个可迭代对象,生成器函数本身不可迭代。
function*oddNums(){leti=40;while(true){if(i<=48){yieldi;i+=2;}else{return;}}}for(letxofoddNums()){console.log(x);}
promises
Promise是一个(省略了一些细节)特殊的setter, 它包含2个setter, 并且还有额外的保障。
函数定义:(X => (), Err => ()) => ()
虽然setter-setter功能强大,但由于控制反转
,它们可能变得非常不可预测。
它们可以是同步或异步的
也可以随时间传递零或一个或多个值。
Promise是一种特殊的setter,它为返回值提供一些特殊的保证:
内部setter(“回调”)从不同步调用
内部setter最多调用一次
提供了一个可选的第二setter,用于传递错误值
将下面的setter与等价的Promise代码比较,可以看出:
Promise只提供一次值,且不会在两个
console.log
输出Promise的返回值是异步返回的:
setter-setter的实现:
functionsetSetTen(setTen){setTen(10)setTen(10)}console.log('beforesetSetTen');setSetTen(console.log);console.log('aftersetSetTen');//(Logshows:)//beforesetSetTen//10//10//aftersetSetTen
promise的实现:
consttenPromise=newPromise(functionsetSetTen(setTen){setTen(10);setTen(10);});console.log('beforePromise.then');tenPromise.then(console.log);console.log('afterPromise.then');//(Logshows:)//beforePromise.then//afterPromise.then//10
Promise可以方便的用于表示:一个异步且只返回一次的值。 在ES2017以后,可以使用async - await
语法糖来编写Promise。 在函数前使用async
关键字,在需要使用Promise值的位置使用await
来接收值。
asyncfunctionmain(){console.log('beforeawait');constten=awaitnewPromise(functionsetSetTen(setTen){setTen(10);});console.log(ten);console.log('afterawait');}main();//(Logshows:)//beforeawait//10//afterawait
语法糖async - await
也可用于创建Promise, 因为async
函数将返回一个Promise对象,该Promise对象将返回return
的值。
asyncfunctiongetTenPromise(){return10;}consttenPromise=getTenPromise();console.log('beforePromise.then');tenPromise.then(console.log);console.log('afterPromise.then');//(Logshows:)//beforePromise.then//afterPromise.then//10
Observables
observable是(省略了一些细节:)一个包含有3个setter的setter函数,并带有额外的保障。
函数定义:(X => (), Err => (), () => ()) => ()
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等最受欢迎库都遵循这一规范)。
Observablesthe dual of Iterables,有以下特性:
可迭代:
它是一个对象
可迭代,有一个“iterate”属性方法,即:
Symbol.iterator
“iterate” 方法是迭代器对象的getter
迭代器对象有一个名为
next
的getter
可观察:
它是一个对象
有可观察method,即:
subscribe
可观察method是Observer对象的setter
Observer有一个名为
next
的setter
Observer对象还可以包含另外两个方法:
complete,表示成功完成,相当于iterator中的“done”指示符
error,表示失败完成,相当iterator中执行时引发异常
与Promise一样,Observable为返回值增加了一些保障:
一旦调用了complete,则不会调用error
一旦调用了error,就不会调用complete
一旦调用了complete或error,则不会调用
next
在下面的示例中,Observable用于异步返回有限的数字序列
constoddNums={subscribe:(observer)=>{letx=40;letclock=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'),});//(Logshows:)//40//42//44//46//48//done
与setter-setter一样,Observable具有控制反转的能力,因此消费端(oddNums.subscribe
)无法暂停或取消传入的数据流。
大多数Observable的实现都添加了一个重要的细节,允许消费者取消订阅:unsubscribe
。
“subscribe”函数将返回一个对象subscription,即“unsubscribe”。 消费端可以使用该方法取消订阅。
因此,“subscribe”不再是setter,因为它是一个具有输入(观察者)和输出(订阅)的函数。
下面,我们在前面的示例中添加了一个订阅对象:
constoddNums={subscribe:(observer)=>{letx=40;letclock=setInterval(()=>{if(x<=48){observer.next(x);x+=2;}else{observer.complete();clearInterval(clock);}},1000);//????Subscription:return{unsubscribe:()=>{clearInterval(clock);}};}};constsubscription=oddNums.subscribe({next:x=>console.log(x),complete:()=>console.log('done'),});//????Canceltheincomingflowofdataafter2.5secondssetTimeout(()=>{subscription.unsubscribe();},2500);//(Logshows:)//40//42
async iterables
异步可迭代函数(省略了一些细节)类似于yield promise的可迭代函数
函数定义:() => (() => Promise<{done, value}>)
Iterables可以表示任何无限或有限的值序列,但它们有一个限制:
只要使用者调用
next()
方法,返回值就必须是同步的。
AsyncIterables扩展了Iterables的功能,它通过允许延后返回值,而不是在调用时立即返回。
AsyncIterables通过使用Promise实现值的异步传递,因为Promise表示单个异步值。每次调用迭代器的next()
(内部getter函数),都会创建并返回一个Promise。 在下面的示例中,我们以oddNums
可迭代为例,其在迭代时每次返回一个Promise对象:
functionslowResolve(val){returnnewPromise(resolve=>{setTimeout(()=>resolve(val),1000);});}function*oddNums(){leti=40;while(true){if(i<=48){yieldslowResolve(i);//????yieldaPromisei+=2;}else{return;}}}
要使用Asynciteable,我们可以使用async - await
语法糖,来遍历&获取异步迭代器的值:
asyncfunctionmain(){for(letpromiseofoddNums()){constx=awaitpromise;console.log(x);}console.log('done');}main();//(Logshows:)//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实现了一个前例等价的功能:
constoddNums={[Symbol.asyncIterator]:()=>{leti=40;return{next:()=>{if(i<=48){constnext=i;i+=2;returnslowResolve({done:false,value:next});}else{returnslowResolve({done:true});}}};}};asyncfunctionmain(){letiter=oddNums[Symbol.asyncIterator]();letdone=false;for(letpromise=iter.next();!done;promise=iter.next()){constresult=awaitpromise;done=result.done;if(!done)console.log(result.value);}console.log('done');}main();
像Iterables有语法糖function*
和for–let–of
,以及Promises有async–await
语法糖一样,ES2018中的AsyncIterable有两个语法糖功能:
生产端:
async function*
消费端:
for–await–let–of
在下面的示例中,我们使用这两种功能创建异步数字序列,并使用for-await
循环遍历它们:
functionsleep(period){returnnewPromise(resolve=>{setTimeout(()=>resolve(true),period);});}//????Productionsidecanuseboth`await`and`yield`asyncfunction*oddNums(){leti=40;while(true){if(i<=48){awaitsleep(1000);yieldi;i+=2;}else{awaitsleep(1000);return;}}}asyncfunctionmain(){//????Consumptionsideusesthenewsyntax`forawait`forawait(letxofoddNums()){console.log(x);}console.log('done');}main();
尽管它们是新特性,但Babel、TypeScript、Firefox、Chrome、Safari 和 Node.js中都已经支持了。
AsyncIterables可以方便地结合基于Promise的API(如fetch
)来创建异步序列。 例如在数据库中列出用户,每次请求一个用户:
asyncfunction*users(from,to){for(letx=from;x<=to;x++){constres=awaitfetch('http://jsonplaceholder.typicode.com/users/'+x);constjson=awaitres.json();yieldjson;}}asyncfunctionmain(){forawait(letxofusers(1,10)){console.log(x);}}main();
operators
在本文中,抽象的列举了一些特殊的JavaScript函数示例。 根据定义,它们的功能的不可能比函数更强大。因此,函数是最强大、最灵活的抽象。完全灵活性的缺点是不可预测性,这些抽象提供的是“保证”,基于这些“保证”,让您可以编写更有组件、更可预测的代码。
另一方向, 函数可以作为JavaScript的值,允许将它进行传递和操纵。
在本文中,我们将Iterable、Observable、AsyncIterable作为值进行传递,并在传递过程中对其进行操作。
最常见的操作之一是map
映射,它在数组中经常使用到,但也与其他抽象相关。 在下面的示例中,我们为AsyncIterables创建map
操作符,并使用它用来创建异步迭代器,以获取数据库的用户信息。
asyncfunction*users(from,to){for(leti=from;i<=to;i++){constres=awaitfetch('http://jsonplaceholder.typicode.com/users/'+i);constjson=awaitres.json();yieldjson;}}//????MapoperatorforAsyncIterablesasyncfunction*map(inputAsyncIter,f){forawait(letxofinputAsyncIter){yieldf(x);}}asyncfunctionmain(){constallUsers=users(1,10);//????Pass`allUsers`around,createanewAsyncIterable`names`constnames=map(allUsers,user=>user.name);forawait(letnameofnames){console.log(name);}}main();
上面的示例代码,没有被抽象至Getter - Setter
金字塔中。
若使用getter - setter
来实现,需要更多的代码,可读性也不好。
因此,在不牺牲可读性的情况下,使用运算符和新的语法糖,用更少的代码编写函数做更多的事情,以处理这种特殊的场景。
关于“JavaScriptgettersetter金字塔怎么实现”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注亿速云行业资讯频道,小编每天都会为大家更新不同的知识点。
JavaScript getter setter金字塔怎么实现的详细内容,希望对您有所帮助,信息来源于网络。