JavaScript進階系列 - 閉包

個人博客倉庫html

this, call, apply 和 bind 以及箭頭函數git

用來放一些我看的書籍和學習的記錄,歡迎點贊和討論。github


閉包(Closures)

在瞭解閉包以前,有一些概念必須瞭解,這些是造成閉包的先決條件。promise

詞法做用域 (lexical scoping)

var age = 10;
function Tom(){
    var name = 'Tom';
    function say(){
        console.log('My name is '+name);
        console.log('My age is '+age);
        console.log(person);
    }
    say();
}
//Tom();
function Person(){
    var person = 'person';
    Tom();
}
Person();
// My name is Tom
// My age is 10
// error: person is not defined
複製代碼
var age = 10;
function Person(){
    var person = 'person';
    function Tom(){
        var name = 'Tom';
        function say(){
            console.log('My name is '+name);
            console.log('My age is '+age);
            console.log(person);
        }
        say();
    }
    Tom();
}
Person();
// My name is Tom
// My age is 10
// person
複製代碼

上面 Tom() 建立了一個局部變量 name 和一個名爲 say() 的函數, say() 是定義在 Tom() 裏的內部函數,僅在該函數體內可用。say() 內沒有本身的局部變量,然而它能夠訪問到外部函數的變量,因此 say() 可使用父函數 Tom() 中聲明的變量 name 。可是,若是有同名變量 name 在 say() 中被定義,則會使用 say() 中定義的 name 。bash

看到這裏你可能就有疑問了,詞法做用域究竟是什麼呢?閉包

它就是指在函數嵌套中,變量解析的一種規則,根據變量在代碼中聲明的位置,嵌套的函數在查找變量時,若是本身沒有聲明,那麼能夠訪問外部的變量。app

上面兩個例子還涉及到 this 的問題。異步

閉包

定義:函數 A 有一個函數 B ,函數 B 引用了函數 A 的變量, 函數 B 就叫作閉包。函數

閉包經常使用來構成偏函數和柯里化。

下面是偏函數的示例,柯里化和偏函數稍微有些區別,放到單獨一個模塊來講。性能

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12
複製代碼

解決循環中建立閉包的問題

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}
複製代碼

這是在循環中建立閉包的常見錯誤,這段代碼會輸出五個6,緣由是給 setTimeout 的函數是閉包,循環六次造成六個閉包,這六個閉包在循環環境中被建立,共享了一個詞法做用域,這個做用域存在一個變量 i ,異步執行的時候循環早已經結束,此時 i = 6 因此輸出的都是 6 。

方法1: let ,並且這樣不會產生閉包

for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}
複製代碼

方法2:匿名函數

for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j)
    }, j * 1000)
  })(i)
}
複製代碼

方法三: 使用閉包

function callback(j){
    return function () {
        console.log(j)
    }
}
for (var i = 1; i <= 5; i++) {
  setTimeout(callback(i), i * 1000)
}
複製代碼

方法四:setTimeout 的第三個參數

for (var i = 1; i <= 5; i++) {
  setTimeout(
    function timer(j) {
      console.log(j)
    },i * 1000,i)
}
複製代碼

性能

閉包在處理速度和內存消耗方面都有比較大的開銷。

例如:在建立新的對象或者類時,方法一般應該關聯於對象的原型,而不是定義到對象的構造器中。緣由是這將致使每次構造器被調用時,方法都會被從新賦值一次(也就是,每一個對象的建立)。

function Persion(name,age){
    this.name = name.toString();
    this.age = Number(age) || 0;
    this.getName = function(){
        console.log(this.name);
    };
    this.getAge = function(){
        console.log(this.age);
    }
}
複製代碼

應該這樣作

function Persion(name,age){
    this.name = name.toString();
    this.age = Number(age) || 0;
}
Persion.prototype.getName = function(){
    console.log(this.name);
};
Persion.prototype.getAge = function(){
    console.log(this.age);
};
複製代碼

如今統一將 callback 函數換成了 promise 。

相關文章
相關標籤/搜索