你不知道的JS之-作用域和閉包

作用域是什么

理解作用域

  • 引擎
    • 從頭到尾負(fù)責(zé)整個(gè)JavaScript程序的編譯和執(zhí)行過(guò)程
  • 編譯器
    • 負(fù)責(zé)語(yǔ)法分析及代碼生成
  • 作用域
    • 負(fù)責(zé)收集并維護(hù)由所有聲明的標(biāo)識(shí)符(變量)組成的一系列查詢,并實(shí)施一套非常嚴(yán)格的規(guī)則,確定當(dāng)前執(zhí)行的代碼對(duì)這些標(biāo)識(shí)符有訪問(wèn)權(quán)限。
image

作用域嵌套

當(dāng)一個(gè)塊或者函數(shù)嵌套在另一個(gè)函數(shù)或函數(shù)中時(shí),就發(fā)生了作用域嵌套。

遍歷嵌套作用域規(guī)則:引擎從當(dāng)前的執(zhí)行作用域開始查找變量,如果找不到,就向上一級(jí)繼續(xù)查找。直到抵達(dá)最外層的全局作用域, 無(wú)論找到還是沒(méi)找到,查找過(guò)程都會(huì)停止。

小結(jié)

作用域是一套規(guī)則,用于確定在何處以及如何查找變量(標(biāo)志符)。
如果查找目的是對(duì)變量進(jìn)行賦值,就是執(zhí)行LHS查詢
如果查找目的是獲取變量的值,就是執(zhí)行RHS查詢

詞法作用域

作用域主要兩種工作模式:詞法作用域和動(dòng)態(tài)作用域

詞法階段

  • 大部分標(biāo)準(zhǔn)語(yǔ)言編譯器的第一個(gè)工作階段叫做詞法化(也叫單詞化)。
  • 簡(jiǎn)單的說(shuō), 詞法作用域就是定義在詞法階段的作用域。換句話說(shuō),詞法作用域是由你在寫代碼的時(shí)候?qū)⒆兞亢蛪K作用域?qū)懺谀睦飦?lái)決定的,因此當(dāng)詞法分析器處理代碼時(shí)會(huì)保持作用域不變。
  • 作用域查找會(huì)在找到第一個(gè)匹配的標(biāo)識(shí)符時(shí)停止。在多層的嵌套作用域中可以定義同名的標(biāo)識(shí)符,叫做“遮蔽效應(yīng)”
  • 作用域查找始終是從運(yùn)行時(shí)所處的最內(nèi)部作用域開始,逐級(jí)向外或者向上查找, 知道遇見第一個(gè)匹配的標(biāo)識(shí)符為止。
  • 全局變量會(huì)自動(dòng)成為全局對(duì)象(例如瀏覽器中的window對(duì)象)的屬性,因此可以不直接通過(guò)全局對(duì)象的詞法名稱, 而是間接的通過(guò)對(duì)全局對(duì)象屬性的引用來(lái)對(duì)其進(jìn)行訪問(wèn)。
    例如window.a。通過(guò)這種技術(shù)可以訪問(wèn)那些被同名變量鎖遮蔽的全局變量。但非全局變量如果被遮蔽了,無(wú)論如何都無(wú)法被訪問(wèn)到。
  • 無(wú)論函數(shù)在哪里被調(diào)用,也無(wú)論它如何被調(diào)用,它的詞法作用域都只由函數(shù)被聲明時(shí)所處的位置決定。

小結(jié)

詞法作用域意味著作用域是由代碼書寫時(shí)候函數(shù)聲明的位置來(lái)決定的。

函數(shù)作用域和塊作用域

函數(shù)中的作用域

函數(shù)作用域是指,屬于這個(gè)函數(shù)的全部變量都可以在整個(gè)函數(shù)的范圍內(nèi)使用以及復(fù)用(事實(shí)上在嵌套的作用域中也可以使用)。

隱藏內(nèi)部實(shí)現(xiàn)

不應(yīng)該這樣:

function doSomething(a) {
 b = a + doSomethingElse(a * 2);

 console.log(b * 3);
}

