【3分鐘帶你學】JS閉包
一、什麼是閉包?
var n=99;
function f1(){
console.log(n);
}
f1(); //99
JavaScript中有兩種作用域,全局作用域和局部作用域,函數內部可以直接讀取全局變量。函數 f1 可以讀取全局變量 n。但是,在函數外部無法讀取函數內部聲明的變量。
function f1() {
var n = 99;
}
f1( );
console.log(n); //undefined
但是,有時我們卻需要在函數外部訪問函數內部的變量,正常情況下,這是辦不到的,只有通過變通方法才能實現。那就是在函數的內部, 再定義一個函數。
function f1() {
var n = 99;
var f2 = function() {
console.log(n);
}
return f2;
}
var f = f1();
f();
上面代碼中, 函數f2就在函數f1內部, 這時f1內部的所有局部變量,對f2都是可見的。但是反過來就不行, f2內部的局部變量,對f1就是不可見的。
這就是JavaScript語言特有的“鏈式作用域”結構(chain scope),子對象會一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立。既然f2可以讀取f1的局部變量, 那麼只要把f2作爲返回值,我們不就可以在f1外部讀取它的內部變量了嗎!
閉包就是函數f2, 即能夠讀取其他函數內部變量的函數。
由於在JavaScript語言中, 只有函數內部的子函數才能讀取內部變量, 因此可以把閉包簡單理解成“定義在一個函數內部的函數”。閉包最大的特點, 就是它可以“記住”誕生的環境, 比如f2記住了它誕生的環境f1, 所以從f2可以得到f1的內部變量。
在本質上, 閉包就是將函數內部和函數外部連接起來的一座橋樑;
二、垃圾回收機制(GC)及閉包
先看一個demo
function f1( ) {
var n = 99;
console.log(++n);
}
f1( ); //100
f1( ); //100
當我們在函數內部引入一個變量或函數時,系統都會開闢一塊內存空間;
還會將這塊內存的引用計數器進行初始化,初始化值爲0;
如果外部有全局變量或程序引用了這塊空間,則引用計數器會自動進行+1操作;
當函數執行完畢後,變量計數器重新歸零,系統會運行垃圾回收機制,將函數運行產生的數據銷燬;
如計數器不是 0 ,則不會清除數據;
這個過程就稱之爲 “JS的垃圾回收機制” ;
但是,如果將代碼改爲閉包形式:
function f1() {
var n = 99;
function f2(){
console.log(++n);
}
return f2;
}
var f = f1();
f(); //100
f(); //101
運行代碼發現,函數調用一次,其變量 n 變化一次;因函數f1被調用時,返回的結果是f2函數體,也就是說,f2函數被當作值返回給f1的調用者,但是f2函數並沒有在此時被調用執行,所以整個 f1 函數體,無法判斷子函數f2會對其產生何種影響,無法判斷變量n是否會被使用;即使f1函數被調用結束,整個f1函數始終保留在內存中不會被垃圾回收機制回收;
三、閉包的作用
閉包的最大用處有2個,
一個是可以讀取函數內部的變量,
另一個就是讓這些變量始終保持在內存中.
此外,還保護變量的安全,因爲函數內的變量只能通過函數內部訪問,其他途徑不能訪問即閉包可以使得它誕生環境一直存在;
注意, 外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,所以內存消耗很大。因此不能濫用閉包, 否則會造成網頁的性能問題。
下附一個實際應用案例 tab切換
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
* {
padding-bottom: 0px;
margin: 0px;
padding-left: 0px;
padding-right: 0px;
font-size: 12px;
padding-top: 0px;
}
BODY {
padding-left: 20px;
padding-top: 20px;
}
.wid240 {
width: 242px;
margin-bottom: 20px;
}
.wid180 {
width: 182px;
}
.tab {
border-bottom: #000 1px solid;
border-left: #000 1px solid;
border-top: #000 1px solid;
border-right: #000 1px solid;
}
.tab UL {
zoom: 1;
clear: both;
}
.tab UL:after {
display: block;
height: 0px;
visibility: hidden;
clear: both;
content: "";
}
.tab UL LI {
text-align: center;
line-height: 26px;
width: 60px;
display: inline;
background: #000;
float: left;
height: 26px;
color: #fff;
}
.tab UL LI.on {
background: #fff;
color: #000;
}
.tabList {
border-bottom: #000 1px solid;
border-left: #000 1px solid;
height: 150px;
border-top: #000 1px;
border-right: #000 1px solid;
}
.tabList .one {
padding-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
display: none;
color: #ff0000;
padding-top: 10px;
}
.tabList .block {
display: block;
}
</style>
<meta name="GENERATOR" content="MSHTML 8.00.7600.16535">
</head>
<body>
<div class="wid240">
<div class="tab">
<ul>
<li id="one1" class="on">one1 </li>
<li id="one2">one2 </li>
<li id="one3">one3 </li>
<li id="one4">one4 </li>
</ul>
</div>
<div class="tabList">
<div id="cont_one_1" class="one block"> cont_one_1</div>
<div id="cont_one_2" class="one"> cont_one_2</div>
<div id="cont_one_3" class="one"> cont_one_3</div>
<div id="cont_one_4" class="one"> cont_one_4</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
//獲取li標籤
var lis = document.getElementsByTagName('li');
//綁定懸浮事件
for(var i=0;i<lis.length;i++) {
lis[i].onmouseover = (function(index){
return function() {
//切換div.one
var num = parseInt(index+1);
var div = document.getElementById('cont_one_'+num);
//判斷
var divs = document.querySelectorAll('.one');
for(var i=0;i<divs.length;i++) {
divs[i].style.display = 'none';
}
div.style.display = 'block';
}
})(i);
}
</script>
</html>