閉包——我知道你的名字,但我卻不瞭解你

前一段時間學習了一下阮一峯大佬寫的python教程,感受本身就是井底之蛙,函數式編程☹、返回函數☹、裝飾器☹、偏函數☹,這些都是些什麼東西?不過,在看過這些以後,有一種感受就是,部分知識和javascript中的閉包很是類似(也能夠說思想如出一轍),經過python與JS,認認真真的去理解一下Javascript中的閉包。javascript

什麼是閉包?

我的理解:函數 A 返回函數 B、函數 B 引用了函數 A 的變量。java

百度百科:閉包就是可以讀取其餘函數內部變量的函數。例如在javascript中,只有函數內部的子函數才能讀取局部變量,因此閉包能夠理解成「定義在一個函數內部的函數「。在本質上,閉包是將函數內部和函數外部鏈接起來的橋樑。python

示例代碼:面試

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        console.log(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();                   // Mozilla
複製代碼

閉包使用場景

1.對象私有數據

閉包最經常使用於實現對象私有數據,將模塊的公有屬性、方法暴露出來。下面一段代碼'return'關鍵字將對象導出賦值給myModule,從而應用到閉包。編程

var myModule = (function (window, undefined) {
    let name = "echo";
    
    function getName() {
        return name;
    }
    
    return {
        name,
        getName
    }
})(window);

console.log( myModule.name ); // echo
console.log( myModule.getName() ); // echo
複製代碼

2.延時器(setTimeout)與定時器(setInterval)

上次面試現時,遇到了定時器這道面試題。題目以下:瀏覽器

// 問這段代碼輸出什麼?
for( var i = 0; i < 10; i++ ) {
	setTimeout(() => {
		console.log(i);
	}, 0);
}

// 答案是輸出 10個 10
複製代碼

考察點:var變量提高,以及setTimeout。執行到setTimeout會往瀏覽器的定時器線程推個任務,而後當達到時間了會經過輪詢線程將回調推到宏任務隊列中,若是那個時候主線程不忙就會去宏任務隊列執行這個回調。因此定時器同步執行,回調是異步!閉包

詳細請參考 javascript的宏任務和微任務app

迴歸正軌,咱們來說閉包: 上面代碼如何作到輸出0,1,2,3,4,5, 6,7,8,9,10呢?(固然可使用let,但咱們經過閉包來實現一下)異步

for( var i = 0; i < 10; i++ ) {
	((j) => {
        setTimeout(() => {
            console.log(j);
        }, 0);
    })(i)
}
複製代碼

"setTimeout"方法裏應用了閉包,使其內部可以記住每次循環所在的詞法做用域和做用域鏈。函數式編程

3. 監聽器

var oDiv = document.querySeletor("#div");

oDiv.onclick = function() {
	console.log( oDiv.id );
}
複製代碼

4.函數式編程中偏函數

在函數式編程中,閉包常常被用於偏函數應用和柯里化。

偏函數應用: 是傳給某個函數其中一部分參數,而後返回一個新的函數,該函數等待接收後續參數的過程。英文是partial application,也能夠譯做「局部應用」、「部分應用」、「偏應用」

話句話講偏函數應用是一個函數,它接受另外一個函數爲參數,這個做爲參數的函數自己接收多個參數,它返回一個函數,這個函數與它的參數相比接收更少的參數。偏函數應用提早給出一部分參數,而返回的函數則會等待調用時傳入剩餘的參數。

// 給一段代碼理解理解
// Relatively flexible, more specific function generator.
function bindFirstArg(fn, a) {
  return function(b) {
    return fn(a, b);
  };
}

// More general functions.
function add(a, b) {
  return a + b;
}

add(1, 2);           // 3

function multiply(a, b) {
  return a * b;
}

multiply(10, 2);     // 20

// More specific functions.
var addOne = bindFirstArg(add, 1);
addOne(2);           // 3
addOne(3);           // 4
addOne(10);          // 11
addOne(9000);        // 9001

var multiplyByTen = bindFirstArg(multiply, 10);
multiplyByTen(2);    // 20
multiplyByTen(3);    // 30
multiplyByTen(10);   // 100
multiplyByTen(9000); // 90000
複製代碼

上面這段代碼,亮點不在於它能夠將某個參數綁定到任意的function,而且這個參數做爲綁定的function的第一個參數,它還能將方法綁定它本身身上做爲第一個參數,所以建立了一個可綁定的function。

python中偏函數案例:

# functools.partial就是幫助咱們建立一個偏函數的,不須要咱們本身定義int2(),
# 能夠直接使用下面的代碼建立一個新的函數int2

import functools
int2 = functools.partial(int, base=2)
int2('1000000')  # 64
int2('1010101') # 85
複製代碼

簡單總結functools.partial的做用就是,把一個函數的某些參數給固定住(也就是設置默認值),返回一個新的函數,調用這個新函數會更簡單。

是否是和上面JS代碼思想很相似?

總結

閉包是JS中又一必須掌握的知識點,寫到最後,想到了之前回答閉包知識點的問題時,本身回答的也不是很好,記住一句函數 A 返回函數 B、函數 B 引用了函數 A 的變量,在想一想應用場景,就能大體掌握閉包知識點了,順帶提一句,若是學過python,裝飾器這一章節就是很典型的閉包,這也是學Python要跨過去的一道門。

來看看python裝飾器代碼:

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print '%s %s():' % (text, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator
    
@log('execute')
def now():
    print '2013-12-25'
    
now()

# 輸出
# execute now():
# 2013-12-25

# 首先執行log('execute'),返回的是decorator函數,再調用返回的函數,參數是now函數,返回值最終是wrapper函數。
複製代碼
相關文章
相關標籤/搜索