閉包很簡單,就是可以訪問另外一個函數做用域變量的函數,更簡單的說,閉包就是函數,只不過是聲明在其它函數內部而已。css
例如:html
function getOuter(){ var count = 0 function getCount(num){ count += num console.log(count) //訪問外部的date } return getCount //外部函數返回 } var myfunc = getOuter() myfunc(1) // 1 myfunc(2) // 3
myfunc
就是閉包, myfunc
是執行 getOuter
時建立的 getCount
函數實例的引用。 getCount
函數實例維護了一個對它的詞法環境的引用,因此閉包就是函數+詞法環境前端
當 myfunc
函數被調用時,變量 count
依然是可用的,也能夠更新的git
function add(x){ return function(y){ return x + y }; } var addFun1 = add(4) var addFun2 = add(9) console.log(addFun1(2)) //6 console.log(addFun2(2)) //11
add
接受一個參數 x
,返回一個函數,它的參數是 y
,返回 x+y
github
add
是一個函數工廠,傳入一個參數,就能夠建立一個參數和其餘參數求值的函數。面試
addFun1
和 addFun2
都是閉包。他們使用相同的函數定義,但詞法環境不一樣, addFun1
中 x
是 4
,後者是 5
算法
即:數組
因此,閉包能夠:緩存
null
,這樣就解除了對這個變量的引用,其引用計數也會減小,從而確保其內存能夠在適當的時機回收)閉包一般用來建立內部變量,使得這些變量不能被外部隨意修改,同時又能夠經過指定的函數接口來操做。例如 setTimeout
傳參、回調、IIFE、函數防抖、節流、柯里化、模塊化等等閉包
setTimeout
傳參//原生的setTimeout傳遞的第一個函數不能帶參數 setTimeout(function(param){ alert(param) },1000) //經過閉包能夠實現傳參效果 function myfunc(param){ return function(){ alert(param) } } var f1 = myfunc(1); setTimeout(f1,1000);
大部分咱們所寫的 JavaScript 代碼都是基於事件的 — 定義某種行爲,而後將其添加到用戶觸發的事件之上(好比點擊或者按鍵)。咱們的代碼一般做爲回調:爲響應事件而執行的函數。
例如,咱們想在頁面上添加一些能夠調整字號的按鈕。能夠採用css,也可使用:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>test</title> <link rel="stylesheet" href=""> </head> <style> body{ font-size: 12px; } h1{ font-size: 1.5rem; } h2{ font-size: 1.2rem; } </style> <body> <p>測試</p> <a href="#" id="size-12">12</a> <a href="#" id="size-14">14</a> <a href="#" id="size-16">16</a> <script> function changeSize(size){ return function(){ document.body.style.fontSize = size + 'px'; }; } var size12 = changeSize(12); var size14 = changeSize(14); var size16 = changeSize(16); document.getElementById('size-12').onclick = size12; document.getElementById('size-14').onclick = size14; document.getElementById('size-16').onclick = size16; </script> </body> </html>
var arr = []; for (var i=0;i<3;i++){ //使用IIFE (function (i) { arr[i] = function () { return i; }; })(i); } console.log(arr[0]()) // 0 console.log(arr[1]()) // 1 console.log(arr[2]()) // 2
debounce
與 throttle
是開發中經常使用的高階函數,做用都是爲了防止函數被高頻調用,換句話說就是,用來控制某個函數在必定時間內執行多少次。
使用場景
好比綁定響應鼠標移動、窗口大小調整、滾屏等事件時,綁定的函數觸發的頻率會很頻繁。若稍處理函數微複雜,須要較多的運算執行時間和資源,每每會出現延遲,甚至致使假死或者卡頓感。爲了優化性能,這時就頗有必要使用 debounce
或 throttle
了。
debounce 與 throttle 區別
防抖 (debounce) :屢次觸發,只在最後一次觸發時,執行目標函數。
節流(throttle):限制目標函數調用的頻率,好比:1s內不能調用2次。
源碼實現
debounce
// 這個是用來獲取當前時間戳的 function now() { return +new Date() } /** * 防抖函數,返回函數連續調用時,空閒時間必須大於或等於 wait,func 纔會執行 * * @param {function} func 回調函數 * @param {number} wait 表示時間窗口的間隔 * @param {boolean} immediate 設置爲ture時,是否當即調用函數 * @return {function} 返回客戶調用函數 */ function debounce (func, wait = 50, immediate = true) { let timer, context, args // 延遲執行函數 const later = () => setTimeout(() => { // 延遲函數執行完畢,清空緩存的定時器序號 timer = null // 延遲執行的狀況下,函數會在延遲函數中執行 // 使用到以前緩存的參數和上下文 if (!immediate) { func.apply(context, args) context = args = null } }, wait) // 這裏返回的函數是每次實際調用的函數 return function(...params) { // 若是沒有建立延遲執行函數(later),就建立一個 if (!timer) { timer = later() // 若是是當即執行,調用函數 // 不然緩存參數和調用上下文 if (immediate) { func.apply(this, params) } else { context = this args = params } // 若是已有延遲執行函數(later),調用的時候清除原來的並從新設定一個 // 這樣作延遲函數會從新計時 } else { clearTimeout(timer) timer = later() } } }
throttle
/** * underscore 節流函數,返回函數連續調用時,func 執行頻率限定爲 次 / wait * * @param {function} func 回調函數 * @param {number} wait 表示時間窗口的間隔 * @param {object} options 若是想忽略開始函數的的調用,傳入{leading: false}。 * 若是想忽略結尾函數的調用,傳入{trailing: false} * 二者不能共存,不然函數不能執行 * @return {function} 返回客戶調用函數 */ _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; // 以前的時間戳 var previous = 0; // 若是 options 沒傳則設爲空對象 if (!options) options = {}; // 定時器回調函數 var later = function() { // 若是設置了 leading,就將 previous 設爲 0 // 用於下面函數的第一個 if 判斷 previous = options.leading === false ? 0 : _.now(); // 置空一是爲了防止內存泄漏,二是爲了下面的定時器判斷 timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { // 得到當前時間戳 var now = _.now(); // 首次進入前者確定爲 true // 若是須要第一次不執行函數 // 就將上次時間戳設爲當前的 // 這樣在接下來計算 remaining 的值時會大於0 if (!previous && options.leading === false) previous = now; // 計算剩餘時間 var remaining = wait - (now - previous); context = this; args = arguments; // 若是當前調用已經大於上次調用時間 + wait // 或者用戶手動調了時間 // 若是設置了 trailing,只會進入這個條件 // 若是沒有設置 leading,那麼第一次會進入這個條件 // 還有一點,你可能會以爲開啓了定時器那麼應該不會進入這個 if 條件了 // 其實仍是會進入的,由於定時器的延時 // 並非準確的時間,極可能你設置了2秒 // 可是他須要2.2秒才觸發,這時候就會進入這個條件 if (remaining <= 0 || remaining > wait) { // 若是存在定時器就清理掉不然會調用二次回調 if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 判斷是否設置了定時器和 trailing // 沒有的話就開啓一個定時器 // 而且不能不能同時設置 leading 和 trailing timeout = setTimeout(later, remaining); } return result; }; };
在計算機科學中,柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數且返回結果的新函數的技術。這個技術由 Christopher Strachey 以邏輯學家 Haskell Curry 命名的,儘管它是 Moses Schnfinkel 和 Gottlob Frege 發明的。
var add = function(x) { return function(y) { return x + y; }; }; var increment = add(1); var addTen = add(10); increment(2); // 3 addTen(2); // 12 add(1)(2); // 3
這裏定義了一個 add
函數,它接受一個參數並返回一個新的函數。調用 add
以後,返回的函數就經過閉包的方式記住了 add
的第一個參數。因此說 bind
自己也是閉包的一種使用場景。
柯里化是將 f(a,b,c)
能夠被以 f(a)(b)(c)
的形式被調用的轉化。JavaScript 實現版本一般保留函數被正常調用和在參數數量不夠的狀況下返回偏函數這兩個特性。
模塊化的目的在於將一個程序按照其功能作拆分,分紅相互獨立的模塊,以便於每一個模塊只包含與其功能相關的內容,模塊之間經過接口調用。
模塊化開發和閉包息息相關,經過模塊模式須要具有兩個必要條件能夠看出:
function myModule (){ const moduleName = '個人自定義模塊' var name = 'sisterAn' // 在模塊內定義方法(API) function getName(){ console.log(name) } function modifyName(newName){ name = newName } // 模塊暴露: 向外暴露API return { getName, modifyName } } // 測試 const md = myModule() md.getName() // 'sisterAn' md.modifyName('PZ') md.getName() // 'PZ' // 模塊實例之間互不影響 const md2 = myModule() md2.sayHello = function () { console.log('hello') } console.log(md) // {getName: ƒ, modifyName: ƒ}
var data = [] for (var i = 0; i < 3; i++) { data[i] = function () { console.log(i) } } data[0]() // 3 data[1]() // 3 data[2]() // 3
這裏的 i
是全局下的 i
,共用一個做用域,當函數被執行的時候這時的 i=3
,致使輸出的結構都是3
方案一:閉包
var data = [] function myfunc(num) { return function(){ console.log(num) } } for (var i = 0; i < 3; i++) { data[i] = myfunc(i) } data[0]() // 0 data[1]() // 1 data[2]() // 2
方案二:let
若是不想使用過多的閉包,你能夠用 ES6 引入的 let 關鍵詞:
var data = [] for (let i = 0; i < 3; i++) { data[i] = function () { console.log(i) } } data[0]() // 0 data[1]() // 1 data[2]() // 2
方案三:forEach
若是是數組的遍歷操做(以下例中的 arr
),還有一個可選方案是使用 forEach()來遍歷:
var data = [] var arr = [0, 1, 2] arr.forEach(function (i) { data[i] = function () { console.log(i) } }) data[0]() // 0 data[1]() // 1 data[2]() // 2
本文首發自「三分鐘學前端」,天天三分鐘,進階一個前端小 tip
面試題庫
算法題庫