JavaScript面向對象~ 做用域和閉包

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

聲明

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

代碼在執行的過程當中會有一個預解析的過程,也就是在代碼的執行過程當中,會先將代碼讀取到內存中,檢查其是否有錯誤,而後將全部聲明在此進行標記,讓js解析器知道有這樣的一個名字,後面使用時便不會出現未定義的錯誤,這個標記的過程就是提高。dom

  • 變量的聲明
    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.代理

做用域

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

一、詞法做用域

在js中,採用的是詞法做用域,詞法做用域是指在編寫代碼的過程當中體現出來的做用範圍,一旦代碼寫好了,不用執行,做用範圍就肯定好了。對象

Javascript的做用域無非就是兩種:全局變量和局部變量。ip

二、詞法做用域的規則

  • 函數容許訪問函數外的數據
  • 整個代碼結構中只有函數可限定做用域
  • 做用域規則首先使用提高規則分析
  • 若當前做用域中有名字了,就不考慮外面的名字

三、做用域鏈

只有函數能夠構成做用域結構。只要存在代碼,就至少有一個做用域,即全局做用域。凡是代碼有函數,那麼這個函數就構成一個做用域,若是函數中還有函數,那麼在這個做用域中就又誕生一個做用域,那麼將這樣的全部做用域列出來,就能夠有一個:函數內指向函數外的鏈式結構。內存

做用域鏈變量訪問規則:看變量在當前做用域中,是否有變量的定義與賦值,若是有,則直接使用;若是沒有,則到外面的做用域中查看,若是有,則中止查找,使用外面一層做用域中定義的變量或值,若是沒有,則繼續往外查找,直到最外層的全局,若是全局也沒有定義,則會報錯: xx is not defined。

閉包

什麼是閉包

閉包,是一個具備封閉功能與包裹功能的一個結構或空間。在js中,函數能夠構成閉包。由於函數在當前的做用域中是一個封閉的結構,具備封閉性;同時根據做用域規則,只容許函數內部訪問外部的數據,而外部沒法訪問函數內部的數據,即函數具備封閉的對外不公開的特性,就像把一個東西包裹起來同樣,所以函數能夠構成閉包。

有點難理解,簡單來講,就是可以讀取其餘函數內部變量的函數,再簡潔一點就是:定義在一個函數內部的函數。

閉包的基本結構

由於閉包不容許外界直接訪問,因此只能間接訪問函數內部的數據,得到函數內部數據的使用權。

一、寫一個函數,函數內定義一個新函數,返回新函數,用新函數得到函數內部的數據
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();
二、寫一個函數,函數內定義一個對象,對象綁定一個或多個函數(方法),返回對象,利用對象的方法訪問函數內部的數據
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中,提出來對象代理概念,在代理層操做數據而不是直接操做原數據。

相關文章
相關標籤/搜索