亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

你不知道的JavaScript上卷之作用域與閉包·讀書筆記

Raaabbit / 3221人閱讀

摘要:的分句會創(chuàng)建一個塊作用域,其聲明的變量僅在中有效。而閉包的神奇作用是阻止此事發(fā)生。依然持有對該作用域的引用,而這個引用就叫做閉包。當(dāng)然,無論使用何種方式對函數(shù)類型的值進(jìn)行傳遞,當(dāng)函數(shù)在別處被調(diào)用時都可以觀察到閉包。

date: 16.12.8 Thursday
第一章 作用域是什么

LHS:賦值操作的目標(biāo)是誰?
比如:

a = 2;

RHS:誰是賦值操作的源頭?
比如:

console.log(2);

作用域嵌套:遍歷嵌套作用域鏈的規(guī)則:引擎從當(dāng)前的執(zhí)行作用域開始查找變量,如果找不到,就向上一級繼續(xù)查找。當(dāng)?shù)诌_(dá)最外層的全局作用域時,無論是否找到都會停止。
異常:為什么區(qū)分LHS和RHS是一件重要的事情?
如果RHS查詢在所有嵌套的作用域中遍尋不到所需的變量,引擎就會拋出ReferenceError異常。
當(dāng)引擎在執(zhí)行LHS查詢時,如果在頂層作用域也無法找到目標(biāo)變量,全局作用域就會創(chuàng)建一個具有該名稱的變量,并將其返回給引擎。(非嚴(yán)格模式下)
如果RHS查詢找到了一個變量,但你嘗試對這個變量的值進(jìn)行不合理的操作,比如試圖對一個非函數(shù)類型的值進(jìn)行函數(shù)調(diào)用,或者引用null或undefined類型的值中的屬性,引擎會拋出TypeError.
ReferenceError同作用域判別失敗相關(guān),TypeError則代表作用域判別成功但對結(jié)果的操作是非法或不合理的。

第二章 詞法作用域

詞法作用域

詞法作用域就是定義在詞法階段的作用域。詞法作用域是由你在寫代碼時將變量和塊作用域?qū)懺谀睦飦頉Q定的,因此當(dāng)詞法分析器處理代碼時會保持作用域不變。
作用域查找會在找到第一個匹配的標(biāo)識符時停止:遮蔽效應(yīng)。(全局變量可以使用window.a來訪問)

欺騙詞法

eval():可以對一段包含一個或多個聲明的代碼字符串進(jìn)行演算,并借此來修改已經(jīng)存在的詞法作用域(在運行時)

function foo(str, a){
  eval( str );
  console.log(a,b);
}
var b = 2
foo("var b = 3;",1); //1,3

with關(guān)鍵字:本質(zhì)上是用過講一個對象的引用當(dāng)作作用域來處理,將對象的屬性當(dāng)作作用域中的標(biāo)識符來處理,從而創(chuàng)建了一個新的詞法作用域。

function foo(obj) {
  with (obj) {
    a = 2;
  }
}

var o1 = {
  a:3
};
var o2 = {
  b:3
};

foo(o1);
console.log( o1.a ); //2

foo(o2);
console.log( o2.a ); // undefined
console.log(a); //2---不好,a被泄露到全局作用域上了。
第三章 函數(shù)作用域和塊作用域

函數(shù)作用域的含義指,屬于這個函數(shù)的全部變量都可以在整個函數(shù)的范圍內(nèi)使用及復(fù)用。
規(guī)避沖突:

function foo() {
  function bar(a) {
    i = 3;  //不小心懂了for循環(huán)所屬作用域中的i
    console.log( a + i );
  }

  for (var i=0; i<10; i++) {
    bar( i*2 ); //進(jìn)入死循環(huán)。
  }
}
foo();

全局命名空間:當(dāng)程序加載了多個第三方庫時,如果他們沒有妥善的將內(nèi)部私有的函數(shù)或變量隱藏起來,就很容易產(chǎn)生沖突。

模塊管理

