欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

從延遲處理解析JavaScript惰性編程

 更新時(shí)間:2022年10月15日 09:53:20   作者:掘金安東尼  
這篇文章主要為大家介紹了從延遲處理解析JavaScript惰性編程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前文回顧

# ?從歷史講起,JavaScript 基因里寫著函數(shù)式編程

# ?從柯里化講起,一網(wǎng)打盡 JavaScript 重要的高階函數(shù)

# ?從純函數(shù)講起,一窺最深刻的函子 Monad

我們從閉包起源開(kāi)始、再到百變柯里化等一票高階函數(shù),再講到純函數(shù)、純函數(shù)的組合以及簡(jiǎn)化演算;

學(xué)到了:

  • 閉包的設(shè)計(jì)就是因?yàn)?lambda 表達(dá)式只能接受一個(gè)參數(shù)的設(shè)計(jì)導(dǎo)致的,誕生 1930 ;
  • 柯里化是閉包的孿生子,柯里化思想是高階函數(shù)的重要指導(dǎo);
  • 原來(lái)編程函數(shù)也可以和數(shù)學(xué)函數(shù)一樣運(yùn)算推導(dǎo),無(wú)副作用的純函數(shù)、函數(shù)組合,代碼更易讀;

本篇將展開(kāi)“延遲處理”這一話題,閑言少敘,沖了~

延遲處理

認(rèn)真讀前面幾篇,雖然沒(méi)有專門講“延遲處理”,但實(shí)際上處處都體現(xiàn)著“延遲處理”。

首先閉包是延遲處理:函數(shù)在聲明的時(shí)候,確定了上下作用域關(guān)系。比如以下代碼:

function addA(A){
    return function(B){
        return B+A
    }
}
let count = addA(7)
console.log(count(8)) // 15

調(diào)用 addA(7) 函數(shù),它說(shuō):我并不會(huì)執(zhí)行運(yùn)算,而會(huì)返回給你一個(gè)新的函數(shù),以及一個(gè)“閉包”,這個(gè)閉包里面是被引用的變量值。等到時(shí)候你要計(jì)算的時(shí)候,再?gòu)倪@里面拿值就行了~

其次,柯里化和閉包同宗同源,由 add(1,2,3) 柯里化為 add(1)(2)(3)(),在判定最后的參數(shù)為空之前,都是一個(gè)待執(zhí)行的函數(shù),不會(huì)進(jìn)行真正的運(yùn)算處理。

function addCurry() {
    let arr = [...arguments]
    let fn = function () {
        if(arguments.length === 0) {
	    return arr.reduce((a, b) => a + b) // 當(dāng)參數(shù)為空時(shí)才執(zhí)行求和計(jì)算;
        } else {
            arr.push(...arguments)
            return fn
        }
    }
    return fn
}

接著,純函數(shù)中,我們不能保證一直寫出不帶副作用的函數(shù),HTTP 操作/ IO 操作/ DOM 操作等這些行為是業(yè)務(wù)場(chǎng)景必做的,于是想了個(gè)法子:用一個(gè)“盒子”把不純的函數(shù)包裹住,然后一個(gè)盒子連著一個(gè)盒子聲明調(diào)用關(guān)系,直到最后執(zhí)行 monad.value() 時(shí)才會(huì)暴露出副作用,盡最大可能的限制住了副作用的影響,延遲了它的影響。

所以,“延遲處理”思想幾乎是根植在函數(shù)式編程的每一個(gè)要點(diǎn)中~

還沒(méi)完,從專欄的整體角度來(lái)看,至此行文已到中段,除了圍繞“閉包”這一核心點(diǎn),另外一個(gè)核心點(diǎn)“異步”也要逐漸拉開(kāi)帷幕、閃亮登場(chǎng)。

延遲處理是在函數(shù)式編程背景下連接 JavaScript 閉包和異步兩大核心的重要橋梁。

惰性求值

“延遲處理”在函數(shù)式編程語(yǔ)言中還有一個(gè)更加官方、學(xué)術(shù)的名稱,即“惰性求值”。

??我們不妨再用一段代碼作簡(jiǎn)要示例:

// 示例代碼 1

const myFunction = function(a, b, c) {
  let result1 = longCalculation1(a,b);
  let result2 = longCalculation2(b,c);
  let result3 = longCalculation3(a,c);
  if (result1 < 10) {
    return result1;
  } else if (result2 < 100) {
    return result2;
  } else {
    return result3;
  }
}

這是一段求值函數(shù),result1、result2、result3 依次經(jīng)過(guò)一段長(zhǎng)運(yùn)算,然后再走一段條件判斷,return 結(jié)果;

這段代碼的不合理之處在于,每次調(diào)用 myFunction() 都要把 3 個(gè) longCalculation 計(jì)算,很耗時(shí),結(jié)果只需要得到其中的某一個(gè)運(yùn)算結(jié)果。

于是,根據(jù)問(wèn)題,我們優(yōu)化代碼策略為:需要用到哪個(gè)計(jì)算,才計(jì)算哪個(gè)。(言外之意:惰性求值)

// 示例代碼 2

