JavaScript物件導向~ 作用域和閉包

默默發表於2019-02-16

大名鼎鼎的作用域和閉包,面試經常會問到。閉包(closure)是Javascript語言的一個難點,也是它的特色。

宣告

理解閉包,先理解函式的執行過程。

程式碼在執行的過程中會有一個預解析的過程,也就是在程式碼的執行過程中,會先將程式碼讀取到記憶體中,檢查其是否有錯誤,然後將所有宣告在此進行標記,讓js解析器知道有這樣的一個名字,後面使用時便不會出現未定義的錯誤,這個標記的過程就是提升。

  • 變數的宣告
    var num;沒有與之對應的資料,僅僅是讓js解析器知道,你定義了一個num的變數。
  • 函式的宣告
    function foo(){};一個獨立的結構,沒有任何語句。首先是將函式名進行提升,讓js解析器知道有一個foo函式,接著是將函式名與函式體連線起來,注意這裡並不執行函式體。

程式碼演示:

var num = 10;
function foo(){
  console.log(num);
}
foo();

預解析的過程(變數提升,函式提升):

var num;
function foo(){
  console.log(num)
}
num = 10;
foo();

程式碼執行時,首先會執行 num = 10; 然後執行foo(),進行函式體,列印出num的值。此時的num訪問到的是全域性中定義的num,所以num的值為10.

作用域

以上程式碼涉及到作用域的問題,所謂的域,表示的是範圍,所以作用域表示的是作用範圍,也就是一個名字在什麼地方可以使用,在什麼地方不可使用。

1、詞法作用域

在js中,採用的是詞法作用域,詞法作用域是指在編寫程式碼的過程中體現出來的作用範圍,一旦程式碼寫好了,不用執行,作用範圍就確定好了。

Javascript的作用域無非就是兩種:全域性變數和區域性變數。

2、詞法作用域的規則

  • 函式允許訪問函式外的資料
  • 整個程式碼結構中只有函式可限定作用域
  • 作用域規則首先使用提升規則分析
  • 若當前作用域中有名字了,就不考慮外面的名字

3、作用域鏈

只有函式可以構成作用域結構。只要存在程式碼,就至少有一個作用域,即全域性作用域。凡是程式碼有函式,那麼這個函式就構成一個作用域,如果函式中還有函式,那麼在這個作用域中就又誕生一個作用域,那麼將這樣的所有作用域列出來,就可以有一個:函式內指向函式外的鏈式結構。

作用域鏈變數訪問規則:看變數在當前作用域中,是否有變數的定義與賦值,如果有,則直接使用;如果沒有,則到外面的作用域中檢視,如果有,則停止查詢,使用外面一層作用域中定義的變數或值,如果沒有,則繼續往外查詢,直到最外層的全域性,如果全域性也沒有定義,則會報錯: xx is not defined。

閉包

什麼是閉包

閉包,是一個具有封閉功能與包裹功能的一個結構或空間。在js中,函式可以構成閉包。因為函式在當前的作用域中是一個封閉的結構,具有封閉性;同時根據作用域規則,只允許函式內部訪問外部的資料,而外部無法訪問函式內部的資料,即函式具有封閉的對外不公開的特性,就像把一個東西包裹起來一樣,因此函式可以構成閉包。

有點難理解,簡單來說,就是能夠讀取其他函式內部變數的函式,再簡潔一點就是:定義在一個函式內部的函式。

閉包的基本結構

因為閉包不允許外界直接訪問,所以只能間接訪問函式內部的資料,獲得函式內部資料的使用權。

1、寫一個函式,函式內定義一個新函式,返回新函式,用新函式獲得函式內部的資料
function foo(){
  var num = 123;
  function func(){
    return num;
  }
  return func;
}
var f = foo();
var res1 = f();
var res2 = f(); 
// 此時,foo只呼叫了一次,不會再記憶體中重新建立一個函式,而通過f,可以訪問並獲取num的值,那麼呼叫f,即可獲得一樣的num值

改良:
function foo(){
  var num = 123;
  return function(){
    return num;
  }
}
var f = foo();
var res1 = f();
var res2 = f();

再改良:
var f = (function foo(){
  var num = 123;
  return function (){
    return num;
  }
})();
var res1 = f();
var res2 = f();
2、寫一個函式,函式內定義一個物件,物件繫結一個或多個函式(方法),返回物件,利用物件的方法訪問函式內部的資料
function func(){
  var num1 = Math.random();
  var num2 = Math.random();
  return {
    num1: function(){
      return num1;
    },
    num2: function(){
      return num2;
    }
  }
}
var p = func();
console.log(p.num1());
console.log(p.num1());// 這兩個訪問到的是同一個隨機數

閉包的基本用法

如上面程式碼演示的那樣,閉包可以通過返回函式來間接訪問到函式內的資料,這樣,閉包可以實現具有私有訪問空間的函式,保護私有的資料。另一方面,可以幫助其他物件讀取到函式內部的變數

閉包的效能問題

函式定義的變數會在函式執行結束後自動回收,但是因為閉包結構引出的資料經常會被外界所引用,這些資料將不會被回收,因此過多的閉包會消耗記憶體資源,影響效能。所以要謹慎使用閉包,可以在使用閉包時,如果不再使用某些變數了,一定要賦值一個null。

在ES6中,提出來物件代理概念,在代理層運算元據而不是直接操作原資料。

相關文章