最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript作用域與作用域鏈優(yōu)化方式

 更新時間:2022年07月22日 09:33:02   作者:??Hardy_?  
這篇文章主要介紹了JavaScript作用域與作用域鏈優(yōu)化方式,圍繞主題展開JavaScript作用域以及作用域鏈的那些事情,以及針對它們的一些我們在代碼中優(yōu)化小技巧,需要的朋友可以參考一下

前言

作用域和作用域鏈是所有JavaScript開發(fā)人員每天都要接觸和應用的內(nèi)容。不管是面試中的作用域鏈的面試考察,還是日常代碼研發(fā)中變量與作用域鏈的構建,它的身影幾乎無處不在。它就像一頂優(yōu)秀廚師的廚師帽,只要我們走進廚房,我們就要將它整理好,套在頭上。沒有它整潔干凈的戴在頭上,你就不是一名好的JavaScript工程師。

其實,作為一名前端工程師,我也曾經(jīng)疑惑過:基本上所有的計算機語言都具有作用域的概念,但是為何JavaScript開發(fā)人員總是對作用域這個概念執(zhí)著不已?直到,我多次在編寫代碼過程中遇到涉及到作用域的問題后,我才漸漸了解這個問題并去仔細研究。

而這篇文章,就是想要和大家聊聊有關JavaScript作用域以及作用域鏈的那些事情,以及針對它們的一些我們在代碼中優(yōu)化小技巧。

內(nèi)容

對于幾乎所有的編程語言來說,最基本的功能之一,就是儲存變量當中的值并且能在之后對這個值進行訪問和修改。這種能力的引入,是程序的狀態(tài)存在的基礎。但是,能力的引入需要我們解決幾個問題,例如:變量存儲在哪里?以何種形式存儲?需要讀取和修改變量的時候,以什么方式獲取到這個變量?

很明顯,為了解決這些問題,我們需要一套設計良好的規(guī)則來存儲變量,并且之后可以方便的找到這些變量。與此同時,整套完整規(guī)則的設計就會衍生出額外規(guī)則概念。而作用域,就是這套規(guī)則下衍生出來的概念。

作用域

我們可以把作用域理解為上面講到的這套規(guī)則下的限定范圍。作用域的職責是,在這段限定范圍中根據(jù)這套設計好的規(guī)則存儲所聲明的變量,并且提供修改該變量的支持。在變量的訪問權限安全上,作用域還承擔著保護當前作用域內(nèi)的變量不被外部作用域訪問的權限保護作用。

通過類比,我們可以把作用域想象成一個氣泡。在這個氣泡里所聲明的變量成員被包含在其中。每個氣泡都配備有一位有原則的管家,將所有的成員管理起來,并針對他們聲明的位置和要求對它們提供保護。當氣泡中代碼語句想要訪問和修改變量成員時,管家會結合變量成員的要求關聯(lián)對應訪問和修改操作。

隨著ECMAScript標準的不斷發(fā)展和完善,JavaScript目前存在著四種作用域類型:

  • 全局作用域(Global Scope): JavaScript語言環(huán)境的最頂級作用域,在語言環(huán)境初始化時創(chuàng)建。
  • 模塊作用域(Module Scope): 由ECMAScript模塊標準(ES Module)引入,在解析ECMAScript模塊時創(chuàng)建。
  • 函數(shù)作用域(Function Scope): 在函數(shù)聲明function() {}或者() => {}時創(chuàng)建。
  • 塊級作用域(Block Scope): 由ECMAScript2015的變量聲明標識符letconst引入,在使用這兩者進行變量聲明時,根據(jù)最近的一對花括號{}創(chuàng)建。
