閱讀目錄
什麼是閉包?
閉包的特性
閉包的作用:
閉包的程式碼範例
注意事項
總結
閉包在javascript來說是比較重要的概念,平時工作中也是用的比較多的一項技術。下來對其進行一個小小的總結
什麼是閉包?
官方說法:
閉包是指有權存取另一個函數作用域中的變數的函數。創建閉包的常見方式,就是在一個函數內部創建另一個函數,透過另一個函數存取這個函數的局部變數
下面就是一個簡單的閉包:
function A(){ var text="hello world"; function B(){ console.log(text); } return B; } var c=A(); c(); // hello world
按照字面量的意思是:函數B有權存取函數A作用域中的變數(text),透過另一個函數C來存取這個函數的局部變數text。因此函數B形成了一個閉包。也可以說C是閉包,因為C執行的實際上是函數B。
這個要注意的是,直接執行A();是沒有任何反應的。因為return B沒有執行,除非是return B();
閉包的特性
閉包有三個特性:
1.函數巢狀函數
2.函數內部可以引用外部的參數和變數
2.函數內部可以引用外部的參數和變數 3.參數和變數不會被垃圾回收機制回收解釋第3點,為什麼閉包的參數和變數不會被垃圾回收機制回收呢? 首先我們先了解javascript的垃圾回收原理:(1)、在javascript中,如果一個物件不再被引用,那麼這個物件就會被GC(garbage collection)回收;(2)、如果兩個物件互相引用,而不再被第3者所引用,那麼這兩個互相引用的物件也會被回收。 上面的範例程式碼中A是B的父函數,而B被賦給了一個全域變數C(全域變數的生命週期直到瀏覽器卸載頁面才會結束),這導致B總是在記憶體中,而B的存在依賴A,因此A也始終在記憶體中,不會在呼叫結束後,被垃圾回收機制(garbage collection)回收。 閉包的作用:其實閉包的作用也是有閉包的特性決定的,根據上面的閉包特性,閉包的作用如下: 1、可以讀取函數內部的變量,而不是定義一起全域變量,避免污染環境 2、讓這些變數的值始終保持在記憶體中。 閉包的程式碼範例下面主要介紹幾個常見的閉包,並進行解析:demo1 局部變數的累加。function countFn(){ var count=1; return function(){ //函数嵌套函数 count++; console.log(count); } } var y = countFn(); //外部函数赋给变量y; y(); //2 //y函数调用一次,结果为2,相当于countFn()() y(); //3 //y函数调用第二次,结果为3,因为上一次调用的count还保存在内存中,没有被销毁,所以实现了累加 y=null; //垃圾回收,释放内存 y(); // y is not a function由於第一次執行完,變數count還保存在記憶體中,所以不會被回收,以致於第二次執行的時候可以對上次的值就行累加。當引入y=null時,銷毀引用,釋放記憶體demo2 循環中使用閉包程式碼如下(下面的三個程式碼範例):我們的目的是想在每次循環中呼叫循環序號:demo2 -1
for (var i = 0; i < 10; i++) { var a = function(){ console.log(i) } a() //依次为0--9 }這個例子的結果是沒有題的,我們依次印出了0-9每一層匿名函數和變數i都組成了一個閉包,但是這樣在循環中並沒有問題,因為函數在循環體中立即被執行了demo2-2但是在setTimeout中就不一樣了
for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); //10次10 }, 1000); }我們期望的依次是打印出0--10,實際情況是打印出10次10 。即使吧setTimeout的時間改為0,也是印出10個10個。這是為什麼呢? 這是因為setTimeout的一種機制,setTimeout是從任務佇列結束的時候開始計時的,如果前面有行程沒有結束,那麼它就等到它結束再開始計時。在這裡,任務隊列就是它自己所在的循環。 循環結束setTimeout才開始計時,所以無論如何,setTimeout裡面的i都是最後一次循環的 i。程式碼中,最後的i 為10,所以印出了10個10.這也就是為什麼setTimeout的回調不是每次取循環時的值,而取最後一次的值demo2-3解決上面的setTimeout不能依序印出循環的問題
for(var i=0;i<10;i++){ var a=function(e){ return function(){ console.log(e); //依次输入0--9 } } setTimeout(a(i),0); }因為setTimeout第一個參數需要一個函數,所以返回一個函數給它,返回的同時把i 作為參數傳進去,透過形參e 快取了i,也就是說e變數相當於是i 的一個拷貝,並帶進返回的函數裡面。 當 setTimeout 的執行時,它就擁有了對 e 的引用,而這個值是不會被循環改變的。 也可以用下面的寫法,和上面類似:
for(var i = 0; i < 10; i++) { (function(e) { setTimeout(function() { console.log(e); //依次打印出0-9 }, 0); })(i); }demo3 循環中添加事件看下面的一個典型的demo.我們希望每次點擊出li的時候,alert好讓li的索引值,所以用下面的程式碼:
<ul id="test"> <li>第一个</li> <li>第二个</li> <li>第三个</li> <li>第四个</li> </ul> var nodes = document.getElementsByTagName("li"); for(i = 0,len=nodes.length;i<len;i++){ nodes[i].onclick = function(){ alert(i); //值全是4 }; }事與願違,無論點擊哪一個li,都是alert(4),也就是都是alert循環結束之後的索引值。這是為什麼呢? 🎜🎜這是因為循環中為不同的元素綁定事件,事件回調函數裡如果調用了跟循環相關的變量,則這個變量取循環的最後一個值。 🎜
由于绑定的回调函数是一个匿名函数,所以上面的代码中, 这个匿名函数是一个闭包,携带的作用域为外层作用域(也就是for里面的作用域),当事件触发的时候,作用域中的变量已经随着循环走到最后了。
还有一点就是,事件是需要触发的,而绝大多数情况下,触发的时候循环已经结束了,所以循环相关的变量就是最后一次的取值。
要实现点击li,alert出li的索引值,需要将上面的代码进行以下的修改:
<ul id="test"> <li>第一个</li> <li>第二个</li> <li>第三个</li> <li>第四个</li> </ul> var nodes=document.getElementsByTagName("li"); for(var i=0;i<nodes.length;i++){ (function(e){ nodes[i].onclick=function(){ alert(e); }; })(i) }
解决思路: 增加若干个对应的闭包域空间(这里采用的是匿名函数),专门用来存储原先需要引用的内容(下标)。
当立即执行函数执行的时候,e 值不会被销毁,因为它的里面有个匿名函数(也可以说是因为闭包的存在,所以变量不会被销毁)。执行后,e 值 与全局变量 i 的联系就切断了,
也就是说,执行的时候,传进的 i 是多少,立即执行函数的 e 就是多少,但是 e 值不会消失,因为匿名函数的存在。
也可以用下面的解法,原理是一样的:
<ul id="test"> <li>第一个</li> <li>第二个</li> <li>第三个</li> <li>第四个</li> </ul> var nodes=document.getElementsByTagName('li'); for(var i = 0; i<nodes.length;i++){ (function(){ var temp = i; nodes[i].onclick = function () { alert(temp); } })(); }
注意事项
1、造成内存泄露
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,所以只有在绝对必要时再考虑使用闭包。
2、在闭包中使用this也可能会导致一些问题。
其实我们的目的是想alert出object里面的name
var name="The Window"; var object={ name:"My Object", getNameFunc:function(){ return function(){ return this.name; } } } alert(object.getNameFunc()()); // The Window
因为在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window。
每个函数在被调用时,都会自动取的两个特殊变量:this和 arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止。也就是说,里面的return function只会搜索
到全局的this就停止继续搜索了。因为它永远不可能直接访问外部函数中的这两个变量。
稍作修改,把外部作用域中的this对象保存在一个闭包能够访问的变量里。这样就可以让闭包访问该对象了。
var name="The Window"; var object={ name:"My Object", getNameFunc:function(){ var that=this; return function(){ return that.name; } } } alert(object.getNameFunc()()); // My Object
我们把this对象赋值给了that变量。定义了闭包之后闭包也可以访问这个变量。因此,即使在函数返回之后,that也仍引用这object,所以调用object.getNameFunc()()就返回 “My Object”了。
总结
当在函数内部定义了其他函数,就创建了闭包。闭包有权访问包含函数内部的所有变量。
闭包的作用域包含着它自己的作用域、包含函数的作用域和全局作用域。
当函数返回一个闭包时,这个函数的作用域会一直在内存中保存到闭包不存在为止。
使用闭包必须维护额外的作用域,所有过度使用它们可能会占用大量的内存
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持PHP中文网!
更多理解javascript中的闭包相关文章请关注PHP中文网!