function doSomethingElse(a) {
 return a - 1;
}

var b;

doSomething(2);

而是應(yīng)該這樣, 隱藏變量:

function doSomething(a) {
 function doSomethingElse(a) {
   return a - 1;
 }
 var b;

 b = a + doSomethingElse(a * 2);

 console.log(b * 3);
}

doSomething(2);

規(guī)避沖突

“隱藏”作用域中的變量和函數(shù)所帶來(lái)的另一個(gè)好處,是可以避免同名標(biāo)識(shí)符之間的沖突,兩個(gè)標(biāo)識(shí)符可能具有相同的名字但是用途卻不一樣,無(wú)意間可能造成命名沖突。 沖突會(huì)導(dǎo)致變量的值被意外覆蓋。

函數(shù)作用域

匿名和具名

例如如下函數(shù):

setTimeout(function() {
 console.log('I waited 1 second');
 
}, 1000);

這叫做匿名函數(shù)表達(dá)式。
匿名函數(shù)表達(dá)式書寫起來(lái)簡(jiǎn)單快捷,但是有幾個(gè)缺點(diǎn):

  1. 匿名函數(shù)在棧追蹤中不會(huì)顯示出有意義的函數(shù)名,使得調(diào)試很困難
  2. 如果沒(méi)有函數(shù)名,當(dāng)函數(shù)需要引用自身時(shí)只能使用已經(jīng)過(guò)期的arguments.callee引用。
  3. 匿名函數(shù)省略了對(duì)于代碼可讀性/可理解性很重要的函數(shù)名。

行內(nèi)函數(shù)表達(dá)式非常強(qiáng)大且有用----匿名和具名之間的區(qū)別并不會(huì)對(duì)這一點(diǎn)有任何影響。給函數(shù)表達(dá)式指定一個(gè)函數(shù)名可以有效解決以上問(wèn)題。所以,最好始終給函數(shù)表達(dá)式命名。

setTimeout(function timeoutHandler() { // 有名字了
  console.log('I waited 1 second');
  
}, 1000);

立即執(zhí)行函數(shù)表達(dá)式

(function(){})()(function(){}())

提升

  • 函數(shù)會(huì)首先別提升,然后才是變量。
  • 出現(xiàn)在后面的函數(shù)聲明還是可以覆蓋前面的。
  • 一個(gè)普通塊內(nèi)部的函數(shù)聲明通常會(huì)被提升到所在作用域的頂部。

總結(jié)

  • 所有的聲明(變量和函數(shù))都會(huì)被“移動(dòng)”到各自作用域的最頂端, 這個(gè)過(guò)程被稱為 提升。
  • 聲明本身會(huì)被提升,而包含函數(shù)表達(dá)式的賦值在內(nèi)的賦值操作并不會(huì)被提升。
  • 要注意避免重復(fù)聲明,特別是當(dāng)普通的var聲明和函數(shù)聲明混合在一起的時(shí)候, 否則會(huì)引起很多危險(xiǎn)的問(wèn)題。

作用域閉包

定義

當(dāng)函數(shù)可以記住并訪問(wèn)所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)是在所在詞法作用域以外被執(zhí)行,這個(gè)引用,就叫做閉包。

  • 無(wú)論通過(guò)何種手段將內(nèi)部函數(shù)傳遞到所在詞法作用域以外,它都會(huì)持有對(duì)原始定義作用域的引用,無(wú)論在何處執(zhí)行這個(gè)函數(shù)都會(huì)使用閉包
  • 本質(zhì)上講,無(wú)論何時(shí)何地,如果將函數(shù)當(dāng)作第一級(jí)的值類型并到處傳遞,你就會(huì)看到閉包在這些函數(shù)中的應(yīng)用。
  • 例如在一些定時(shí)器、事件監(jiān)聽器、Ajax請(qǐng)求等,只要使用了回調(diào)函數(shù),實(shí)際上就是在使用閉包