/* 全局作用域 start,JavaScript語言環(huán)境初始化時就被創(chuàng)建 */
/* 模塊作用域 start,作為ES Module解析和執(zhí)行時被創(chuàng)建 */
let name = 'Wu';
{
  /* 塊級作用域 start,const進行變量聲明在最近的花括號{}內(nèi)創(chuàng)建 */
  const prefix = Hardy;
  name = prefix + name;
  /* 塊級作用域 end */
}
export function sayMyName(myName) {
  /* 函數(shù)作用域 start,函數(shù)聲明時自動創(chuàng)建,初始化默認包含函數(shù)的形參變量 */
  if (!myName) {
    /* 塊級作用域 start */
    const noNameAnswer = 'Sorry!';
    console.log(noNameAnswer);
    return;
    /* 塊級作用域 end */
  }
  const wordPrifix = 'Hi! My Name is ';
  const answer = wordPrifix + myName + '.';
  console.log(answer);
  /* 函數(shù)作用域 end */
}
/* 模塊作用域 end */
/* 全局作用域 end */

作用域的嵌套

作用域在使用上具有嵌套特征。一個作用域能夠在自身內(nèi)部創(chuàng)建一個新作用域從而形成內(nèi)部和外部作用域的嵌套關系。

全局作用域作為JavaScript的初始作用域,是所有其他作用域最外層的作用域。另外,每一個ES Module都具有模塊自己的頂級作用域(top-level scope),模塊中的頂級作用域變量和函數(shù)都包含在這個模塊頂級作用域中,而模塊作用域的外部作用域是全局作用域。而函數(shù)作用域和塊級作用域則相對比較靈活,可以相互嵌套。

作用域的一些實現(xiàn)細節(jié)

在JavaScript中,每一個函數(shù)、代碼塊{...}以及script腳本被運行前,都會有一個相對應的稱為詞法環(huán)境(Lexical Environment) 的內(nèi)部關聯(lián)對象被創(chuàng)建。

詞法環(huán)境由兩部分組成:

  • 環(huán)境記錄(Environment Record):一個存儲所有局部變量作為其屬性(包括一些執(zhí)行上下文信息,例如this的值)的對象。
  • 外部詞法環(huán)境引用(Outer):對外部詞法環(huán)境的引用,以此關聯(lián)外部詞法環(huán)境。

代碼執(zhí)行的過程中,每一個局部變量和局部函數(shù)的聲明,都會作為一個屬性字段被添加到環(huán)境記錄中,后續(xù)對變量和函數(shù)的讀取則通過對應標識符在環(huán)境記錄中進行查找。

根據(jù)上面的概念,我們可以通過下面的對象結構理解詞法環(huán)境:

  lexicalEnvironment = {
    environmentRecord: {
      <identifier>: <value>,
      <identifier>: <value>,
    },
    outer: <Reference to the parent lexical environment>,
  }

再來通過下面的代碼例子來理解詞法環(huán)境:

/*
  當前模塊運行時,模塊的詞法環(huán)境被創(chuàng)建,
  moduleLexicalEnvironment = {
    environmentRecord: {
      name: <uninitialized>,
      sayName: <reference to function object>,
    },
    outer: <globalLexicalEnvironment>,
  }
*/
let name = 'Hardy';
/*
  變量聲明和賦值,修改環(huán)境記錄的字段屬性值,
  moduleLexicalEnvironment = {
    environmentRecord: {
      name: 'Hardy',
      sayName: <reference to function object>,
    },
    outer: <globalLexicalEnvironment>,
  }
*/
function sayName(myName) {
  /*
    執(zhí)行函數(shù)時,函數(shù)的詞法環(huán)境被創(chuàng)建,
    functionLexicalEnvironment = {
      environmentRecord = {
        myName: 'Hardy',
      },
      outer: <moduleLexicalEnvironment>,
    }
  */
  /* 通過讀取環(huán)境記錄的對應標識符字段屬性值獲取myName的變量值 */
  console.log(myName);
}
sayName(); // Hardy

我們來分析下上面的代碼例子:

