深刻理解JavaScript之做用域鏈與閉包

做用域

做用域是指程序源代碼中定義變量的區域。javascript

實際上描述的就是查找變量的範圍,做用域必須有的兩個功能就是存儲變量以及查找變量,做用域就是發揮這兩個做用以及更多做用的規則。java

做用域規定了如何查找變量,也就是肯定當前執行代碼對變量的訪問權限。閉包

詞法做用域和動態做用域

  • 詞法做用域:(靜態做用域)函數的做用域在函數定義的時候就決定了。
  • 動態做用域:函數的做用域是在函數調用的時候才決定的。

JavaScript採用的是詞法做用域模塊化

全局做用域

在代碼中任何地方都能訪問到的對象擁有全局做用域函數

全局變量:設計

  1. 生命週期將存在於整個程序以內。
  2. 能被程序中任何函數或者方法訪問。
  3. 在 JavaScript 內默認是能夠被修改的。

JavaScript 採用詞法做用域(lexical scoping),也就是靜態做用域。code

顯式聲明

帶有關鍵字 var 的聲明對象

var winValue = 10;
console.log(window.winValue); // 10

隱式聲明

不帶有聲明關鍵字的變量,JS 會默認幫你聲明一個全局變量繼承

function foo(value) {
    result = value + 1;     // 沒有用 var 聲明
    return result;
};
foo(1);    
console.log(window.result);    // 2 <=  掛在了 window全局對象上

函數做用域

函數做用域內,對外是封閉的,從外層的做用域沒法直接訪問函數內部的做用域。生命週期

在函數內部的變量權限稱爲函數做用域,有如下特色:

  1. 每一個函數都有本身的做用域,並且調用一次就會生成新的做用域
  2. 只能在函數內部才能訪問,外部是沒有權限訪問的
  3. 進入函數內部時開啓,函數執行完畢後銷燬
function bar() {
    var foo = 'test';
}

console.log(foo); // Uncaught ReferenceError: foo is not defined

塊級做用域(ES6新增)

凡是由{}符號包裹起來的都是塊做用域

for(let i = 0; i < 5; i++) {
    // ...
}
console.log(i); // Uncaught ReferenceError: i is not defined

在 for 循環執行完畢以後 i 變量就被釋放了

做用域鏈

做用域鏈:當訪問一個變量時,解釋器會首先在當前做用域查找,若是沒有找到,就去父做用域找,直到找到該變量或者不在父做用域中,這就是做用域鏈。

var a = 1

function foo () {
    var b = 2
    console.log(a)
}

foo() // 1
console.log(b) // Uncaught ReferenceError: b is not defined

從上面代碼的執行結果能夠看出,foo 函數取到了它外部的變量 a, 而最外層的 console.log(b) 操做並沒能取得 foo 函數裏面的變量 b。

  • 做用域鏈和原型繼承查找時的區別:

若是去查找一個普通對象的屬性,可是在當前對象和其原型中都找不到時,會返回undefined;但查找的屬性在做用域鏈中不存在的話就會拋出ReferenceError。

閉包

定義

閉包是指有權訪問另一個函數做用域中的變量的函數

在javascript中,只有函數內部的子函數才能讀取局部變量,因此閉包能夠理解成"定義在一個函數內部的函數"。在本質上,閉包是將函數內部和函數外部鏈接起來的橋樑。

  • 閉包由兩部分構成:
  1. 函數
  2. 能訪問另一個函數做用域中的變量

爲何有閉包?

之因此出現閉包是由於JS的垃圾回收機制,JS自己爲了不解釋器過量消耗內存,形成系統崩潰,自帶有一套垃圾回收機制,垃圾回收機制可以檢測到一個對象是否是無用的。檢測到以後,就會把它佔用的內存釋放掉。可是實際工做中,咱們也會須要一些變量不那麼及時的被清理,因此就出現了閉包,用來達成這個效果。

閉包的特色

  • 閉包能夠訪問當前函數之外的變量
  • 即便外部函數已經返回,閉包仍能訪問外部函數定義的變量
  • 參數和變量不會被垃圾回收機制收回。
function getOuter(){
    var name = 'jacky';
    function getName(str){
        console.log(str + name);  // 能夠訪問getName函數外部的name
    }
    return getName('名字是:'); 
}
getOuter(); // 名字是:jacky

再來看下面一段代碼:

function bar() {
    var x = 1;
    return function () {
        var y = 2;
        return x + y;
    }
}
var foo = bar(); // 這一句執行完,變量x並無被回收,由於要內部函數還須要引用
console.log(foo()) // 3  執行內部函數,引用外部變量x

上面代碼中,在全局執行上下文中定義了一個函數bar和變量foo,函數bar內部返回一個匿名函數,因此此刻匿名函數的做用域鏈初始化爲包含了全局變量對象和bar中的變量對象。

當執行var foo = bar()時,把函數bar的執行上下文壓入棧,當bar執行完後,其執行上下文應該彈出棧,可是由於bar內部的匿名函數做用域鏈還引用這bar函數內的變量x,因此bar的執行上下文得不到釋放,這樣就造成了閉包。

閉包的應用

1.設計私有的方法和變量(封裝,定義模塊)。

var counter = (function(){
    var privateCounter = 0; //私有變量
    function change(val){
        privateCounter += val;
    }
    return {
        increment:function(){  
            change(1);
        },
        decrement:function(){
            change(-1);
        },
        value:function(){
            return privateCounter;
        }
    };
})();

2.匿名函數最大的用途是建立閉包。減小全局變量的使用。從而使用閉包模塊化代碼,減小全局變量的污染。

var objEvent = objEvent || {};
(function() {
    var addEvent = function() {
        // some code
    }
    function removeEvent() {
        // some code
    }
    objEvent.addEvent = addEvent
    objEvent.removeEvent = removeEvent
})()

addEvent 和 removeEvent 都是局部變量,但咱們能夠經過全局變量 objEvent 使用它

如何銷燬閉包

javascript中,若是一個對象再也不被引用,那麼這個對象就會被垃圾回收機制回收。若是兩個對象互相引用,而再也不被第3者所引用,那麼這兩個互相引用的對象也會被回收。

即釋放對閉包的引用,使引用變量爲null。

閉包的優缺點

優勢:

  1. 閉包裏的變量不會污染全局,由於變量被封在閉包裏;
  2. 全部變量都在閉包裏保證了隱私性和私有性;
  3. 可讓這些局部變量保存在內存中,實現變量數據共享。

缺點:

造成閉包即要把一個函數當成值傳遞,並且該函數還引用這另外一個函數的做用域鏈使得被引用的函數不能被回收,使用不當容易形成內存泄漏;

相關文章
相關標籤/搜索