循環(huán)和閉包

  • let聲明可以用來(lái)劫持塊作用域,并且在這個(gè)作用域中聲明一個(gè)變量。
  • for循環(huán)頭部的let聲明還會(huì)有一個(gè)特殊的行為。這個(gè)行為指出變量在循環(huán)過(guò)程中不止被聲明一次,每次迭代都會(huì)聲明。隨后每個(gè)迭代都會(huì)使用上一個(gè)迭代結(jié)束時(shí)的值來(lái)初始化這個(gè)變量。

模塊

模塊模式需要具備兩個(gè)必要條件:

  1. 必須有外部的封閉函數(shù),該函數(shù)必須至少別調(diào)用一次(每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的模塊實(shí)例)
  2. 封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問(wèn)或者修改私有得狀態(tài)。

一個(gè)具有函數(shù)屬性的對(duì)系那個(gè)本身并不是真正的模塊。從方便觀察的角度看,一個(gè)從函數(shù)調(diào)用鎖返回的,只有數(shù)據(jù)屬性而沒(méi)有閉包函數(shù)得對(duì)象并不是真正的模塊。

現(xiàn)代的模塊機(jī)制

大多數(shù)模塊依賴加載器/管理器本質(zhì)上都是將這種模塊定義封裝進(jìn)一個(gè)友好的API。

var MyModules = (function Manager() {
  var modules = {};

  function define(name, deps, impl) {
    for (var i = 0; i < deps.length; i++) {
      deps[i] = modules[deps[i]];
    }
    modules[name] = impl.apply(impl, deps);
  }

  function get(name) {
    return modules[name];
  }

  return {
    define: define,
    get: get
  };
})();

MyModules.define('bar', [], function() {
  function hello(who) {
    return 'let me introduce: ' + who;
  }

  return {
    hello: hello
  };
});

MyModules.define('foo', ['bar'], function(bar) {
  var hungry = 'xiaofan';

  function awesome() {
    console.log(bar.hello(hungry).toUpperCase());
  }

  return {
    awesome: awesome
  };
});

var bar = MyModules.get('bar');
var foo = MyModules.get('foo');

console.log(bar.hello('xiaofan'));

foo.awesome();

foobar模塊都是通過(guò)一個(gè)返回公共API的函數(shù)來(lái)定義的。foo甚至接受bar的實(shí)例作為依賴參數(shù),并能響相應(yīng)的使用它。

總結(jié)

當(dāng)函數(shù)可以記住并訪問(wèn)所在的詞法作用域,即使函數(shù)是在當(dāng)前詞法作用域以外執(zhí)行,這時(shí)就產(chǎn)生了閉包。

模塊有兩個(gè)主要特征:

  1. 為創(chuàng)建內(nèi)部作用域而調(diào)用了一個(gè)包裝函數(shù)
  2. 包裝函數(shù)的返回值必須包含至少一個(gè)對(duì)內(nèi)部函數(shù)的引用,這樣就會(huì)創(chuàng)建涵蓋整個(gè)包裝函數(shù)內(nèi)部作用域的閉包
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • You don't KnowJS 引語(yǔ):你不懂的JS這本書?github上已經(jīng)有了7w的star最近也是張野大大給...
    Sleet閱讀 666評(píng)論 0 0
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 5,715評(píng)論 16 88
  • 《你不知道的JavaScript》真的是一本好書,閱讀這本書,我有多次“哦,原來(lái)是這樣”的感覺,以前自以為理解了(...
    然并阮閱讀 662評(píng)論 2 9
  • 作用域閉包當(dāng)函數(shù)可以記住并訪問(wèn)所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)式在當(dāng)前詞法作用域之外執(zhí)行。下面用一些代碼...
    xpwei閱讀 461評(píng)論 0 2
  • 擇一城終老,哪里好呢?當(dāng)?shù)氐脑捯苈牰?,生活消費(fèi)不能高,尤其是房租要便宜,然后還要能找到生意做,不僅能養(yǎng)活自己,關(guān)...
    清淼人生閱讀 237評(píng)論 0 0

友情鏈接更多精彩內(nèi)容