根據(jù)聲明提前的特性,變量name和函數(shù)sayName都會在模塊的詞法環(huán)境創(chuàng)建時被添加在環(huán)境記錄中。但是,由于let的暫時性死區(qū)特性,變量name在自身聲明和初始化賦值之前處于不可引用和未初始化狀態(tài)。函數(shù)的聲明則不同,除了聲明提前外還會初始化函數(shù)的引用。這就是我們可以在函數(shù)執(zhí)行聲明語句前調(diào)用函數(shù)的原因。另外,函數(shù)的詞法環(huán)境在被創(chuàng)建時,對應函數(shù)的參數(shù)會被初始化在環(huán)境記錄中,并且會被賦值上調(diào)用函數(shù)時的所傳值或者函數(shù)參數(shù)的默認值。

outer引用方面,模塊詞法環(huán)境moduleLexicalEnvironmentouter引用指向JavaScript最外部的全局詞法環(huán)境globalLexicalEnvironment,而函數(shù)詞法環(huán)境functionLexicalEnvironmentouter引用指向外部的模塊詞法環(huán)境moduleLexicalEnvironment。

我們可以看出,詞法環(huán)境是JavaScript對作用域概念的內(nèi)部技術實現(xiàn)。它是JavaScript引擎創(chuàng)建一個執(zhí)行上下文時,創(chuàng)建用來存儲變量和函數(shù)聲明的環(huán)境。代碼執(zhí)行過程中,通過它訪問到存儲在其內(nèi)部的變量和函數(shù)。在代碼執(zhí)行完畢后,執(zhí)行上下文會從堆棧中被銷毀回收,而詞法環(huán)境也會根據(jù)情況的被銷毀(如果詞法環(huán)境被其他外部的詞法環(huán)境所引用,則不會被銷毀回收,例如閉包)。

作用域鏈

作用域可以嵌套,嵌套在內(nèi)部的作用域可以訪問外部的作用域所聲明的變量和函數(shù)。通過上面詞法環(huán)境的介紹,我們大概清楚,作用域的這種嵌套關系是通過詞法環(huán)境的外部詞法環(huán)境引用outer來關聯(lián)實現(xiàn)的。這種詞法環(huán)境的外部引用的關聯(lián)關系,構建了一條單向的詞法環(huán)境的鏈條。這就是我們常說的作用域鏈。

本質上,作用域鏈是JavaScript引擎給所執(zhí)行代碼維護的一條詞法環(huán)境鏈條。代碼執(zhí)行中對外部作用域的變量的引用,通過這一條鏈條進行變量的查找、讀取、修改。

代碼執(zhí)行中對某個變量的訪問大致如下:

  • 當代碼要訪問一個變量時,首先會搜索當前內(nèi)部詞法環(huán)境。如果搜索成功,就返回對一個變量值或變量引用,結束搜索。如果搜索不到,則通過outer引用繼續(xù)搜索外部詞法環(huán)境,以此類推,直到全局詞法環(huán)境。
  • 如果在任何地方都找不到這個變量,那么在嚴格模式下就會報錯。

根據(jù)上面的概念,我們來看看下面的例子:

let phrase = 'Hello';

function sayHello(name) {
  /*
    函數(shù)的作用域鏈,
    functionLexicalEnvironment{ name: 'Hardy' } ==outer==>
    moduleLexicalEnvironment{ phrase: 'Hello' } ==outer==>
    globalLexicalEnvironment

    變量name從當前functionLexicalEnvironment中查找到并獲取,
    變量phrase沿作用域鏈查找,從moduleLexicalEnvironment中查找到并獲取
  */
  console.log(`${phrase}, ${name}!`);
}
sayHello('Hardy'); // Hello, Hardy!

上面例子中,函數(shù)sayHello在內(nèi)部引用了namephrase兩個變量,函數(shù)被調(diào)用的執(zhí)行時會創(chuàng)建functionLexicalEnvironment > moduleLexicalEnvironment > globalLexicalEnvironment的作用域鏈。

