一.什么是作用域?
單從字面意思上講,作用:起作用。域:區域、范圍。即作用范圍。
在javascript中作用域分為全局作用域和函數作用域。
-
es6有可以定義塊級作用域。
var a = 1; function fun(){ var b=2; } fun() console.log(a);//1 console.log(b)///會報錯 Uncaught ReferenceError: b is not defined
為什么我們可以訪問到a,卻訪問不到b呢?
- 原來,javascript本身是一個全局作用域,由于函數的存在,將其又劃分出自己單獨的函數作用域。
- 定義在函數中的變量(var),又叫局部變量。只有在函數內部才可以訪問到,當然在函數內也可以訪問全局作用域中的變量。
- 定義在函數外的變量,又叫全局變量。在全局作用域中訪問函數作用域中的變量是訪問不到的。
我們再看一個例子:
function fun(){
b=2;
}
fun()
console.log(b) //2
函數內部聲明變量的時候,一定要使用var命令。如果不用的話,你實際上聲明了一個全局變量!
我們再看一個例子:
function fun(a) {
};
fun(1)
console.log(a) //會報錯
函數的形參也是局部變量。
二. 作用域鏈
作用域鏈是保證對執行環境有權訪問的所有變量和函數的有序訪問。
var c=3
function f1(){
var b=2;
function f2(){
var a=1;
console.log(a)
//這里面可以訪問a,b,c
}
//這里面可以訪問b
}
//這里面只能訪問a
- 以上代碼涉及了3個執行環境:全局環境、f1()局部環境和f2()局部環境。
- 在一個變量環境中只能訪問他自己的環境和父執行環境。
bb.jpg
三. 閉包
前面我們講了js的作用域和作用域鏈,還拿前面的例子舉例
function fun(){
var b=2;
}
fun()
console.log(b)///會報錯 Uncaught ReferenceError: b is not defined
我們現在想要在外面獲取b的值怎么辦?
function A() {
var name = '閉包';
function B() {
console.log("Hello " + name + "!");
}
return B;
}
var C = A();
C(); //Hello 閉包!
上例是最簡單的閉包,我們可以發現
通俗來講:
- js分全局作用域和函數作用域。函數作用域里可以訪問到全局,通過一個叫作用域鏈的東西。但全局怎么訪問函數呢?
- 就有人想了在函數里面再寫一個函數(閉包),這樣把作用域鏈加長了。子函數通過引用父函數的變量,父函數返回這個函數,就可以在全局訪問到函數里的數據了。
總結一下
- 閉包是指有權訪問另一個函數作用域中變量的函數。
- 專業點講,即外部作用域——通過——函數內部返回引用,有權訪問該函數作用域中的變量。
但是
閉包能訪問到父級函數里面的數據說明父級里的數據一直存在內存中(只要閉包一直在),這就會導致內存一直被占著。即內存泄漏。
這就牽扯到瀏覽器的垃圾回收機制。
四. 瀏覽器的垃圾回收機制
一般情況下,當函數執行完畢后,局部活動對象就會被銷毀,也就是所謂的垃圾回收機制,內存里面只存有全局變量。但閉包則不一樣,因為閉包通常返回了一個引用,由于該引用沒有解除,所以局部活動對象仍然留在內存中。即造成內存泄漏!從而造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重后果。
function A() {
var count = 0;
function B() {
console.log(++count);
}
return B;
}
var C = A();
C(); // 1
C(); // 2
C(); // 3
C = null; // 解除引用
C =A();
C(); // 1
通過上面的例子可以發現,如果不解除C的引用,函數A中的count一直在內存中,但count卻不是全局變量。當不想污染全局變量卻需要使變量一直停留在內存以便使用的時候,可以使用閉包,這也是閉包的優點。在使用閉包過程中一定要注意內存泄露,當該變量不在有使用價值的時候,立刻解除引用。
五. 閉包的好處
- 封裝私有變量和方法
- 避免全局變量的污染
- 可以讓一個變量常駐內存 (如果用的多了就成了缺點)
- 因為閉包使用的自由變量對外部不可見,所以可以起到私有化的作用,只向外提供必要的接口,封裝內部邏輯。
- 正是因為有了閉包,才為JavaScript的模塊化編程奠定了基礎。
var human = (function() {
// 私有變量
var name = "閉包";
// 私有方法
var think = function() {
// 心想還是讓別人叫我小林吧
return name;
};
// 提供的外部接口
return {
speak : function() {
console.log("你好" + think());
}
};
})();
// 打?。耗愫?,閉包
human.speak();
外層是一個匿名方法:function(){...},通過立即執行的方式(用括號包裹起來作為方法表達式,并使用()立即執行)創建了一個閉包。外部的human無法直接訪問name與think,只能訪問對外開放的speak,從而達到封裝的效果。
六. 閉包的缺點
- 因為閉包會攜帶包含它的函數的作用域,因此會比其他函數占用更多的內存
- 引起內存泄露