const myFunction = function(a, b, c) {
  let result1 = longCalculation1(a,b);
  if (result1 < 10) {
    return result1;
  } else {
    let result2 = longCalculation2(b,c);
    if (result2 < 100) {
     return result2;
    } else {
      let result3 = longCalculation3(a,c);
      return result3;
    }
  }
}

優(yōu)化后的這個(gè)寫法在邏輯上更合理,但是 if...else... 嵌套總讓人看的難受。

因?yàn)?JavaScript 本身不是惰性求值語(yǔ)言,它和比如 C 語(yǔ)言這類主流語(yǔ)言一樣,是【及早求值】,惰性求值語(yǔ)言有比如 Haskell 這類純粹的函數(shù)式編程語(yǔ)言,用 Haskell 實(shí)現(xiàn)上述函數(shù)為:

myFunction :: Int -> Int -> Int -> Int
myFunction a b c =
  let result1 = longCalculation1 a b
      result2 = longCalculation2 b c
      result3 = longCalculation3 a c
  in if result1 < 10
       then result1
       else if result2 < 100
         then result2
         else result3

看上去,這似乎和 JavaScript 示例代碼 1 一樣,但是它實(shí)際上實(shí)現(xiàn)的卻是 JavaScript 示例代碼 2 的效果;

在 GHC 編譯器中,result1, result2, 和 result3 被存儲(chǔ)為 “thunk” ,并且編譯器知道在什么情況下,才需要去計(jì)算結(jié)果,否則將不會(huì)提前去計(jì)算!這太牛皮了~

在《Haskell 函數(shù)式編程入門》,thunk 被解釋為:

thunk 意為形實(shí)替換程序(有時(shí)候也稱為延遲計(jì)算,suspended computation)。它指的是在計(jì)算的過(guò)程中,一些函數(shù)的參數(shù)或者一些結(jié)果通過(guò)一段程序來(lái)代表,這被稱為 thunk??梢院?jiǎn)單地把 thunk 看做是一個(gè)未求得完全結(jié)果的表達(dá)式與求得該表達(dá)式結(jié)果所需要的環(huán)境變量組成的函數(shù),這個(gè)表達(dá)式與環(huán)境變量形成了一個(gè)無(wú)參數(shù)的閉包(parameterless closure) ,所以 thunk 中有求得這個(gè)表達(dá)式所需要的所有信息,只是在不需要的時(shí)候不求而已。

雖然 JavaScript 本身語(yǔ)言的設(shè)計(jì)不是惰性求值,但并不意味著它不能用惰性的思想來(lái)編程~

從惰性編程的角度來(lái)思考問(wèn)題,可以消除代碼中不必要的計(jì)算,也可以幫你重構(gòu)程序,使之能更加直接地面向問(wèn)題。

惰性編程

什么是惰性編程?

惰性編程是一種將對(duì)函數(shù)或請(qǐng)求的處理延遲到真正需要結(jié)果時(shí)進(jìn)行的通用概念。

有很多應(yīng)用程序都采用了這種概念,有的非常明顯,有些則不太明顯。

比如 JavaScript 的“父親” Scheme 中就有簡(jiǎn)單的惰性編程,它有兩個(gè)特殊的結(jié)構(gòu),delayforce,delay 接收一個(gè)代碼塊,不會(huì)立即執(zhí)行它們,而是將代碼和參數(shù)作為一個(gè) promise 存儲(chǔ)起來(lái)。而 force promise 則會(huì)運(yùn)行這段代碼,產(chǎn)生一個(gè)返回值;

這里提到 promise?在 JS 中也有 Promise,它是 JS 實(shí)現(xiàn)惰性的關(guān)鍵嗎?

我們不妨用代碼來(lái)測(cè)試一下:

const st=()=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log("done promise")
            resolve(true)
        },1000)
    })
}
let a = st()
console.log(a)

可以看到,Promise 并不是惰性的,它一旦執(zhí)行,狀態(tài)就轉(zhuǎn)為 Pending,不能暫停。我們無(wú)法知道 Promise 是剛開(kāi)始執(zhí)行,或者是快執(zhí)行完了,還是其它哪個(gè)具體執(zhí)行階段;內(nèi)部的異步任務(wù)就已經(jīng)啟動(dòng)了,執(zhí)行無(wú)法中途取消;這些問(wèn)題也是面試中??嫉?Promise 的缺點(diǎn)有哪些。

好在,后來(lái),Generator 函數(shù)的出現(xiàn),把 JavaScript 異步編程帶入了一個(gè)全新的階段。

ES6 引入的 Generator ,為 JavaScript 賦予了惰性的能力! ??

Generator

Thunk

Generator 就像是 Haskell 中的 thunk,賦值的時(shí)候,我不進(jìn)行計(jì)算,把你包裝成一個(gè) <suspended> 暫停等待,等你調(diào)用 next() 的時(shí)候,我再計(jì)算;

function* gen(x){
 const y = yield x + 6;
 return y;
}
const g = gen(1);
g.next() // { value: 7, done: false }
g.next() // { value: undefined, done: true }