其中,變量name作為函數(shù)參數(shù)屬于當前函數(shù)作用域的局部變量,變量可以直接從當前函數(shù)的詞法環(huán)境functionLexicalEnvironment中查找到并返回相關信息。而變量phrase屬于外部作用域中聲明的變量,存儲在外部的模塊詞法環(huán)境moduleLexicalEnvironment中。函數(shù)sayHello引用變量phrase,會首先從在自身函數(shù)詞法環(huán)境functionLexicalEnvironment中進行查找,查找不到后,會沿外部詞法環(huán)境引用outer找到模塊詞法環(huán)境moduleLexicalEnvironment,并從中繼續(xù)進行變量的查找,查找到了并返回變量的相關信息。

值得注意的是console.log()是全局內(nèi)置對象console上的方法,對該方法的調(diào)用需要引用console。這個變量的引用會沿作用域鏈一直查找到全局詞法環(huán)境globalLexicalEnvironment中,從中查找到并返回相關變量信息。

變量標識符解析和引用的過程就是沿作用域鏈迭代查找變量是否在作用域鏈節(jié)點中并返回變量相關信息的過程。

相關優(yōu)化

綜合上面的標識符的解析過程和作用域以及作用域鏈的關系,我們可以了解到,變量標識符解析的性能是和變量標識符所處在作用域鏈中的位置是息息相關的。變量標識符所出的作用域節(jié)點越靠近整個作用域鏈的前端,則需要沿作用域鏈迭代查找的次數(shù)就越少,變量標識符解析的速度就會越快,性能就越好。

這種標識符解析性能的規(guī)律,讓我們可以得出以下使用變量的優(yōu)化點:

  • 對于頻繁引用的外部作用域的變量,可以根據(jù)情況在當前作用域內(nèi)聲明賦值為局部變量后使用。
  • 減少作用域增強with語句的使用。

外部作用域變量標識符的多次引用,會造成執(zhí)行過程中的標識符解析沿作用域鏈查找的頻繁執(zhí)行,這種查找在第一次解析引用時是必須的,但是后續(xù)解析引用卻是重復的。將外部作用域變量通過在當前作用域內(nèi)聲明賦值為局部變量,可以優(yōu)化后續(xù)查找的需要經(jīng)過的作用域鏈節(jié)點個數(shù),得到一定的性能提升。

with語句可以在當前作用域鏈前端臨時添加一個詞法環(huán)境,從而在位置構建和使用新的作用域鏈。但是這方式問題也很顯而易見:作用域鏈被加長了,除了被添加到前端的詞法環(huán)境中的存儲的變量外,其他變量的標識符解析性能都會變差。因此,我們應該減少with語句的使用。

總結

隨著JavaScript語言的發(fā)展,語言中的作用域的種類也變得豐富起來,不再局限于函數(shù)作用域作為最小變量聲明范圍來使用,而是可以基于更小范圍的跨級作用域來管理我們的變量引用范圍。變量的管理變得更加的靈活、安全。

作用域鏈是作用域鏈嵌套的結構產(chǎn)物,所有變量標識符的解析和引用會沿著作用域鏈進行查找。而詞法環(huán)境,是JavaScript對于作用域的內(nèi)部技術實現(xiàn)。深入了解詞法環(huán)境后,也讓我們更清楚代碼在解析變量標識符時的內(nèi)部執(zhí)行過程。也根據(jù)這個過程,我們大概總結出了兩點關于作用域和變量使用的性能優(yōu)化點。

作用域的使用作為每一位JavaScript開發(fā)人員的必修課,了解得深入才能在使用它的時候不再迷茫。它就像空氣,存在于JavaScript的許多地方,值得我們?nèi)ズ煤昧私狻?/p>

