1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        JavaScript閉包應(yīng)用介紹

        共 5398字,需瀏覽 11分鐘

         ·

        2020-07-28 16:10

        來(lái)源 |?https://www.zoo.team/article/vue3-jsx

        本文介紹一下js中的一個(gè)重要概念——閉包。其實(shí)即便是最初級(jí)的前端開(kāi)發(fā)人員,應(yīng)該都已經(jīng)接觸過(guò)它。

        一、閉包的概念和特性

        首先看個(gè)閉包的例子:
        function makeFab () { let last = 1, current = 1 return function inner() { [current, last] = [current + last, current] return last }}
        let fab = makeFab()console.log(fab()) // 1console.log(fab()) // 2console.log(fab()) // 3console.log(fab()) // 5
        這是一個(gè)生成斐波那契數(shù)列的例子。makeFab的返回值就是一個(gè)閉包,makeFab像一個(gè)工廠函數(shù),每次調(diào)用都會(huì)創(chuàng)建一個(gè)閉包函數(shù),如例子中的fab。
        fab每次調(diào)用不需要傳參數(shù),都會(huì)返回不同的值,因?yàn)樵陂]包生成的時(shí)候,它記住了變量last和current,以至于在后續(xù)的調(diào)用中能夠返回不同的值。
        能記住函數(shù)本身所在作用域的變量,這就是閉包和普通函數(shù)的區(qū)別所在。
        MDN中給出的閉包的定義是:函數(shù)與對(duì)其狀態(tài)即詞法環(huán)境的引用共同構(gòu)成閉包。
        這里的“詞法環(huán)境的引用”,可以簡(jiǎn)單理解為“引用了函數(shù)外部的一些變量”,例如上述例子中每次調(diào)用makeFab都會(huì)創(chuàng)建并返回inner函數(shù),引用了last和current兩個(gè)變量。

        二、閉包——函數(shù)式編程之魂

        JavaScript和python這兩門(mén)動(dòng)態(tài)語(yǔ)言都強(qiáng)調(diào)一個(gè)概念:萬(wàn)物皆對(duì)象。自然,函數(shù)也是對(duì)象。
        JavaScript里,我們可以像操作普通變量一樣,把函數(shù)在我們的代碼里拋來(lái)拋去,然后在某個(gè)時(shí)刻調(diào)用一下,這就是所謂的函數(shù)式編程。
        函數(shù)式編程靈活簡(jiǎn)潔,而語(yǔ)言對(duì)閉包的支持,讓函數(shù)式編程擁有了靈魂。
        以實(shí)現(xiàn)一個(gè)可復(fù)用的確認(rèn)框?yàn)槔热缭谟脩暨M(jìn)行一些刪除或者重要操作的時(shí)候,為了防止誤操作,我們可能會(huì)通過(guò)彈窗讓用戶再次確認(rèn)操作。
        因?yàn)榇_認(rèn)框是通用的,所以確認(rèn)框組件的邏輯應(yīng)該足夠抽象,僅僅是負(fù)責(zé)彈窗、觸發(fā)確認(rèn)、觸發(fā)取消事件,而觸發(fā)確認(rèn)/取消事件是異步操作,這時(shí)候我們就需要使用兩個(gè)回調(diào)函數(shù)完成操作,彈窗函數(shù)confirm接收三個(gè)參數(shù):一個(gè)提示語(yǔ)句,一個(gè)確認(rèn)回調(diào)函數(shù),一個(gè)取消回調(diào)函數(shù):
        function confirm (confirmText, confirmCallback, cancelCallback) { // 插入提示框DOM,包含提示語(yǔ)句、確認(rèn)按鈕、取消按鈕 // 添加確認(rèn)按鈕點(diǎn)擊事件,事件函數(shù)中做dom清理工作并調(diào)用confirmCallback // 添加取消按鈕點(diǎn)擊事件,事件函數(shù)中做dom清理工作并調(diào)用cancelCallback}
        這樣我們可以通過(guò)向confirm傳遞回調(diào)函數(shù),并且根據(jù)不同結(jié)果完成不同的動(dòng)作,比如我們根據(jù)id刪除一條數(shù)據(jù)可以這樣寫(xiě):
        function removeItem (id) { confirm('確認(rèn)刪除嗎?', () => { // 用戶點(diǎn)擊確認(rèn), 發(fā)送遠(yuǎn)程ajax請(qǐng)求 api.removeItem(id).then(xxx) }, () => { // 用戶點(diǎn)擊取消, console.log('取消刪除') })}
        這個(gè)例子中,confirmCallback正是利用了閉包,創(chuàng)建了一個(gè)引用了上下文中id變量的函數(shù),這樣的例子在回調(diào)函數(shù)中比比皆是,并且大多數(shù)時(shí)候引用的變量是很多個(gè)。?
        試想,如果語(yǔ)言不支持閉包,那這些變量要怎么辦?作為參數(shù)全部傳遞給confirm函數(shù),然后在調(diào)用confirmCallback/cancelCallback時(shí)再作為參數(shù)傳遞給它們?顯然,這里閉包提供了極大便利。

        三、閉包的一些例子

        1. 防抖、節(jié)流函數(shù)

        前端很常見(jiàn)的一個(gè)需求是遠(yuǎn)程搜索,根據(jù)用戶輸入框的內(nèi)容自動(dòng)發(fā)送ajax請(qǐng)求,然后從后端把搜索結(jié)果請(qǐng)求回來(lái)。
        為了簡(jiǎn)化用戶的操作,有時(shí)候我們并不會(huì)專門(mén)放置一個(gè)按鈕來(lái)點(diǎn)擊觸發(fā)搜索事件,而是直接監(jiān)聽(tīng)內(nèi)容的變化來(lái)搜索(比如像vue的官網(wǎng)搜索欄)。
        這時(shí)候?yàn)榱吮苊庹?qǐng)求過(guò)于頻繁,我們可能就會(huì)用到“防抖”的技巧,即當(dāng)用戶停止輸入一段時(shí)間(比如500ms)后才執(zhí)行發(fā)送請(qǐng)求。
        可以寫(xiě)一個(gè)簡(jiǎn)單的防抖函數(shù)實(shí)現(xiàn)這個(gè)功能:
        function debounce (func, time) { let timer = 0 return function (...args) { timer && clearTimeout(timer) timer = setTimeout(() => { timer = 0 func.apply(this, args) }, time) }}
        input.onkeypress = debounce(function () { console.log(input.value) // 事件處理邏輯}, 500)
        debounce函數(shù)每次調(diào)用時(shí),都會(huì)創(chuàng)建一個(gè)新的閉包函數(shù),該函數(shù)保留了對(duì)事件邏輯處理函數(shù)func以及防抖時(shí)間間隔time以及定時(shí)器標(biāo)志timer的引用。
        類似的還有節(jié)流函數(shù):
        function throttle(func, time) { let timer = 0 // 定時(shí)器標(biāo)記相當(dāng)于一個(gè)鎖標(biāo)志 return function (...args) { if (timer) return func.apply(this, args) timer = setTimeout(() => timer = 0, time) }}

        2. 優(yōu)雅解決按鈕多次連續(xù)點(diǎn)擊問(wèn)題

        用戶點(diǎn)擊一個(gè)表單提交按鈕,前端會(huì)向后臺(tái)發(fā)送一個(gè)異步請(qǐng)求,請(qǐng)求還沒(méi)返回,焦急的用戶又多點(diǎn)了幾下按鈕,造成了額外的請(qǐng)求。
        有時(shí)候多發(fā)幾次請(qǐng)求最多只是多消耗了一些服務(wù)器資源,而另外一些情況是,表單提交本身會(huì)修改后臺(tái)的數(shù)據(jù),那多次提交就會(huì)導(dǎo)致意料之外的后果了。
        無(wú)論是為了減少服務(wù)器資源消耗還是避免多次修改后臺(tái)數(shù)據(jù),給表單提交按鈕添加點(diǎn)擊限制是很有必要的。
        怎么解決呢?一個(gè)常用的辦法是打個(gè)標(biāo)記,即在響應(yīng)函數(shù)所在作用域聲明一個(gè)布爾變量lock,響應(yīng)函數(shù)被調(diào)用時(shí),先判斷l(xiāng)ock的值,為true則表示上一次請(qǐng)求還未返回,此次點(diǎn)擊無(wú)效;為false則將lock設(shè)置為true,然后發(fā)送請(qǐng)求,請(qǐng)求結(jié)束再將lock改為false。
        ?很顯然,這個(gè)lock會(huì)污染函數(shù)所在的作用域,比如在vue組件中,我們可能就要將這個(gè)標(biāo)記記錄在組件屬性上;而當(dāng)有多個(gè)這樣的按鈕,則還需要不同的屬性來(lái)標(biāo)記(想想給這些屬性取名都是一件頭疼的事情吧?。?/span>
        而生成閉包伴隨著新的函數(shù)作用域的創(chuàng)建,利用這一點(diǎn),剛好可以解決這個(gè)問(wèn)題。下面是一個(gè)簡(jiǎn)單的例子:? ? ? ?
        let clickButton = (function () { let lock = false return function (postParams) { if (lock) return lock = true // 使用axios發(fā)送請(qǐng)求 axios.post('urlxxx', postParams).then( // 表單提交成功 ).catch(error => { // 表單提交出錯(cuò) console.log(error) }).finally(() => { // 不管成功失敗 都解鎖 lock = false }) }})()
        button.addEventListener('click', clickButton)
        這樣lock變量就會(huì)在一個(gè)單獨(dú)的作用域里,一次點(diǎn)擊的請(qǐng)求發(fā)出以后,必須等請(qǐng)求回來(lái),才會(huì)開(kāi)始下一次請(qǐng)求。
        當(dāng)然,為了避免各個(gè)地方都聲明lock,修改lock,我們可以把上述邏輯抽象一下,實(shí)現(xiàn)一個(gè)裝飾器,就像節(jié)流/防抖函數(shù)一樣。以下是一個(gè)通用的裝飾器函數(shù):
        function singleClick(func, manuDone = false) { let lock = false return function (...args) { if (lock) return lock = true let done = () => lock = false if (manuDone) return func.call(this, ...args, done) let promise = func.call(this, ...args) promise ? promise.finally(done) : done() return promise }}
        默認(rèn)情況下,需要原函數(shù)返回一個(gè)promise以達(dá)到promise決議后將lock重置為false,而如果沒(méi)有返回值,lock將會(huì)被立即重置(比如表單驗(yàn)證不通過(guò),響應(yīng)函數(shù)直接返回),調(diào)用示例:
        let clickButton = singleClick(function (postParams) { if (!checkForm()) return return axios.post('urlxxx', postParams).then( // 表單提交成功 ).catch(error => { // 表單提交出錯(cuò) console.log(error) })})button.addEventListener('click', clickButton)
        在一些不方便返回promise或者請(qǐng)求結(jié)束還要進(jìn)行其它動(dòng)作之后才能重置lock的地方,singleClick提供了第二個(gè)參數(shù)manuDone,允許你可以手動(dòng)調(diào)用一個(gè)done函數(shù)來(lái)重置lock,這個(gè)done函數(shù)會(huì)放在原函數(shù)參數(shù)列表的末尾。使用例子:
        let print = singleClick(function (i, done) { console.log('print is called', i) setTimeout(done, 2000)}, true)
        function test () { for (let i = 0; i < 10; i++) { setTimeout(() => { print(i) }, i * 1000) }}
        print函數(shù)使用singleClick裝飾,每次調(diào)用2秒后重置lock變量,測(cè)試每秒調(diào)用一次print函數(shù),執(zhí)行代碼輸出如下圖:

        可以看到,其中一些調(diào)用沒(méi)有打印結(jié)果,這正是我們想要的結(jié)果!singleClick裝飾器比每次設(shè)置lock變量要方便許多,這里singleClick函數(shù)的返回值,以及其中的done函數(shù),都是一個(gè)閉包。

        3. 閉包模擬私有方法或者變量

        “封裝”是面向?qū)ο蟮奶匦灾?,所謂“封裝”,即一個(gè)對(duì)象對(duì)外隱藏了其內(nèi)部的一些屬性或者方法的實(shí)現(xiàn)細(xì)節(jié),外界僅能通過(guò)暴露的接口操作該對(duì)象。
        js是比較“自由”的語(yǔ)言,所以并沒(méi)有類似C++語(yǔ)言那樣提供私有變量或成員函數(shù)的定義方式,不過(guò)利用閉包,卻可以很好地模擬這個(gè)特性。
        比如游戲開(kāi)發(fā)中,玩家對(duì)象身上通常會(huì)有一個(gè)經(jīng)驗(yàn)屬性,假設(shè)為exp,"打怪"、“做任務(wù)”、“使用經(jīng)驗(yàn)書(shū)”等都會(huì)增加exp這個(gè)值,而在升級(jí)的時(shí)候又會(huì)減掉exp的值,把exp直接暴露給各處業(yè)務(wù)來(lái)操作顯然是很糟糕的。
        js里面我們可以用閉包把它隱藏起來(lái),簡(jiǎn)單模擬如下:
        function makePlayer () { let exp = 0 // 經(jīng)驗(yàn)值 return { getExp () { return exp }, changeExp (delta, sReason = '') { // log(xxx),記錄變動(dòng)日志 exp += delta } }}
        let p = makePlayer()console.log(p.getExp()) // 0p.changeExp(2000)console.log(p.getExp()) // 2000
        這樣我們調(diào)用makePlayer()就會(huì)生成一個(gè)玩家對(duì)象p,p內(nèi)通過(guò)方法操作exp這個(gè)變量,但是卻不可以通過(guò)p.exp訪問(wèn),顯然更符合“封裝”的特性。

        四、總結(jié)

        閉包是js中的強(qiáng)大特性之一,然而至于閉包怎么使用,我覺(jué)得不算是一個(gè)問(wèn)題,甚至我們完全沒(méi)必要研究閉包怎么使用。
        我的觀點(diǎn)是,閉包應(yīng)該是自然而言地出現(xiàn)在你的代碼里,因?yàn)樗墙鉀Q當(dāng)前問(wèn)題最直截了當(dāng)?shù)霓k法;而當(dāng)你刻意想去使用它的時(shí)候,往往可能已經(jīng)走了彎路。
        ?本文完~


        瀏覽 33
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            激情久久中文字幕 | 羞羞偷拍视频 | 嫩草一区二区 | 久草一级| 粗大挺进粉嫩紧窄的翘臀在线观看 | 国产一级毛片A级免责 | 色偷偷888欧美精品久久久 | 日韩人妻无码一级毛片欧美 | 久久露脸国语精品国产91 | 色色播播|