調(diào)用 Generator 函數(shù)后,該函數(shù)并不執(zhí)行,返回的也不是函數(shù)運(yùn)行結(jié)果,而是一個(gè)指向內(nèi)部狀態(tài)的指針對(duì)象,也就是遍歷器對(duì)象。下一步,必須調(diào)用遍歷器對(duì)象的 next 方法,使得指針移向下一個(gè)狀態(tài)。

在異步場(chǎng)景下同樣適用,將上述 promise 的測(cè)試代碼改造為:

function * st1(){
    setTimeout(()=>{
        console.log("done promise")
    },1000)
    yield("done promise")
}
let aThunk = st1()
console.log(aThunk)

只有執(zhí)行 aThunk.next() 時(shí),異步才開(kāi)始執(zhí)行。

迭代生成器

Promise 不能隨用隨停,而 Generator 可以。我們通過(guò) Generator 生成的序列值是可以迭代的,迭代過(guò)程可以操作,比方說(shuō)在循環(huán)中迭代生成器:

//基本的生成器函數(shù)產(chǎn)生序列值。
function* gen(){
    yield 'first';
    yield 'second';
    yield 'third';
}
//創(chuàng)建生成器。
var generator = gen();
//循環(huán)直到序列結(jié)束。
while(true) {
    //獲取序列中的下一項(xiàng)。
    let item = generator.next();
    //下一個(gè)值等于 'third' 嗎
    if(item.value === 'third') {
        break;
    }
    console.log('while', item.value);
}

當(dāng) item.value === 'third',break 跳出循環(huán),迭代結(jié)束。

循環(huán)+請(qǐng)求

綜合循環(huán)和異步的問(wèn)題,拋一個(gè)經(jīng)典的面試題:

如何依次請(qǐng)求一個(gè) api 數(shù)組中的接口,需保證一個(gè)請(qǐng)求結(jié)束后才開(kāi)始另一個(gè)請(qǐng)求?

代碼實(shí)現(xiàn)如下:

async function* generateSequence(items) {
  for (const i of items) {
    await new Promise(resolve => setTimeout(resolve, i));
    yield i;
  }
}
(async () => {
  let generator = generateSequence(['3000','8000','1000','4000']);
  for await (let value of generator) {
    console.log(value);
  }
})();

這里用 setTimeout 模擬了異步請(qǐng)求,代碼可復(fù)制到控制臺(tái)中自行跑一跑、試一試。

無(wú)限序列

在函數(shù)式編程語(yǔ)言中有一個(gè)特殊的數(shù)據(jù)結(jié)構(gòu) —— 無(wú)限列表,Generator 也可以幫助 JS 實(shí)現(xiàn)這一結(jié)構(gòu):

??比如生成一個(gè)無(wú)限增長(zhǎng)的 id 序列:

function* idMaker(){
    let index = 0;
    while(true)
        yield index++;
}
let gen = idMaker(); // "Generator { }"
console.log(gen.next().value);
// 0
console.log(gen.next().value);
// 1
console.log(gen.next().value);
// 2
// ...

??比如實(shí)現(xiàn)一個(gè)循環(huán)交替的無(wú)限序列:

//一個(gè)通用生成器將無(wú)限迭代
//提供的參數(shù),產(chǎn)生每個(gè)項(xiàng)。
function* alternate(...seq) {
    while (true) {
        for (let item of seq) {
            yield item;
        }
    }
}
//使用新值創(chuàng)建新的生成器實(shí)例
//來(lái)迭代每個(gè)項(xiàng)。
let alternator = alternator('one', 'two', 'three');
//從無(wú)限序列中獲取前10個(gè)項(xiàng)。
for (let i = 0; i < 6; i++) {
    console.log(`"${alternator.next().value}"`);
}
// "one"
// "two"
// "three"
// "one"
// "two"
// "three"

由于 while 循環(huán)永遠(yuǎn)不會(huì)退出,for 循環(huán)將自己重復(fù)。也就是說(shuō),參數(shù)值會(huì)交替出現(xiàn)了。

無(wú)限序列是有現(xiàn)實(shí)意義的,很多數(shù)字組合都是無(wú)限的,比如素?cái)?shù),斐波納契數(shù),奇數(shù)等等;

結(jié)語(yǔ)

看到這里,大家有沒(méi)有感覺(jué) Generator 和之前講過(guò)的什么東西有點(diǎn)像?

純函數(shù)的衍生 compose 組合函數(shù),把一個(gè)一個(gè)函數(shù)組裝、拼接形成鏈條;Generator 自定義生成序列,依次執(zhí)行。二者有異曲同工之妙。前者側(cè)重函數(shù)封裝、后者側(cè)重異步處理,但二者都有“延遲處理”的思想。真掘了!

JavaScript 也能借助 閉包、柯里化、組合函數(shù)、Generator 實(shí)現(xiàn)惰性編程,減少不必要的計(jì)算、精確控制序列的執(zhí)行、實(shí)現(xiàn)無(wú)限列表等。。。

不愧是你,真膠水語(yǔ)言,啥都能干!

以上就是從延遲處理解析JavaScript惰性編程的詳細(xì)內(nèi)容,更多關(guān)于JavaScript 延遲處理惰性編程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論