到此這篇關于JavaScript作用域與作用域鏈優(yōu)化方式的文章就介紹到這了,更多相關JS 作用域鏈優(yōu)化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • JS中mouseup事件丟失的原因與解決辦法

    JS中mouseup事件丟失的原因與解決辦法

    這篇文章主要跟大家介紹了關于Javascript中mouseup事件丟失的原因與解決辦法的相關資料,文中給出詳細的示例代碼供大家參考學習,需要的朋友們下面跟著小編一起來學習學習吧。
    2017-06-06
  • javascript實現(xiàn)點擊按鈕切換圖片

    javascript實現(xiàn)點擊按鈕切換圖片

    這篇文章主要為大家詳細介紹了javascript實現(xiàn)點擊按鈕切換圖片,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • JavaScript中數(shù)組嵌套對象排序方法的示例詳解

    JavaScript中數(shù)組嵌套對象排序方法的示例詳解

    在?JavaScript?中,可以使用?sort()?方法對包含嵌套對象的數(shù)組進行排序,本文將通過三個簡單的示例為大家進行簡單的介紹,需要的可以參考下
    2024-03-03
  • uniapp中vuex的應用使用步驟

    uniapp中vuex的應用使用步驟

    Vuex是一個專為Vue.js應用程序開發(fā)的狀態(tài)管理模式,它采用集中式存儲管理應用的所有組件的狀態(tài),并以相應的規(guī)則保證狀態(tài)以一種可預測的方式發(fā)生變化,下面這篇文章主要給大家介紹了關于uniapp中vuex的應用使用,需要的朋友可以參考下
    2022-08-08
  • 全面解析Bootstrap中form、navbar的使用方法

    全面解析Bootstrap中form、navbar的使用方法

    這篇文章主要為大家詳細解析了Bootstrap中form、navbar的使用方法,感興趣的朋友可以參考一下
    2016-05-05
  • 可多次使用的仿126郵箱選項卡的源碼

    可多次使用的仿126郵箱選項卡的源碼

    可多次使用的仿126郵箱選項卡的源碼...
    2007-10-10
  • 結合ES6?編寫?JavaScript?設計模式中的結構型模式

    結合ES6?編寫?JavaScript?設計模式中的結構型模式

    這篇文章主要介紹了結合ES6編寫JavaScript?設計模式中的結構型模式,設計模式是軟件設計中常見問題的解決方案,這些模式很容易重復使用并且富有表現(xiàn)力
    2022-07-07
  • 在javaScript中檢測數(shù)據(jù)類型的幾種方式小結

    在javaScript中檢測數(shù)據(jù)類型的幾種方式小結

    在用javaScript編程的過程中,我們經(jīng)常會遇到這樣一個問題,就是需要檢測一個數(shù)據(jù)或變量的類型,本篇文章主要介紹了在javaScript中檢測數(shù)據(jù)類型的幾種方式小結,有興趣的可以了解一下。
    2017-03-03
  • js chrome瀏覽器判斷代碼

    js chrome瀏覽器判斷代碼

    今天發(fā)現(xiàn)chrome還是有一些獨一無二的東西,基于它們,我們就可以搞出基于特征的判定了。
    2010-03-03
  • 基于Bootstrap表單驗證功能

    基于Bootstrap表單驗證功能

    這篇文章主要為大家詳細介紹了基于Bootstrap表單驗證功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11

最新評論

吴桥县| 洱源县| 阿合奇县| 镇安县| 灵川县| 曲周县| 靖西县| 金门县| 嵊州市| 万安县| 沙坪坝区| 庆元县| 彭泽县| 共和县| 凉城县| 乐东| 通州区| 余庆县| 闵行区| 洛川县| 海安县| 富顺县| 长垣县| 阳曲县| 蒲江县| 肥乡县| 榕江县| 南澳县| 陵川县| 镇康县| 瑞金市| 思南县| 榆中县| 汽车| 海城市| 星子县| 防城港市| 搜索| 岳普湖县| 钟祥市| 夹江县|