前面一片文章講到過一點函數,瞭解到每聲明一個函數就會產生一個做用域。而外面的做用域訪問不了裏面的做用域(把裏面的變量和函數隱藏起來),而裏面的能夠訪問到外面的。對於隱藏變量和函數是一個很是有用的技術。html
基於做用域隱藏的方法叫作最小受權或最小暴露原則。ajax
這個原則是指在軟件設計中,應該最小限度的暴露必要內容,而將其內容都隱藏起來,好比某個模塊或對象得API設計。隱藏變量和函數能夠解決同名標識符的之間的衝突,衝突會致使變量的意外覆蓋。瀏覽器
例如:閉包
var a = 2; function foo(){ var a = 3; console.log(a); } foo(); console.log(a);
雖然這種技術能夠解決一些問題,可是他並不理想,會致使一些額外的問題,首先必須聲明一個具名函數foo(),意味着foo這個名稱自己「污染」了所在的做用域,其次必須顯式的經過函數名foo()調用這個函數才能運行其中的代碼。異步
若是函數不須要函數名,而且可以自動運行,這會更加理想。幸虧js提供了同時解決這兩個問題的方案 -- (IIFE) Immediately Invoked Function Expression -- 當即執行函數
async
var a = 2; (function foo(){ var a = 3; console.log(a); })() console.log(a);
首先當即執行函數不會當作函數聲明處理而是當作函數表達式處理。 函數
區分函數聲明仍是函數表達式:看function在聲明中是否是第一個詞,若是是第一個詞就是函數聲明不然就是函數表達式。而當即執行函數" (function ",不是" function ",因此是函數表達式。oop
函數聲明和函數表達式之間最重要的區別是他們的名稱標識符將會綁定在何處spa
函數聲明的函名稱數會綁定在當前做用域內。假如在全局做用域建立一個函數聲明,就能夠在全局做用域訪問這個函數名稱並執行。而函數表達式的函數名稱會綁定在自身的函數中,而不是當前說在做用域中。例如你全局建立一個函數表達式,若是你直接執行這個你建立的函數表達式的函數名就會報錯,由於當前做用域下沒有這個標識符,而你在函數表達式裏面的做用域裏訪問這個函數名就會返回這個函數的引用。線程
做用域閉包,嗯,閉包這兒兩個字就有點讓人難以理解,(能夠想象成一個包是關上的,裏面隱藏了一些神祕的東西)而對於閉包的定義是這樣說的:當函數能夠記住並訪問所在的做用域時,就產生了閉包,即便函數是在當前做用域以外執行。
for instance(拽個英文,哈哈)。
function foo() { var a = 2; function bar() { console.log(a); } bar(); } foo();
上面的 代碼bar()能夠訪問外部做用域中的變量。根據上面的定義這是閉包嗎?從技術來說也許是,但咱們理解的是做用域在當前做用域查找變量若是沒找到會繼續向上面查找,找到返回,找不到繼續找,直到全局做用域。-- 而這些正是閉包的一部分。函數bar()具備一個涵蓋foo()做用域的閉包。
function foo(){ var a = 2; function bar (){ console.log(a); } return bar; } var baz = foo(); baz();
在上面的代碼更好的展現了閉包。
bar()函數在定義時做用域之外的地方執行(此時在全局做用域執行)。在foo()函數執行後,一般會期待foo()整個內部做用域都被銷燬,由於咱們知道引擎有垃圾回收器用來釋放不在使用的內存空間,因爲foo()已經執行完,看上去內容不會再被使用,因此很天然的會考慮對齊進行回收,回收後意味着裏面的函數和變量訪問不到了。foo()執行完,baz變量存着bar函數的引用。當執行baz也就是bar函數時。console.log(a)。不理解閉包的人可能認爲會報錯,事實上,打印的是2;???what?
foo()函數做用域不是執行完銷燬了嗎?怎麼還能訪問到a變量?-- 這就是閉包。
當foo()執行後,bar函數被返回全局做用域下,可是bar函數還保留着當時的詞法做用域(當時寫代碼是的順序就已經定義了做用域,這個做用域叫詞法做用域--外面函數套着裏面的函數的那種)甚至直到全局做用域。因此bar還留有foo()函數的引用。使得foo()函數沒有被回收。
閉包能夠說不出不在,只是你沒有發現認出他。在定時器,事件監聽器,ajax請求,跨窗口通訊或者任何其餘的異步(或者同步)任務中,只要使用了回調函數,實際上就是使用閉包。
for instance
function wait(message) { setTimeout(function timer() { console.log(message); }, 1000); } wait("hello");
在上面的代碼中將一個內部函數(名爲timer)傳遞給setTimerout(...).timer具備涵蓋wait(...)的做用域的閉包。所以還保有對變量message的引用。wait()執行1000毫秒後,它的內部做用域不會消失,timer函數依然保有wait()做用域的閉包。
而閉包和當即執行函數息息相關。
循環和閉包
for(var i = 1; i <= 5; i++){ setTimeout(function timer(){ console.log(i); },i*1000); }
上面代碼咱們覺得輸出的會是1-5,可事實上輸出的是5個6,這是爲啥啊 -- 閉包啊。
延遲函數的回調會在循環結束時執行。事實上,當定時器運行時即便每一個迭代的是setTimerout(...,0),全部的回調函數依然是循環結束後纔會執行。我猜是跟js執行機制有關係吧。至於爲何都是6. 由於即便5個函數是在各個迭代中分別定義的,可是他們又被封閉在一個共享的全局做用域中所以實際上只有一個i.而怎麼解決呢,當即執行函數來了!!!
for (var i = 1; i <= 5; i++) { (function (i) { setTimeout(function timer() { console.log(i); }, i * 1000); })(i) }
打印出來1,2,3,4,5了歐,這回是你想要的數了。解釋一下,5次循環建立了5個當即執行函數,這5個函數的做用域都不相同,當即函數接收的參數是當前循環的i.因此當timer執行時訪問的就是本身當即執行函數對應的做用域。也就是說5個timer函數分別對應5個做用域,每一個做用域保存的變量i都不一樣,解決啦!!!
你懂閉包了嗎?
js執行機制
JavaScript語言的一大特色就是單線程,也就是說,同一個時間只能作一件事。那麼,爲何JavaScript不能有多個線程呢?這樣能提升效率啊。JavaScript的單線程,與它的用途有關。做爲瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操做DOM。這決定了它只能是單線程,不然會帶來很複雜的同步問題。好比,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準因此,爲了不復雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特徵,未來也不會改變。
單線程就意味着,全部任務須要排隊,前一個任務結束,纔會執行後一個任務。若是前一個任務耗時很長,後一個任務就不得不一直等着。JavaScript語言的設計者意識到這個問題,將全部任務分紅兩種,一種是同步任務(synchronous),另外一種是異步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。
主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,因此整個的這種運行機制又稱爲Event Loop(事件循環)。只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。
哪些語句會放入異步任務隊列及放入時機通常來講,有如下四種會放入異步任務隊列:setTimeout 和 setlnterval ,DOM事件,ES6中的Promise,Ajax異步請求
原文出處:https://www.cnblogs.com/jiaobaba/p/11218624.html