国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

你應(yīng)該要知道的作用域和閉包

JouyPub / 3498人閱讀

摘要:寫在前面對于一個前端開發(fā)者,應(yīng)該沒有不知道作用域的。欺騙詞法作用域有兩個機制可以欺騙詞法作用域和。關(guān)于你不知道的的第一部分作用域和閉包已經(jīng)結(jié)束了,但是,更新不會就此止住未完待續(xù)

這是《你不知道的JavaScript》的第一部分。

本系列持續(xù)更新中,Github 地址請查閱這里。

寫在前面

對于一個前端開發(fā)者,應(yīng)該沒有不知道作用域的。它是一個既簡單有復(fù)雜的概念,簡單到每行代碼都有它的影子,復(fù)雜到寫過很多的代碼依然不一定能完全理解。

最近在看《你不知道的JavaScript》,看完之后不寫點什么,好像看的意義就不大了。今天就花點時間,從最簡單又復(fù)雜的作用域開始縷一縷。

先來份關(guān)于《你不知道的JavaScript》第一部分作用域和閉包的目錄感受一下:

第1章 作用域是什么

第2章 詞法作用域

第3章 函數(shù)作用域和塊作用域

第4章 提升

第5章 作用域閉包

真正開始寫的時候發(fā)現(xiàn)好像并沒有什么好寫了,因為這并不是很難的概念,但是要問自己閉包到底是什么,到底有什么用,什么時候場景用的時候,好像又有點模糊了?閉包這個經(jīng)常寫的代碼,居然好像并沒有徹底地理解。

開始之前

先問自己幾個問題

你真的理解了作用域嗎?

什么是動態(tài)作用域?什么是詞法作用域?

閉包到底是什么?閉包的應(yīng)用場景有哪些?

怎么實現(xiàn)一個簡單的模塊依賴加載器?

JS是一門解釋型語言,它的編譯過程不是發(fā)生在構(gòu)建之前,那么聲明提升為什么會發(fā)生呢?

如果這些你都知道的話,或許這篇內(nèi)容并不是很需要看了,如果還有一點困惑,希望它能對你有點幫助。

作用域是什么

在講作用域之前,我們先來看看什么是編譯。在傳統(tǒng)編譯語言的流程中,編譯分為3步:

分詞/詞法分析:將由字符組成的字符串分解成有意義的代碼塊(詞法單元)

解析/語法分析:將詞法單元流轉(zhuǎn)換成一個由元素逐級嵌套所組成的代表程序語法機構(gòu)的樹(抽象語法樹,AST)

代碼生成:將AST轉(zhuǎn)換成可執(zhí)行代碼

JS是一門解釋型語言,它的編譯過程不是發(fā)生在構(gòu)建之前,大部分情況下編譯發(fā)生在代碼執(zhí)行前的幾微秒(甚至更短)的時間內(nèi),所以在作用域的背后,JS引擎用盡了各種辦法來保證性能最佳。

簡單了解了編譯后,我們再來看看作用域是什么:

作用域負責(zé)收集并維護有所有聲明的標(biāo)識符(變量)組成的一系列查詢,并實施一套非常嚴(yán)格的規(guī)則,確定當(dāng)前執(zhí)行的代碼對這些標(biāo)識符的訪問權(quán)限

上面的話不是很好理解,我們來分解一下下面的代碼:

var a = 2;
var b = a;

熟悉JS的同學(xué)都知道,包括變量和函數(shù)在內(nèi)的所有聲明都會有一個提升的過程,即首先被處理,但是賦值不會提升。所以上面的代碼會被如下處理:

var a;
var b;

a = 2;
b = a;

當(dāng)編譯器遇到var a時,編譯器會詢問作用域是否存在名稱為a的變量。如果存在,則忽略聲明,繼續(xù)編譯;如果不存在,則聲明一個新變量,命名a。(var b;同理)

當(dāng)遇到a = 2時,引擎會詢問作用域,當(dāng)前作用域中是否存在該變量,如果找不到,就向上一級查找,當(dāng)?shù)诌_最外層的全局作用域時,無論找到還是沒找到,查找都會停止,找到了就賦值2給它,沒找到就拋出異常。

其中,a = 2這一過程中對a的查找被稱為LHS查詢,在b = a這一句中,引擎會查找變量a的值和變量b的容器,并將a的值賦值給b,這一查找變量a的值被稱為RHS查詢。

詞法作用域

作用域有兩種工作模式,一種詞法作用域,一種動態(tài)作用域。詞法作用域(也叫靜態(tài)作用域)就是在詞法階段的作用域,詞法分析階段就確定了,不會改變。JS采用的就是詞法作用域,但是可以通過一些欺騙詞法作用域的方法,在詞法分析過后依然可以修改作用域。

詞法作用域與動態(tài)作用域

