簡談JavaScript閉包

閉包是什麼

  • 第一種說法:閉包建立一個詞法做用域,這個做用域裏面的變量被引用以後能夠在這個詞法做用域外面被自由訪問,是一個函數和聲明該函數的詞法環境的組合
  • 第二種說法:閉包就是引用了自由變量的函數,這個自由變量與函數一同存在,即便脫離了建立它的環境。因此常常看到的說閉包就是綁定了上下文環境的函數。
  • 我更偏向於閉包是一個函數和聲明該函數的詞法環境的組合。

JS裏面的閉包

先上一個閉包

function sayHello(name){
    let str = 'Hello,${name}';
    function say(){
        console.log(str);
    }
    return say;
}

let myHello = sayHello('abby');
myHello();

該例子的解釋

上面的代碼,在sayHello函數裏面定義的say函數和這個函數聲明的詞法環境就造成了一個閉包。say函數引用了sayHello函數裏面定義的一個變量str,而且sayHello函數將say這個函數return了出去,這樣,在sayHello函數的外面也能訪問到它詞法做用域裏面的變量str,最後就像say這個函數和str這個變量綁定了同樣閉包

爲何在外部還能訪問到變量str呢?

  • 在一些語言中,通常認爲函數的局部變量只在函數的執行期間能夠訪問
  • 當上段代碼在執行到let myHello = sayHello('abby');這段代碼的時候,按理會銷燬掉sayHello這個函數的執行環境,可是在這裏卻沒有,由於,sayHello這個函數返回的是一個函數,這個函數裏面的str引用了外部的變量str,若是銷燬了sayHello的執行環境就會找不到了,因此,sayHello的執行環境會一直在內存中,因此也就會有閉包會增長內存開銷的說法

體會

  • 在JavaScript語言中,只有函數內部的子函數才能讀取內部變量,能夠把閉包簡單理解成「定義在一個函數內部的函數」
  • 閉包就是將函數內部和函數外部鏈接起來的一座橋樑

閉包的用處

一、讀取函數內部的變量

二、讓這些變量始終保持在內存中

function createIncrementor(start) {
    return function () {
        return start++;
    };
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7

start是函數createIncrementor的內部變量,經過閉包,start的狀態被保留了,每一次調用都是在上一次調用的基礎上進行計算,閉包inc使得函數createIncrementor的內部環境一直存在,由於inc始終存在內存中,而inc的存在依賴於createIncrementor,所以該函數不會在調用結束後,被垃圾回收機制回收函數

三、封裝對象的私有屬性和私有方法

function Person(name) {
    var _age;
    function setAge(n) {
        _age = n;
    }
    function getAge() {
        return _age;
    }
    return {
        name: name,
        getAge: getAge,
        setAge: setAge
    };
}

var p1 = Person("xiaoming");
p1.setAge(25);
pa.getAge(); //25

函數Person的內部變量_age,經過閉包getAge和setAge,變成了返回對象p1的私有變量,外層函數每次運行,都會產生一個新的閉包,而這個閉包又會保留外層函數的內部變量,內存也就消耗較多code

在舉幾個例子

一、常見的閉包都是return出來一個函數,但並非說明,閉包必定須要return一個函數,return一個函數也只是爲了能在做用域範圍以外訪問一個變量對象

let say;
function sayHello(name){
    let str = 'Hello,${name}';
    say = function(){
        console.log(str);
    }
}

let myHello = sayHello('abby');
say();

二、同一個調用函數生成同一個閉包環境,在裏面聲明的全部函數同時具備這個環境裏面的變量的引用ip

let get,up,down
function setUp(){
    let number = 20;
    get = function(){
        console.log(number);
    }
    up = function(){
        number += 3;
    }
    down = function(){
        number -= 2;
    }
}

setUp();
get();
up();
down();
get();

三、每個調用函數都會建立不一樣的閉包環境,裏面的變量互不影響內存

function newClosure(){
    let array = [1,2];
    return function(num){
        array.push(num);
        console.log('array:${array}');
    }
}

let myClosure = newClosure();
let yourClosure = newClosure();
myClosure(3);
yourClosure(4);
myClosure(5);

四、在循環裏面建立閉包作用域

function newClosure(){
    for(var i=0;i<5;i++){
        setTimeout(function(){
            console.log(i);
        });
    }
}

newClosure();//5個5

改進方法一:建立一個新的閉包對象,這樣每一個閉包對象裏面的變量就互不影響rem

function log(i){
    return function(){
        console.log(i);
    }
}

function newClosure(){
    for(var i=0;i<5;i++){
        setTimeout(log(i));
    }
}
newClosure();

每次log(i)都會建立不一樣的閉包對象,全部的回調函數不會指向同一個環境get

改進方法二:使用自執行函數,外部的匿名函數會當即執行,而且把i做爲它的參數,此時函數內變量e就擁有了i的一個拷貝。當傳遞給setTimeout的匿名函數執行時,它就擁有了對e的引用,而這個值是不會被循環改變的回調函數

function newClosure(){
    for(var i=0;i<5;i++){
        (function(e){
            setTimeout(function(){
                console.log(e);
            });
        })(i)
    }
}

newClosure();
相關文章
相關標籤/搜索