摘要:而閉包卻能阻止這件事情發生。由于的聲明位置使它擁有涵蓋內部作用域的閉包,使得該作用域能夠一直存在,以供在之后進行引用。到這里,小菊花課堂之閉包的內容就告一段落啦,感謝各位能耐心看到這里。
由于前段時間項目沒有那么忙,然后我這人一天不看點啥就非常焦慮,于是二刷《你不知道的JavaScript》,現在讀到閉包,想著看完這一章節,寫點東西也是挺好的,所以有了下面的內容,如有不對的地方,敬請斧正,歡迎探討。
作用域我們一般講到閉包,就會談到作用域,那么作用域又分為了函數作用域和塊級作用域 ,在這里,我們簡單的介紹一下這兩種作用域。
函數作用域函數作用域是指,屬于這個函數的全部變量都可以在整個函數的范圍內使用及服用(事實上在嵌套的作用域中也可以使用)。
我們先來看一個例子
var a = 2; function foo() { var a = 3; console.log(a); // 3 } foo(); console.log(a); // 2
可以看到,這種技術雖然能解決一些問題,但是也會導致其他的問題。首先,必須聲明一個具名函數foo(),意味著foo這個名稱本身“污染”了所在的作用域。其次,必須顯式地(有隱式和顯式的區別,這里暫且不表)通過函數名(foo())調用這個函數才能運行其中的代碼。
那么我們有沒有其他的辦法呢,繼續往下看。
var a = 2 (function foo() { var a = 3; console.log(a); // 3 })(); console.log(a); // 2
比較一下這兩段代碼。第一段中foo被綁定在所在所用域中,可以直接通過foo()來調用調用它。第二段中foo被綁定在函數表達式自身的函數中而不是所在作用域中。
塊級作用域在JavaScript中,并不支持塊作用域,但是我們為什么還要說它,因為它的風格在JS開發中很常見。
在日常的開發或者學習工作中,我們其實經常能見到類似塊作用域,思考以下代碼:
for(var i=0; i<6; i++){ console.log(i); }
在for循環的頭部直接定義了變量 i,通常是因為只想在for循環內部的上下文中使用 i,而忽略了 i 會被綁定在外部作用域(函數或全局)中的事實。當使用var時,它寫在哪里都是一樣的,因為它們最終都會屬于外部作用域。
塊作用域是一個用來對之前的最小授權原則進行擴展的工具,將代碼從在函數中隱藏信息擴展為在塊中隱藏信息。
繼續思考下面代碼:
function foo() { var a = 2; function bar() { console.log(a); } return bar; } var baz = foo(); baz(); // 2
看到了嗎,這就是閉包的效果。
函數bar()的詞法作用域能夠訪問foo()的內部作用域。然后將bar()函數本身當作一個值類型進行傳遞。
在foo()執行后,其返回值賦值給變量baz并調用baz(),實際上只是通過不同的標識符引用調用了內部的函數bar()。
我們知道,JavaScript引擎有垃圾回收器用來釋放不再使用的內存空間。而閉包卻能阻止這件事情發生。事實上內部作用域依然存在,而沒有被回收。
由于bar()的聲明位置使它擁有涵蓋foo()內部作用域的閉包,使得該作用域能夠一直存在,以供bar()在之后進行引用。
再看下面例子
function foo() { var a = 2; function baz() { console.log(a); // 2 } bar(baz); } function bar(fn) { fn(); // 這是閉包 } foo();
無論通過何種手段將內部函數傳遞到所在的詞法作用域外,它都會持有對原始定義作用域的引用,無論在何處執行這個函數都會使用閉包。
OK,本質上無論何時何地,如果將(訪問它們各自詞法作用域的)函數當作第一級的值類型并到處傳遞,你就能看到閉包了。在定時器、事件監聽器、Ajax請求或其他異步或同步任務中,只要使用了回調函數,實際上就是在使用閉包。
循環與閉包先看看最常見的for循環。
for(var i=1; i<=5; i++) { setTimeout(function timer(){ console.log(i); }, i*1000) }
你覺得最后會輸出什么,每秒輸出一次,分別輸出1~5?
那就錯啦,實際上,它是會每秒輸出一次,但輸出~對,就是66666。
為什么?
延遲函數的回調會在循環結束時才執行,而循環結束的條件就是i不再<=5。當定時器運行時,即使每個迭代中執行的是setTimeout(...,0),所有的回調函數依然是在循環結束后才被執行,所以每次都輸出6。
根據作用域的原理,盡管循環中的五個函數是在各個迭代中分別定義的,但是它們都被封閉在一個共享的全局作用域中,因此只有一個i。
知道原因之后,我們可以對代碼進行一些改造,看看有沒有好事發生。
foo(var i=1; i<=5; i++) { (function (j) { setTimeout(function timer() { console.log(j); }, j*1000); })(i); }
Fine,我們終于改造好了,擁有了更多的詞法作用域。在迭代中使用立即執行函數(IIFE)會為每個迭代都生成一個新的作用域,使得延遲函數的回調可以將新的作用域封閉在每個迭代內部,每個迭代中都會有一個具有正確值的變量。
到這里,小菊花課堂之JavaScript閉包的內容就告一段落啦,感謝各位能耐心看到這里。
此時是0點52分,時候也不早了,該洗洗睡啦。
see u ~ again
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98801.html
摘要:另外,的綁定和函數聲明的位置沒有任何關系,之取決于函數的調用方式。請看下面代碼這樣,我們就可以在調用的時候強制把它的綁定到上綁定在傳統的面向類語言中,使用初始化類時會調用類中的構造函數。 關于this 上一章我們講了關于作用域和閉包的相關知識,現在開始新一輪的學習,那就是JavaScript中最復雜的機制之一---this關鍵字。它是一個很特別的關鍵字,被自動定義在所有函數的作用域中。...
摘要:文章來源詳談防抖和節流輕松理解函數節流和函數防抖函數防抖和節流好啦,今天的小菊花課堂之的防抖與節流的內容就告一段落啦,感各位能耐心看到這里。 前言 陸游有一首《冬夜讀書示子聿》——古人學問無遺力,少壯工夫老始成。紙上得來終覺淺,絕知此事要躬行。,其中的意思想必大家都能明白,在學習或工作中,不斷的印證著這首詩的內涵。所以,又有了此篇小菊花文章。 詳解 在前端開發中,我們經常會碰到一些會持...
摘要:文章來源詳談防抖和節流輕松理解函數節流和函數防抖函數防抖和節流好啦,今天的小菊花課堂之的防抖與節流的內容就告一段落啦,感各位能耐心看到這里。 前言 陸游有一首《冬夜讀書示子聿》——古人學問無遺力,少壯工夫老始成。紙上得來終覺淺,絕知此事要躬行。,其中的意思想必大家都能明白,在學習或工作中,不斷的印證著這首詩的內涵。所以,又有了此篇小菊花文章。 詳解 在前端開發中,我們經常會碰到一些會持...
摘要:的變量作用域是基于其特有的作用域鏈的。需要注意的是,用創建的函數,其作用域指向全局作用域。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。 作用域 定義 在編程語言中,作用域控制著變量與參數的可見性及生命周期,它能減少名稱沖突,而且提供了自動內存管理 --javascript 語言精粹 我理解的是,一個變量、函數或者成員可以在代碼中訪問到的范圍。 js的變量作...
摘要:的分句會創建一個塊作用域,其聲明的變量僅在中有效。而閉包的神奇作用是阻止此事發生。依然持有對該作用域的引用,而這個引用就叫做閉包。當然,無論使用何種方式對函數類型的值進行傳遞,當函數在別處被調用時都可以觀察到閉包。 date: 16.12.8 Thursday 第一章 作用域是什么 LHS:賦值操作的目標是誰? 比如: a = 2; RHS:誰是賦值操作的源頭? 比如: conso...
閱讀 3729·2021-11-24 09:39
閱讀 2610·2019-08-30 15:54
閱讀 1149·2019-08-30 13:01
閱讀 3429·2019-08-28 18:30
閱讀 1623·2019-08-26 17:44
閱讀 3591·2019-08-26 11:31
閱讀 2413·2019-08-26 10:40
閱讀 1239·2019-08-26 10:27