為了不污染作用域,可以使用包裝函數(shù)來解決這個問題。包裝函數(shù)的聲明以(function.. 開始。包裝函數(shù)會自動運行,是一個表達(dá)式。
IIFE:立即執(zhí)行函數(shù)表達(dá)式(Immediately Invoked Function Expression)

var a = 2;
(function foo(){
  var a = 3;
  console.log(a); //3
  })();    //防止了foo這個名稱污染了作用域

console.log(a); //2

匿名函數(shù)表達(dá)式的利弊

setTimeout( function() {
  console.log("+1s,WTF!")
  },100);

行內(nèi)函數(shù)表達(dá)式

setTimeout( function haveName() {
  console.log("+1s,WTF!")
  },100);

塊作用域:幾乎形同虛設(shè),只能靠開發(fā)者自覺了。在塊作用域內(nèi)聲明的變量都會屬于外部作用域。表面上看如此,但如果深入探究。
用with從對象中創(chuàng)建出的作用域僅在with聲明中而非外部作用域中有效。
try/catch的catch分句會創(chuàng)建一個塊作用域,其聲明的變量僅在catch中有效。
let關(guān)鍵字可以將變量綁定到所在的任意作用域中。let聲明附屬于一個新的作用域而不是當(dāng)前的函數(shù)作用域(也不屬于全局作用域)。

var foo = true;

if (foo) {
  let bar = foo * 2;
  bar = something(bar);
  console.log(bar);
}

console.log(bar); //ReferenceError
第四章 提升

先有雞還是先有蛋的問題:
Demo1:

a = 2;
var a;
console.log(a); //2

Demo2:

console.log(a); //undefined
var a = 2;

事實是先有蛋(聲明)后有雞(賦值)。實際處理如下:
demo1實際:

var a;
a = 2;
console.log(a);

demo2實際:

var a;
console.log(a);
a = 2;

只有聲明本身會被提升,而賦值或者其他運行邏輯會留在本地。

foo(); //TypeError
var foo = function bar() {
  // ...
};

demo3:

foo(); // TypeError
bar(); //ReferenceError

var foo = function bar(){
  // ...
}

上述代碼提升后實際理解形式:

var foo;
foo();
bar();

foo = function() {
  var bar = ..self..
  //...
}

提升過程函數(shù)優(yōu)先,然后才是變量:

foo(); //1
var foo;
function foo() {
  console.log(1);
}
foo = function() {
  console.log(2);
}

上述代碼會被理解成以下形式:

function foo() {
  console.log(1);
}
foo();
foo = function() {
  console.log(2);
};

盡管var foo出現(xiàn)在function foo()之前,但它是重復(fù)的聲明,因此被忽略。因為函數(shù)聲明會被提升到普通變量之前。
聲明本身會被提升,但包括函數(shù)表達(dá)式的賦值在內(nèi)的賦值操作并不會提升。

第五章 作用域閉包

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

function foo() {
  var a = 2;

  function bar() {
    console.log(a);
  }

  return bar;
}
var baz = foo();
baz(); //2 這就是閉包的效果

函數(shù)bar()詞法作用域能夠訪問foo()的內(nèi)部作用域。然后我們將bar()函數(shù)本身當(dāng)作一個值類型進(jìn)行傳遞。我們將bar所引用的函數(shù)對象本身當(dāng)作返回值。
在foo()執(zhí)行后,其返回值賦值給變量baz并調(diào)用baz(),實際上是通過不同的標(biāo)識符引用調(diào)用了內(nèi)部的函數(shù)bar()。
bar()顯然可以被正常執(zhí)行。但在這個例子中,它在自己定義的詞法作用域以外的地方執(zhí)行。
在foo()執(zhí)行后,通常會期待foo()的整個內(nèi)部作用域都被銷毀,因為引擎有垃圾回收器用來釋放不再使用的內(nèi)存空間。由于foo()的內(nèi)容不會再被使用,所以會被回收。
而閉包的神奇作用是阻止此事發(fā)生。事實上內(nèi)部作用域依舊存在,因為bar()本身在使用。
拜bar()所聲明的位置所賜,它擁有涵蓋foo()內(nèi)部作用域的閉包,使得該作用域能夠一直存活,以供bar()在之后任何時間進(jìn)行引用。
bar()依然持有對該作用域的引用,而這個引用就叫做閉包。

當(dāng)然,無論使用何種方式對函數(shù)類型的值進(jìn)行傳遞,當(dāng)函數(shù)在別處被調(diào)用時都可以觀察到閉包。

var fn;

function foo() {
  var a = 2;
  function baz() {
    console.log(a);
  }
  fn = baz; //將baz分配給全局變量
}

function bar() {
  fn();
}
foo();
bar(); //2

無論通過何種手段將內(nèi)部函數(shù)傳遞到所在的詞法作用域外,它都會持有對原始定義作用域的引用,無論在何處執(zhí)行這個函數(shù)都會使用閉包。
本質(zhì)上無論何時何地。如果將函數(shù)(訪問它們各自的詞法作用域)當(dāng)作第一級的值類型并到處傳遞,你就會看到閉包在這類函數(shù)中的應(yīng)用。(比如使用了回調(diào)函數(shù))

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

我們預(yù)期上述代碼依次輸出1,2,3,4,5。實際會輸出五次6。因為輸出顯示的是循環(huán)結(jié)束時i的值。
因為延遲函數(shù)的回調(diào)會在循環(huán)結(jié)束后才執(zhí)行。根據(jù)作用域的工作原理,實際情況是盡管循環(huán)中的五個函數(shù)是在各個迭代中分別定義的,但是它們都被封閉在一個共享的全局作用域中,因此實際上只有一個i.

修改如下:

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

再迭代中使用IIFE會為每個迭代都生成一個新的作用域,使得延遲函數(shù)的回調(diào)可以將新的作用域封閉在每個迭代內(nèi)部,每個迭代中都會含有一個具有正確值的變量供我們訪問。
將塊作用域和閉包聯(lián)手后:

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

模塊也是利用閉包的一個好方法:

function CoolModule() {
  var something = "cool";
  var another = [1,2,3];

  function doSomething() {
    console.log( something );
  }

  function doAnother() {
    console.log( another.join("!"));
  }
  return {
    doSomething: doSomething,
    doAnother: doAnother
  };
}

var foo = CoolModule();

foo.doSomething(); //cool
foo.doAnother(); //1!2!3

這就是JavaScript中最常用的模塊,doSomething()和doAnother()函數(shù)具有涵蓋模塊實例內(nèi)部作用域的閉包。
總結(jié)一下,模塊模式需要兩個必要條件:
1.必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次(每次調(diào)用都會創(chuàng)建一個新的模塊實例)。
2.封閉函數(shù)必須返回至少一個內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問或者修改私有的狀態(tài)。
也可以用單例模式來實現(xiàn),這種情況適用于只需要一個實例的情景:

var foo = (function CoolModule() {
  var something = "cool";
  var another = [1,2,3];

  function doSomething() {
    console.log( something );
  }

  function doAnother() {
    console.log( another.join("!"));
  }
  return {
    doSomething: doSomething,
    doAnother: doAnother
  };
})();
foo.doSomething();
foo.doAnother();

模塊模式也可以接受參數(shù),不再贅述。

最后總結(jié)一下閉包:
當(dāng)函數(shù)可以記住并訪問所在的詞法作用域,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行,這是就產(chǎn)生了閉包。

附錄A 動態(tài)作用域

JavaScript并不具有動態(tài)作用域,它只有詞法作用域。

function foo() {
  console.log(a);
}
function bar() {
  var a = 3;
  foo();
}

var a = 2;

bar();

實際上上述代碼輸出2,因為詞法作用域讓foo()中的a通過RHS引用到了全局作用域中的a,因此會輸出2.如果JavaScript有動態(tài)作用域,那么會輸出3,但是JavaScript并沒有動態(tài)作用域。

第一部分完
感謝作者Kyle Simpson和譯者趙望野,感謝自由和開源世界

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/88127.html

相關(guān)文章

  • 讀書筆記-你不知道JavaScript(上)

    摘要:比如程序會被分解為解析語法分析將詞法單元流轉(zhuǎn)換成一個由元素逐級嵌套所組成的代表了程序語法接口的書,又稱抽象語法樹。代碼生成將抽象語法樹轉(zhuǎn)換為機器能夠識別的指令。 showImg(https://segmentfault.com/img/remote/1460000009682106?w=640&h=280); 本文首發(fā)在我的個人博客:http://muyunyun.cn/ 《你不知道的...

    jzzlee 評論0 收藏0
  • 你不知道javascript筆記_作用域與閉包

    摘要:建筑的頂層代表全局作用域。實際的塊級作用域遠(yuǎn)不止如此塊級作用域函數(shù)作用域早期盛行的立即執(zhí)行函數(shù)就是為了形成塊級作用域,不污染全局。這便是閉包的特點吧經(jīng)典面試題下面的代碼輸出內(nèi)容答案個如何處理能夠輸出閉包方式方式下一篇你不知道的筆記 下一篇:《你不知道的javascript》筆記_this 寫在前面 這一系列的筆記是在《javascript高級程序設(shè)計》讀書筆記系列的升華版本,旨在將零碎...

    galaxy_robot 評論0 收藏0
  • 先有蛋還是先有雞?JavaScript 作用域與閉包探析

    摘要:而閉包的神奇之處正是可以阻止事情的發(fā)生。拜所聲明的位置所賜,它擁有涵蓋內(nèi)部作用域的閉包,使得該作用域能夠一直存活,以供在之后任何時間進(jìn)行引用。依然持有對該作用域的引用,而這個引用就叫閉包。 引子 先看一個問題,下面兩個代碼片段會輸出什么? // Snippet 1 a = 2; var a; console.log(a); // Snippet 2 console.log(a); v...

    elisa.yang 評論0 收藏0
  • 十分鐘快速了解《你不知道 JavaScript》(上卷

    摘要:最近剛剛看完了你不知道的上卷,對有了更進(jìn)一步的了解。你不知道的上卷由兩部分組成,第一部分是作用域和閉包,第二部分是和對象原型。附錄詞法這一章并沒有說明機制,只是介紹了中的箭頭函數(shù)引入的行為詞法。第章混合對象類類理論類的機制類的繼承混入。 最近剛剛看完了《你不知道的 JavaScript》上卷,對 JavaScript 有了更進(jìn)一步的了解。 《你不知道的 JavaScript》上卷由兩部...

    趙春朋 評論0 收藏0
  • 進(jìn)擊JavaScript(二)詞法作用域與作用域鏈

    摘要:一作用域域表示的就是范圍,即作用域,就是一個名字在什么地方可以使用,什么時候不能使用。概括的說作用域就是一套設(shè)計良好的規(guī)則來存儲變量,并且之后可以方便地找到這些變量。 一、作用域 域表示的就是范圍,即作用域,就是一個名字在什么地方可以使用,什么時候不能使用。想了解更多關(guān)于作用域的問題推薦閱讀《你不知道的JavaScript上卷》第一章(或第一部分),從編譯原理的角度說明什么是作用域。概...

    denson 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<