我們先來看看詞法作用域與動態(tài)作用域的區(qū)別(因為JS采用的是詞法作用域,所以對動態(tài)作用域不做過多介紹):

var a = 2;

function foo () {
  console.log(a); // 會輸出2還是3?
}

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

bar();

熟悉JS的同學(xué)應(yīng)該都知道通過RHS引用到了全局作用域中的a,所以輸出2。但是如果JS是動態(tài)作用域,情況就不一樣了,當(dāng)foo()無法找到a的變量引用時,會在調(diào)用foo()的地方查找a,而不是在嵌套的詞法作用域上查找,所以會輸出3。

下面我們來看看什么是欺騙詞法。

欺騙詞法作用域

JS有兩個機制可以欺騙詞法作用域:eval()和with。大多數(shù)情況下,它們是不被推薦使用的,因為欺騙詞法作用域?qū)е乱鏌o法在編譯時對作用域查找進行優(yōu)化,所以會導(dǎo)致性能下降;另外,在嚴(yán)格模式下,with被完全禁止使用,間接或非安全的使用eval也被禁止。

eval()這個方法接受一個字符串為參數(shù),并將其中的內(nèi)容視為好像在書寫時就存在于程序中這個位置的代碼。

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

可以看到 eval() 調(diào)用了 var b =3; 導(dǎo)致修改了原本的作用域。

with 通常被當(dāng)作重復(fù)引用同一個對象中的多個屬性的快捷方式。

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被泄漏到全局作用域上了!

with 聲明實際上是根據(jù)你傳遞給它的對象憑空創(chuàng)建了一個全新的詞法作用域

聲明提升

前面有個例子說到了聲明提升,我們已經(jīng)知道引擎會在解釋 JavaScript 代碼之前首先對其進行編譯。編譯階段中的一部分工作就是找到所有的聲明,并用合適的作用域?qū)⑺鼈冴P(guān)聯(lián)起來,這也正詞法作用域的核心內(nèi)容。聲明提升在代碼中比較常見,相信面試過的朋友肯定對它非常熟悉了,但是它也有幾個必須要注意的點:

每個作用域都會進行提升操作,聲明會被提升到所在作用域的頂部(只有聲明會被提升,賦值或其他運行邏輯會留在原地)

并非所有的聲明都會被提升,不同聲明提升的權(quán)重也不同,具體來說函數(shù)聲明會被提升,函數(shù)表達式不會被提升(就算是有名稱的函數(shù)表達式也不會提升),通過var定義的變量會提升,而let和const進行的聲明不會提升

函數(shù)聲明和變量聲明都會被提升。但是一個值得注意的細節(jié)也就是函數(shù)會首先被提升,然后才是變量,也就是說如果一個變量聲明和一個函數(shù)聲明同名,那么就算在語句順序上變量聲明在前,該標(biāo)識符還是會指向相關(guān)函數(shù)

如果變量或函數(shù)有重復(fù)聲明以會第一次聲明為主,但是后面的函數(shù)聲明還是可以覆蓋前面的

閉包

閉包到底是什么?

閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)

內(nèi)部函數(shù)總是可以訪問其所在的外部函數(shù)中聲明的參數(shù)和變量,即使在其外部函數(shù)被返回(壽命終結(jié))了之后

閉包這個詞的意思是封閉,將外部作用域中的局部變量封閉起來的函數(shù)對象稱為閉包。被封閉起來的變量與封閉它的函數(shù)對象有相同的生命周期

關(guān)于閉包的解釋,網(wǎng)上有很多,《你不知道的JavaScript》的作者也給出了他的定義:

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

看起來不是那么生動的解釋,仔細看看好像也不是很難理解,不過,作為一個程序員,代碼才是王道

function foo () {
  var a = 2;
  function bar () {
    console.log(a);
  }
  return bar;
}
var baz = foo();
baz();

函數(shù)bar()的詞法作用域可以訪問foo()的內(nèi)部作用域,foo()執(zhí)行之后,bar()依然持有對foo()內(nèi)部作用域的引用(也就不會被垃圾回收機制回收),bar()對該作用域的這個引用就被叫做閉包。

再來看看下面這段代碼,有沒有很熟悉的感覺,看過一些面試題的朋友應(yīng)該都不會陌生吧,答案是每隔一秒的頻率輸出五次6

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

瀏覽器運行機制,任務(wù)隊列之類的我們就不討論了,我們來看看怎么改進,從閉包的角度出現(xiàn)讓它輸出1~5

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

上面的代碼可以嗎?是的,不行,我們只是封閉了什么都沒有的空作用域中,依然會向上查找全局的i。怎么實現(xiàn)?寫了這么多,我的任務(wù)完成了,輪到你動一下腦瓜子了。

模塊依賴加載器

