欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

前言

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

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

而這篇文章,就是想要和大家聊聊有關(guān)JavaScript作用域以及作用域鏈的那些事情,以及針對(duì)它們的一些我們?cè)诖a中優(yōu)化小技巧。

內(nèi)容

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

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

作用域

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

通過(guò)類(lèi)比,我們可以把作用域想象成一個(gè)氣泡。在這個(gè)氣泡里所聲明的變量成員被包含在其中。每個(gè)氣泡都配備有一位有原則的管家,將所有的成員管理起來(lái),并針對(duì)他們聲明的位置和要求對(duì)它們提供保護(hù)。當(dāng)氣泡中代碼語(yǔ)句想要訪問(wèn)和修改變量成員時(shí),管家會(huì)結(jié)合變量成員的要求關(guān)聯(lián)對(duì)應(yīng)訪問(wèn)和修改操作。

隨著ECMAScript標(biāo)準(zhǔn)的不斷發(fā)展和完善,JavaScript目前存在著四種作用域類(lèi)型:

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

作用域的嵌套

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

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

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

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

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

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

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

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

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

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

/*
  當(dāng)前模塊運(yùn)行時(shí),模塊的詞法環(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í),函數(shù)的詞法環(huán)境被創(chuàng)建,
    functionLexicalEnvironment = {
      environmentRecord = {
        myName: 'Hardy',
      },
      outer: <moduleLexicalEnvironment>,
    }
  */
  /* 通過(guò)讀取環(huán)境記錄的對(duì)應(yīng)標(biāo)識(shí)符字段屬性值獲取myName的變量值 */
  console.log(myName);
}
sayName(); // Hardy

我們來(lái)分析下上面的代碼例子:

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

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

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

作用域鏈

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

本質(zhì)上,作用域鏈?zhǔn)荍avaScript引擎給所執(zhí)行代碼維護(hù)的一條詞法環(huán)境鏈條。代碼執(zhí)行中對(duì)外部作用域的變量的引用,通過(guò)這一條鏈條進(jìn)行變量的查找、讀取、修改。

代碼執(zhí)行中對(duì)某個(gè)變量的訪問(wèn)大致如下:

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

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

let phrase = 'Hello';

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

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

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

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

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

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

相關(guān)優(yōu)化

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

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

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

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

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

總結(jié)

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

作用域鏈?zhǔn)亲饔糜蜴溓短椎慕Y(jié)構(gòu)產(chǎn)物,所有變量標(biāo)識(shí)符的解析和引用會(huì)沿著作用域鏈進(jìn)行查找。而詞法環(huán)境,是JavaScript對(duì)于作用域的內(nèi)部技術(shù)實(shí)現(xiàn)。深入了解詞法環(huán)境后,也讓我們更清楚代碼在解析變量標(biāo)識(shí)符時(shí)的內(nèi)部執(zhí)行過(guò)程。也根據(jù)這個(gè)過(guò)程,我們大概總結(jié)出了兩點(diǎn)關(guān)于作用域和變量使用的性能優(yōu)化點(diǎn)。

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

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

相關(guān)文章

  • 深入理解JavaScript系列(13) This? Yes,this!

    深入理解JavaScript系列(13) This? Yes,this!

    在這篇文章里,我們將討論跟執(zhí)行上下文直接相關(guān)的更多細(xì)節(jié)。討論的主題就是this關(guān)鍵字。實(shí)踐證明,這個(gè)主題很難,在不同執(zhí)行上下文中this的確定經(jīng)常會(huì)發(fā)生問(wèn)題
    2012-01-01
  • js禁止document element對(duì)象選中文本實(shí)現(xiàn)代碼

    js禁止document element對(duì)象選中文本實(shí)現(xiàn)代碼

    禁止document element對(duì)象選中文本在某在情況下還是很有必要的接下來(lái)本文將使用js實(shí)現(xiàn),感興趣的各位可以參考下哈
    2013-03-03
  • jQuery實(shí)現(xiàn)鼠標(biāo)放置名字上顯示詳細(xì)內(nèi)容氣泡提示框效果的方法分析

    jQuery實(shí)現(xiàn)鼠標(biāo)放置名字上顯示詳細(xì)內(nèi)容氣泡提示框效果的方法分析

    這篇文章主要介紹了jQuery實(shí)現(xiàn)鼠標(biāo)放置名字上顯示詳細(xì)內(nèi)容氣泡提示框效果的方法,結(jié)合實(shí)例形式分析了jQuery結(jié)合bootstrap插件實(shí)現(xiàn)的鼠標(biāo)響應(yīng)式提示框相關(guān)操作技巧,需要的朋友可以參考下
    2020-04-04
  • JS 自定義函數(shù)缺省值的設(shè)置方法

    JS 自定義函數(shù)缺省值的設(shè)置方法

    有時(shí)候定義的函數(shù)需要設(shè)置默認(rèn)值,因?yàn)椴粠Ь蜁?huì)出現(xiàn)一些錯(cuò)誤,大家可以參考下這里默認(rèn)值的定義方法。
    2010-05-05
  • javascript定義函數(shù)的方法

    javascript定義函數(shù)的方法

    javscript中定義和聲明函數(shù)有三種方式:正常方法 構(gòu)造函數(shù) 函數(shù)直接量。
    2010-12-12
  • 針對(duì)JavaScript中this指向的簡(jiǎn)單理解

    針對(duì)JavaScript中this指向的簡(jiǎn)單理解

    這篇文章主要為大家詳細(xì)JavaScript中this指向的簡(jiǎn)單理解,感興趣的小伙伴們可以參考一下
    2016-08-08
  • JavaScript引用類(lèi)型RegExp基本用法詳解

    JavaScript引用類(lèi)型RegExp基本用法詳解

    這篇文章主要介紹了JavaScript引用類(lèi)型RegExp基本用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了引用類(lèi)型RegExp正則表達(dá)式相關(guān)函數(shù)使用技巧與操作注意事項(xiàng),需要的朋友可以參考下
    2018-08-08
  • JavaScript偽數(shù)組和數(shù)組的使用與區(qū)別

    JavaScript偽數(shù)組和數(shù)組的使用與區(qū)別

    這篇文章主要給大家介紹了關(guān)于JavaScript偽數(shù)組和數(shù)組使用與區(qū)別的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • javascript中in運(yùn)算符用法分析

    javascript中in運(yùn)算符用法分析

    這篇文章主要介紹了javascript中in運(yùn)算符用法,實(shí)例分析了in運(yùn)算符的相關(guān)使用技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-04-04
  • JavaScript中的類(lèi)型判斷你真的了解了嗎

    JavaScript中的類(lèi)型判斷你真的了解了嗎

    這篇文章主要為大家詳細(xì)介紹了JavaScript中類(lèi)型判斷的相關(guān)常見(jiàn)方法,文中的示例代碼講解詳細(xì),對(duì)我們深入學(xué)習(xí)JavaScript有一定幫助,需要的可以參考下
    2023-11-11

最新評(píng)論