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

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

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

前文回顧

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

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

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

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

學到了:

  • 閉包的設計就是因為 lambda 表達式只能接受一個參數(shù)的設計導致的,誕生 1930 ;
  • 柯里化是閉包的孿生子,柯里化思想是高階函數(shù)的重要指導;
  • 原來編程函數(shù)也可以和數(shù)學函數(shù)一樣運算推導,無副作用的純函數(shù)、函數(shù)組合,代碼更易讀;

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

延遲處理

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

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

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

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

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

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

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

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

還沒完,從專欄的整體角度來看,至此行文已到中段,除了圍繞“閉包”這一核心點,另外一個核心點“異步”也要逐漸拉開帷幕、閃亮登場。

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

惰性求值

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

??我們不妨再用一段代碼作簡要示例:

// 示例代碼 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)過一段長運算,然后再走一段條件判斷,return 結(jié)果;

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

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

// 示例代碼 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)化后的這個寫法在邏輯上更合理,但是 if...else... 嵌套總讓人看的難受。

因為 JavaScript 本身不是惰性求值語言,它和比如 C 語言這類主流語言一樣,是【及早求值】,惰性求值語言有比如 Haskell 這類純粹的函數(shù)式編程語言,用 Haskell 實現(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 一樣,但是它實際上實現(xiàn)的卻是 JavaScript 示例代碼 2 的效果;

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

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

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

雖然 JavaScript 本身語言的設計不是惰性求值,但并不意味著它不能用惰性的思想來編程~

從惰性編程的角度來思考問題,可以消除代碼中不必要的計算,也可以幫你重構程序,使之能更加直接地面向問題。

惰性編程

什么是惰性編程?

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

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

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

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

我們不妨用代碼來測試一下:

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,不能暫停。我們無法知道 Promise 是剛開始執(zhí)行,或者是快執(zhí)行完了,還是其它哪個具體執(zhí)行階段;內(nèi)部的異步任務就已經(jīng)啟動了,執(zhí)行無法中途取消;這些問題也是面試中??嫉?Promise 的缺點有哪些。

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

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

Generator

Thunk

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

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ù)運行結(jié)果,而是一個指向內(nèi)部狀態(tài)的指針對象,也就是遍歷器對象。下一步,必須調(diào)用遍歷器對象的 next 方法,使得指針移向下一個狀態(tài)。

在異步場景下同樣適用,將上述 promise 的測試代碼改造為:

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

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

迭代生成器

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

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

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

循環(huán)+請求

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

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

代碼實現(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 模擬了異步請求,代碼可復制到控制臺中自行跑一跑、試一試。

無限序列

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

??比如生成一個無限增長的 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
// ...

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

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

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

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

結(jié)語

看到這里,大家有沒有感覺 Generator 和之前講過的什么東西有點像?

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

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

不愧是你,真膠水語言,啥都能干!

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

相關文章

  • uniapp?u-picker多列用法以及設置默認選中值

    uniapp?u-picker多列用法以及設置默認選中值

    uview組件庫u-picker組件可能很多人不熟悉,下面這篇文章主要給大家介紹了關于uniapp?u-picker多列用法以及設置默認選中值的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-06-06
  • 動態(tài)的9*9乘法表效果的實現(xiàn)代碼

    動態(tài)的9*9乘法表效果的實現(xiàn)代碼

    下面小編就為大家?guī)硪黄獎討B(tài)的9*9乘法表效果的實現(xiàn)代碼。小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考,一起跟隨小編過來看看吧
    2016-05-05
  • JavaScript利用crypto模塊實現(xiàn)加解密

    JavaScript利用crypto模塊實現(xiàn)加解密

    crypto模塊提供了加密功能,包含對 OpenSSL 的哈希、HMAC、加密、解密、簽名、以及驗證功能的一整套封裝。本文將利用它實現(xiàn)加解密算法,需要的可以參考一下
    2023-02-02
  • javascript對HTML字符轉(zhuǎn)義與反轉(zhuǎn)義

    javascript對HTML字符轉(zhuǎn)義與反轉(zhuǎn)義

    這篇文章主要介紹了javascript對HTML字符轉(zhuǎn)義與反轉(zhuǎn)義,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-12-12
  • window.onerror()的用法與實例分析

    window.onerror()的用法與實例分析

    目前在做window.onerror時上報js錯誤信息的事,整理下相關資料,需要的朋友可以參考下
    2016-01-01
  • 微信小程序?qū)崿F(xiàn)tab頁面切換功能

    微信小程序?qū)崿F(xiàn)tab頁面切換功能

    這篇文章主要介紹了微信小程序?qū)崿F(xiàn)tab頁面切換功能,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-07-07
  • JS對select控件option選項的增刪改查示例代碼

    JS對select控件option選項的增刪改查示例代碼

    Javascript操作select是表單中比較常見的,大家可以在網(wǎng)上搜索到很多的相關資料,接下來為大家詳細介紹下,JS動態(tài)操作select中的各種方法,感興趣的朋友可以參考下
    2013-10-10
  • JS+CSS實現(xiàn)分類動態(tài)選擇及移動功能效果代碼

    JS+CSS實現(xiàn)分類動態(tài)選擇及移動功能效果代碼

    這篇文章主要介紹了JS+CSS實現(xiàn)分類動態(tài)選擇及移動功能效果代碼,涉及JavaScript實現(xiàn)頁面元素動態(tài)變換效果實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-10-10
  • layer彈出層取消遮罩的方法

    layer彈出層取消遮罩的方法

    今天小編就為大家分享一篇layer彈出層取消遮罩的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-09-09
  • JavaScript學習筆記--常用的互動方法

    JavaScript學習筆記--常用的互動方法

    本文對JavaScript中常用的互動方法進行實例分析介紹,圖文并茂,有需要的朋友可以看下
    2016-12-12

最新評論