require(["a", "b"], callback)這樣的模塊加載方式有沒有勾起你老人家什么回憶呢?作為一個年輕人,ES6的import大法還是比較適合我,不過前輩當(dāng)時的先進經(jīng)驗還是有很多可以學(xué)習(xí)的地方的,直接貼代碼了

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
  }
})();

上面實現(xiàn)了一個簡單的模塊加載器,下面是使用它來定義模塊

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

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

  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("hippo")); // Let me introduce: hippo 
foo.awesome(); // LET ME INTRODUCE: HIPPO
寫在最后

不知不覺,篇幅已經(jīng)不短,怎么總結(jié)的更精煉確實是一個技術(shù)活,我得好好學(xué)學(xué)才行。本篇主要從編譯、詞法作用域、聲明提升的角度對JS的作用域進行了介紹,并慢慢打開了閉包的大門,最后展示了一個簡單的模塊加載器的代碼。

關(guān)于《你不知道的JavaScript》的第一部分——作用域和閉包已經(jīng)結(jié)束了,但是,更新不會就此止住

未完待續(xù)...

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

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/94791.html

相關(guān)文章

  • 弄懂JavaScript作用域和閉包

    摘要:關(guān)于本書,我會寫好幾篇讀書筆記用以記錄那些讓我恍然大悟的瞬間,本文是第一篇弄懂的作用域和閉包。作用域也可以看做是一套依據(jù)名稱查找變量的規(guī)則。聲明實際上是根據(jù)你傳遞給它的對象憑空創(chuàng)建了一個全新的詞法作用域。 《你不知道的JavaScript》真的是一本好書,閱讀這本書,我有多次哦,原來是這樣的感覺,以前自以為理解了(其實并非真的理解)的概念,這一次真的理解得更加透徹了。關(guān)于本書,我會寫好...

    everfly 評論0 收藏0
  • javascript作用域和閉包之我見

    摘要:查詢是在作用域鏈中,一級級的往上查找該變量的引用。作用域和作用域鏈作用域的概念,應(yīng)該兩張圖幾句話就能解釋吧。這個建筑代表程序中的嵌套作用域鏈。一層嵌一層的作用域形成了作用域鏈,變量在作用域鏈中的函數(shù)內(nèi)得到了自己的定義。 javascript作用域和閉包之我見 看了《你不知道的JavaScript(上卷)》的第一部分——作用域和閉包,感受頗深,遂寫一篇讀書筆記加深印象。路過的大牛歡迎指點...

    SoapEye 評論0 收藏0
  • JavaScript面向?qū)ο髜 作用域和閉包

    摘要:大名鼎鼎的作用域和閉包,面試經(jīng)常會問到。聲明理解閉包,先理解函數(shù)的執(zhí)行過程。閉包的基本結(jié)構(gòu)因為閉包不允許外界直接訪問,所以只能間接訪問函數(shù)內(nèi)部的數(shù)據(jù),獲得函數(shù)內(nèi)部數(shù)據(jù)的使用權(quán)。 大名鼎鼎的作用域和閉包,面試經(jīng)常會問到。閉包(closure)是Javascript語言的一個難點,也是它的特色。 聲明 理解閉包,先理解函數(shù)的執(zhí)行過程。 代碼在執(zhí)行的過程中會有一個預(yù)解析的過程,也就是在代碼的...

    WilsonLiu95 評論0 收藏0
  • [JS]《知道Javascript·上》——詞法作用域和閉包

    摘要:吐槽一下,閉包這個詞的翻譯真是有很大的誤解性啊要說閉包,要先說下詞法作用域。閉包兩個作用通過閉包,在外部環(huán)境訪問內(nèi)部環(huán)境的變量。閉包使得函數(shù)可以繼續(xù)訪問定義時的詞法作用域。 閉包是真的讓人頭暈啊,看了很久還是覺得很模糊。只能把目前自己的一些理解先寫下來,這其中必定包含著一些錯誤,待日后有更深刻的理解時再作更改。 吐槽一下,閉包這個詞的翻譯真是有很大的誤解性啊…… 要說閉包,要先說下詞法...

    guqiu 評論0 收藏0
  • 重讀知道JS (上) 第一節(jié)三章

    摘要:如果是聲明中的第一個詞,那么就是一個函數(shù)聲明,否則就是一個函數(shù)表達式。給函數(shù)表達式指定一個函數(shù)名可以有效的解決以上問題。始終給函數(shù)表達式命名是一個最佳實踐。也有開發(fā)者干脆關(guān)閉了靜態(tài)檢查工具對重復(fù)變量名的檢查。 你不知道的JS(上卷)筆記 你不知道的 JavaScript JavaScript 既是一門充滿吸引力、簡單易用的語言,又是一門具有許多復(fù)雜微妙技術(shù)的語言,即使是經(jīng)驗豐富的 Ja...

    lavor 評論0 收藏0

發(fā)表評論

0條評論

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