摘要:內部的稱為內部函數或閉包函數。過度使用閉包會導致性能下降。,閉包函數分為定義時,和運行時。循環會先運行完畢,此時,閉包函數并沒有運行。閉包只能取得外部函數中的最后一個值。事件綁定種的匿名函數也是閉包函數。而對象中的閉包函數,指向。
閉包概念解釋:
閉包(也叫詞法閉包或者函數閉包)。
在一個函數parent內聲明另一個函數child,形成了嵌套。函數child使用了函數parent的參數或變量,那么就形成了閉包。
閉包(closure)是可以訪問外部函數作用域中的變量或參數的函數。
此時,包裹的函數稱為外部函數。內部的稱為內部函數或閉包函數。(zyx456自定義:或稱為父函數和子函數)。
閉包wiki
JS采用詞法作用域(lexical scoping),函數的執行依賴于函數作用域,這個作用域是在函數定義時決定的,而不是函數調用時決定的。
詞法作用域:詞法作用域也叫靜態作用域,是指作用域在詞法解析階段就已經確定了,不會改變。
動態作用域:是指作用域在運行時才能確定。
參看下面的例子,引自楊志的回答
var foo=1; function static(){ alert(foo); } !function(){ var foo=2; static(); }(); 在js中,會彈出1而非2,因為static的scope在創建時,記錄的foo是1。 如果js是動態作用域,那么他應該彈出2
zyx456:識別閉包,在詞法分析階段已經確定了。
當外部函數運行的時候,一個閉包就形成了,他由內部函數的代碼以及任何內部函數中指向外部函數局部變量的引用組成。
注意事項01,閉包函數作用域中,使用的外部函數變量不會被立刻銷毀回收,所以會占用更多的內存。過度使用閉包會導致性能下降。建議在非常有必要的時候才使用閉包。
02,同一個閉包函數,所訪問的外部函數的變量是同一個變量。
03,如果把閉包函數,賦值給不同的變量,那么不同的變量指向的是不同的閉包函數,所使用的外部函數變量是不同的。
04,閉包函數分為定義時,和運行時。只有運行時,才會訪問外部函數的變量。
05,在for循環的閉包函數,只有在運行時,才在作用域中尋找變量。for循環會先運行完畢,此時,閉包函數并沒有運行。
06,如果在for循環中,使用閉包的自執行函數。那么閉包會使用for循環的變量i(0-*,假設i從0開始)。
07,一個函數里,可以有多個閉包。
匿名自執行函數,可以封裝私有變量。不會污染全局作用域。匿名函數中定義的任何變量,都會在執行結束時被銷毀。
eval+with(僅了解)在評論中賀師俊還提到,eval 和 with可以產生動態作用域的效果:
比如 with(o) { console.log(x) } 這個x實際有可能是 o.x 。所以這就不是靜態(詞法)作用域了。
var x = 0; void function (code) { eval(code); console.log(x) }("var x=1")
不過注意eval在strict模式下被限制了,不再能形成動態作用域了。
為什么閉包函數可以訪問外部函數的變量?因為閉包函數的作用域鏈包含了外部函數的作用域。
如何創建閉包?在一個函數類內創建另外一個函數。內部函數使用了外部函數的變量,就形成了閉包。
普通函數的內部函數是閉包函數么?zyx456:不是。
函數第一次被調用時,會發生什么?當函數第一次被調用時,會創建一個執行環境(execution context)和相應的作用域鏈,并把作用域鏈賦值給一個內部屬性(即[[Scope]])。
然后,使用this、arguments和其他參數來初始化函數的活動對象(activation object)。
在作用域鏈中,內部函數的活動對象處于第一位,外部函數的活動對象始終處于第二位,外部函數的外部函數的活動對象處于第三位,……直至作為作用域鏈終點的全局執行環境。
在函數執行過程中,讀取和寫入變量的值,都需要在作用域鏈中查找變量。
每次調用JS函數,會為之創建一個新的對象來保存所有的局部變量(函數定義的變量,函數參數。),把這個對象添加到作用域鏈中。函數體內部的變量都保存在函數作用域內。
我們將作用域鏈看做一個對象列表,而不是一個棧。
(zyx456:棧是一種線性表,僅允許在表的一端進行插入和刪除操作。)
當函數返回的時候,就從作用域鏈中將這個綁定變量的對象刪除。
如果這個函數不存在嵌套的函數,也沒有其他引用指向這個綁定變量的對象,它就會被當做垃圾回收掉。
(zyx456:這個操作由瀏覽器自動完成)。
如果這個函數有嵌套的函數,每個嵌套的函數都各自對應一個作用域鏈。
這時:
內部函數,被作為返回值返回,或存儲在某處屬性中,就是會有一個外部引用指向這個它,那么它就不會被當做垃圾回收,并且它所使用外部變量所在的對象也不會被當做垃圾回收。只有內部函數被銷毀后,外部函數的活動對象才會被銷毀。
在函數中訪問一個變量時,就會從作用域鏈中查找變量。
一般來講,當函數執行完畢后,局部活動對象就會被銷毀,內存中僅保存全局作用域(全局執行環境的環境對象)。
但是,閉包的情況又有所不同。閉包函數的作用域鏈上有外部函數的作用域鏈。所以閉包函數可以訪問外部函數的變量。
閉包函數必須返回(return)么,return這個閉包函數?zyx456:不必要返回,只要使用外部函數的變量即可。
代碼:
function fn1() { var a = 1; function fn2() { console.log(a); } fn2(); } fn1();如果用不同的變量引用函數中的閉包函數,那么是不同的閉包變量。
簡單的例子:
function outter(){ var x = 0; return function(){ return x++; } } var a = outter(); console.log(a()); console.log(a()); var b = outter(); console.log(b()); console.log(b());
運行結果為:
0
1
0
1
可以創建私有變量。
因為只有閉包函數可以訪問外部函數的變量。
因為在閉包內部保持了對外部活動對象的訪問,但外部的變量卻無法直接訪問內部,避免了全局污染;
function setMoyu(){ var name = "moyu"; return function(newValue){ name=newValue; console.log(name); } } var setValue = setMoyu(); setValue("world");//world /*zyx456:這時name是私有屬性了,只能通過閉包函數設置它*/閉包的缺點?
可能導致內存占用過多,因為閉包攜帶了自身的函數作用域。
閉包只能取得外部函數中的最后一個值。
作用域:變量聲明如果不使用 var 關鍵字,那么它就是一個全局變量,即便它在函數內定義。
變量生命周期
全局變量的作用域是全局性的,即在整個JS程序中,全局變量處處都在。
而在函數內部聲明的變量,只在函數內部起作用。
這些變量是局部變量,作用域是局部性的;
函數的參數也是局部性的,只在函數內部起作用。
在JS中,所有函數都能訪問它們上一層的作用域。
例子:
function compare(value1, value2){ if (value1 < value2){ return -1; } else if (value1 > value2){ return 1; } else { return 0; } } var result = compare(5, 10);內存泄漏
由于IE 的JS對象和DOM對象使用不同的垃圾收集方式,因此閉包在IE中會導致內存泄漏的問題,也就是無法銷毀駐留在內存中的元素。
事件綁定種的匿名函數也是閉包函數。
如果閉包的作用域鏈中保存著一個HTML元素,那么就意味著該元素將無法被銷毀。
function box() { var oDiv = document.getElementById("oDiv"); //oDiv 用完之后一直駐留在內存 oDiv.onclick = function () { alert(oDiv.innerHTML); //這里用oDiv 導致內存泄漏 }; } box();
那么在最后應該將oDiv 解除引用來避免內存泄漏。
function box() { var oDiv = document.getElementById("oDiv"); var text = oDiv.innerHTML; oDiv.onclick = function () { alert(text); }; oDiv = null; //解除引用 }
PS:如果并沒有使用解除引用,那么需要等到瀏覽器關閉才得以釋放。
閉包和this和arguments
閉包函數中的this問題對于某個函數來說,如果函數在全局環境中,this指向window。如果在對象中,就指向這個對象。
而對象中的閉包函數,this指向window。因為閉包并不屬于這個對象的屬性或方法。
var user = "The Window"; var obj = { user : "The Object", getUserFunction : function () { return function () { //閉包不屬于obj,里面的this 指向window return this.user; }; } }; alert(obj.getUserFunction()()); //The window //可以強制指向某個對象 alert(obj.getUserFunction().call(obj)); //The Object //也可以從上一個作用域中得到對象 getUserFunction : function () { var that = this; //從對象的方法里得對象 return function () { return that.user; }; }
例子:
var self = this; // 將this保存至一個變量中,以便嵌套的函數能夠訪問它
綁定arguments的問題與之類似。
arguments并不是一個關鍵字,但在調用每個函數時都會自動聲明它,由于閉包具有自己所綁定的arguments,因此閉包內無法直接訪問外部函數的參數數組,除非外部函數將參數數組保存到另外一個變量中:
var outerArguments = arguments; //保存起來以便嵌套的函數能使用它
在通過call()或apply()改變函數執行環境的情況下,this就會指向其他對象。
例子:
var scope = "global scope"; // 全局變量 function checkscope() { var scope = "local scope"; // 局部變量 function f() { return scope; } // 在作用域中返回這個值 return f(); } checkscope() // => "local scope"
checkscope()函數聲明了一個局部變量,并定義了一個函數f(),函數f()返回了這個變量的值,最后將函數f()的執行結果返回。
你應當非常清楚為什么調用checkscope()會返回"local scope"?,F在我們對這段代碼做一點改動。
var scope = "global scope"; // 全局變量 function checkscope() { var scope = "local scope"; // 局部變量 function f() { return scope; } // 在作用域中返回這個值 return f; } checkscope()() // 返回值是什么?//local scope
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/106028.html
摘要:和構造函數構造函數可以使用,然后再次創建實例。提供的值被忽略,提供的那些參數仍然會被前置到構造函數調用的前面。在這種情況下,指向全局作用域。現在將作為的方法來調用,傳入這些實參用于構造函數。 概念 bind() 方法會返回一個新函數(稱為綁定函數),綁定函數與原函數(使用bind()的函數)具有相同的函數體,但是綁定函數有新的this值和參數。 說白了,bind()就是創建一個有著新t...
摘要:系列目錄復習資料資料整理個人整理重溫基礎篇重溫基礎對象介紹重溫基礎對象介紹重溫基礎介紹重溫基礎相等性判斷本章節復習的是中的關于閉包,這個小哥哥呀,看看。這里隨著閉包函數的結束,執行環境銷毀,變量回收。 本文是 重溫基礎 系列文章的第十九篇。今日感受:將混亂的事情找出之間的聯系,也是種能力。 系列目錄: 【復習資料】ES6/ES7/ES8/ES9資料整理(個人整理) 【重溫基礎】...
摘要:本文是重溫基礎系列文章的第四篇。系列目錄復習資料資料整理個人整理重溫基礎語法和數據類型重溫基礎流程控制和錯誤處理重溫基礎循環和迭代本章節復習的是中的基礎組件之一,函數,用來復用特定執行邏輯。箭頭函數不能使用命令,即不能用作函數。 本文是 重溫基礎 系列文章的第四篇。今日感受:常懷感恩之心,對人對己。 系列目錄: 【復習資料】ES6/ES7/ES8/ES9資料整理(個人整理) 【重溫基...
摘要:內存泄露內存泄露概念在計算機科學中,內存泄漏指由于疏忽或錯誤造成程序未能釋放已經不再使用的內存。判斷內存泄漏,以字段為準。 本文是 重溫基礎 系列文章的第二十二篇。 今日感受:優化學習方法。 系列目錄: 【復習資料】ES6/ES7/ES8/ES9資料整理(個人整理) 【重溫基礎】1-14篇 【重溫基礎】15.JS對象介紹 【重溫基礎】16.JSON對象介紹 【重溫基礎】1...
摘要:箭頭函數本文字符數,閱讀時間約分鐘左右。箭頭函數等于說,只保留了函數的參數和返回。箭頭函數體內的,繼承的是外層代碼塊的。所以,不用用箭頭函數聲明對象的方法。不可以使用命令因此箭頭函數不能用作函數。 【01】ES6箭頭函數 本文字符數4300+,閱讀時間約8分鐘左右。 【01】箭頭函數 等于說,只保留了函數的參數和返回。省略function和return。 寫法: (形參) => {st...
閱讀 2570·2021-09-06 15:02
閱讀 3200·2021-09-02 10:18
閱讀 2821·2019-08-30 15:44
閱讀 685·2019-08-30 15:43
閱讀 1948·2019-08-30 14:08
閱讀 2758·2019-08-30 13:16
閱讀 1397·2019-08-26 13:52
閱讀 931·2019-08-26 12:21