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

深度解析JavaScript中作用域與作用域鏈

 更新時(shí)間:2025年11月21日 15:48:12   作者:貪婪的君子  
作用域(Scope)與作用域鏈(Scope Chain)是 JavaScript 的核心概念,它們決定了變量的可訪問范圍、生命周期,以及代碼運(yùn)行時(shí)變量查找的規(guī)則,下面小編就和大家詳細(xì)講講吧

概述

作用域(Scope)與作用域鏈(Scope Chain)是 JavaScript 的核心概念,它們決定了變量的可訪問范圍、生命周期,以及代碼運(yùn)行時(shí)變量查找的規(guī)則,理解這兩個(gè)概念,可以回答我們 “變量在這里為什么能訪問”,“為什么這里訪問到的變量值是 undefined” 等諸多疑問,同時(shí)還能幫助我們在開發(fā)過程中規(guī)避變量污染、提升代碼的可維護(hù)性,對于 ES Module 等模塊方案也可以有更好的理解。

作用域類型

全局作用域(Global Scope)

全局作用域是最頂層的作用域,代碼中未被任何函數(shù)或塊級結(jié)構(gòu)包裹的變量 / 函數(shù),都屬于全局作用域。

  • 生命周期:與程序運(yùn)行周期一致,頁面加載時(shí)創(chuàng)建,頁面關(guān)閉時(shí)銷毀;
  • 瀏覽器環(huán)境:全局作用域的變量會掛載到 window? 對象上(Node.js 環(huán)境掛載到 global 對象);
  • 訪問范圍:代碼中的任何位置都能訪問;
// 全局變量:屬于全局作用域
const globalVar = "我是全局變量";

function bar() {
  // 可訪問全局變量
  console.log(globalVar); // 輸出:我是全局變量
}

bar();
console.log(window.globalVar); // 瀏覽器環(huán)境輸出:我是全局變量

函數(shù)作用域(Function Scope)

函數(shù)作用域是指變量 / 函數(shù)僅在定義它們的函數(shù)內(nèi)部可訪問,函數(shù)外部無法直接訪問。

  • 生命周期:函數(shù)調(diào)用時(shí)創(chuàng)建,函數(shù)執(zhí)行結(jié)束后銷毀(閉包除外);
  • 訪問范圍:僅函數(shù)內(nèi)部及嵌套的子函數(shù)可訪問;
  • 核心特性:函數(shù)參數(shù)也屬于函數(shù)作用域的變量。
function foo() {
  // 函數(shù)作用域變量:僅foo內(nèi)部可訪問
  const funcVar = "我是函數(shù)作用域變量";
  
  // 嵌套函數(shù)可訪問外層函數(shù)作用域的變量
  function inner() {
    console.log(funcVar); // 輸出:我是函數(shù)作用域變量
  }
  
  inner();
}

foo();
console.log(funcVar); // 報(bào)錯(cuò):funcVar is not defined(外部無法訪問)

塊級作用域(Block Scope)

塊級作用域是 ES6 引入的特性,由 { }? 包裹的代碼塊(如 if?、for?、while?、try/catch?,以及直接用 { }? 定義的塊)形成,僅 let/const 聲明的變量會綁定到塊級作用域。

  • 生命周期:代碼塊執(zhí)行時(shí)創(chuàng)建,執(zhí)行結(jié)束后銷毀;
  • 訪問范圍:僅塊內(nèi)部可訪問;
  • 核心特性:不存在變量提升(或稱為暫時(shí)性死區(qū)),避免變量泄露。
if (true) {
  // let聲明的變量:綁定到塊級作用域
  let blockVar = "我是塊級作用域變量";
  const blockConst = "塊級常量";
  
  // var聲明的變量:不綁定塊級作用域,屬于外層作用域(如全局)
  var nonBlockVar = "我不屬于塊級作用域";
}

console.log(blockVar); // 報(bào)錯(cuò):blockVar is not defined
console.log(blockConst); // 報(bào)錯(cuò):blockConst is not defined
console.log(nonBlockVar); // 輸出:我不屬于塊級作用域(全局變量)

雖然我這里劃分了三種類型的作用域,但其實(shí)是兩個(gè)大類型:全局和局部,函數(shù)作用域和塊級作用域就屬于是局部的作用域。

作用域的本質(zhì)

作用域本質(zhì)上是定義了一套?變量的訪問規(guī)則,用于確定在代碼執(zhí)行過程中,某個(gè)變量何時(shí)被創(chuàng)建、何時(shí)被銷毀,在何處可以被訪問、修改。

JavaScript 的作用域是?靜態(tài)作用域?(通常也稱為詞法作用域), 靜態(tài)作用域是在代碼定義階段而非運(yùn)行階段確定的,通俗的說就是,你把變量寫在代碼的哪里,它的作用域就在哪個(gè)范圍內(nèi),舉個(gè)例子:

function outer() {
  const a = 1; // 定義在 outer 作用域中的變量
  
  function inner() {
    console.log(a); // inner函數(shù)定義時(shí),嵌套在outer內(nèi)部,所以可以訪問這個(gè) a 變量
  }
  
  return inner;
}

console.log(a); // 這個(gè) log 是在全局作用域下,是在 outer 作用域外的,所以訪問不到 outer 中的變量
// 但這里有一個(gè)注意點(diǎn),a 雖然訪問不到 outer 中的變量 a,但是他可以訪問到全局作用域的 a,由于未定義,所以輸出是 undefined (非嚴(yán)格模式下)

const fn = outer();
fn(); // 輸出:1(即使 fn 在 outer 外部執(zhí)行,仍能訪問 outer 的 a,這就是經(jīng)典的閉包)

一般和作用域同時(shí)提到的還有?執(zhí)行上下文,這是兩個(gè)概念,需要注意區(qū)分:

  • 作用域是靜態(tài)的,代碼定義時(shí)確定,不關(guān)注代碼的執(zhí)行
  • 執(zhí)行上下文是動態(tài)的,在代碼執(zhí)行的過程中動態(tài)的創(chuàng)建,包含 this ,變量對象,作用域鏈等信息,在每次調(diào)用函數(shù)時(shí)都會創(chuàng)建新的執(zhí)行上下文。

作用域鏈

作用域鏈,顧名思義,就是一個(gè)鏈表,是?由當(dāng)前作用域和外層作用域組成的鏈表,用于解析變量引用。當(dāng)代碼在某個(gè)作用域訪問一個(gè)變量時(shí),會從當(dāng)前作用域出發(fā)逐級向外層作用域去尋找變量,舉個(gè)例子:

// 全局作用域
const globalVar = "全局變量";

function outer() {
  // 外層函數(shù)作用域
  const outerVar = "外層變量";
  
  function inner() {
    // 內(nèi)層函數(shù)作用域
    const innerVar = "內(nèi)層變量";
    console.log(innerVar); // 查找鏈:【找到】inner 作用域
    console.log(outerVar); // 查找鏈:inner 作用域 -> 【找到】outer 作用域
    console.log(globalVar); // 查找鏈:innter 作用域 -> outer 作用域 -> 【找到】全局作用域
  }
  
  inner();
}

outer();

inner? 函數(shù)中訪問 globalVar 變量時(shí)就是沿著作用域鏈逐級查找的。

鏈的構(gòu)建過程

函數(shù)定義時(shí):JavaScript 引擎會為函數(shù)關(guān)聯(lián)一個(gè) [[Scopes]] 內(nèi)部屬性,存儲函數(shù)定義時(shí)所處的所有外層作用域;

函數(shù)調(diào)用時(shí):創(chuàng)建該函數(shù)的執(zhí)行上下文,此時(shí)作用域鏈會被初始化:

  • 鏈的第一個(gè)元素是當(dāng)前執(zhí)行上下文的變量對象(存儲當(dāng)前作用域的變量、函數(shù));
  • 后續(xù)元素是函數(shù) [[Scopes]]? 屬性中的外層作用域變量對象,按從內(nèi)到外的順序排列;

作用域鏈固化:作用域鏈在執(zhí)行上下文創(chuàng)建時(shí)確定,后續(xù)不會因代碼執(zhí)行而改變。

以本節(jié)開始的代碼為例:

  • ?inner? 函數(shù)定義時(shí),引擎會為其添加一個(gè) [[Scopes]]? 屬性,其中包含:外層函數(shù)作用域(outer)、全局作用域;
  • ?inner 調(diào)用時(shí),會創(chuàng)建執(zhí)行上下文,該上下文的作用域鏈為:[inner 變量對象({innerVar: "內(nèi)層變量"})→ outer 變量對象({outerVar: "外層變量"})→ 全局變量對象({globalVar: "全局變量"})],箭頭表示鏈表的方向及連接。

變量查找規(guī)則

當(dāng)訪問一個(gè)變量時(shí),JavaScript 引擎的查找步驟為:

  • 從作用域鏈的第一個(gè)元素(當(dāng)前作用域)開始查找
  • 若找到變量,直接返回其值(或引用),停止查找
  • 若未找到,繼續(xù)查找作用域鏈的下一個(gè)元素(外層作用域)
  • 依次類推,直到找到變量或遍歷完整個(gè)作用域鏈
  • 若遍歷完所有作用域仍未找到,拋出 ReferenceError(變量未定義)、

需要注意的是,修改是直接作用到這個(gè)查找到的變量上的,比如:

const x = 1; // 全局變量

function foo() {
  x = 2; // 修改的是全局變量x,而非創(chuàng)建局部變量
  console.log(x); // 輸出:2
}

foo();
console.log(x); // 輸出:2(全局變量被修改)

在過去不默認(rèn)聲明嚴(yán)格模式的時(shí)候,我們在 foo 函數(shù)里面 x =2 會隱式的創(chuàng)建一個(gè)全局變量,在編碼的時(shí)候是一個(gè)很大的坑,但是現(xiàn)在的項(xiàng)目基本都默認(rèn)嚴(yán)格模式了,這種使用方式就會報(bào)錯(cuò),提前為我們規(guī)避一些問題。

應(yīng)用

閉包

閉包應(yīng)該是作用域鏈的一個(gè)最典型、廣泛的應(yīng)用了,他是由函數(shù)和定義時(shí)的詞法作用域組合成的,他允許函數(shù)在外部作用域執(zhí)行時(shí),依舊能夠訪問到該函數(shù)定義時(shí)的局部變量(函數(shù)作用域內(nèi)的變量),舉個(gè)例子:

function createCounter() {
  let count = 0; // 外層函數(shù)作用域變量
  
  // 內(nèi)部函數(shù)引用了count,且被返回(導(dǎo)出到外部)
  return function increment() {
    count++;
    console.log(count);
  };
}

// increment在createCounter作用域之外執(zhí)行
const counter = createCounter();
counter(); // 輸出:1
counter(); // 輸出:2
counter(); // 輸出:3

從作用域的視角上看:

  • ?increment? 函數(shù)定義的時(shí)候,其對應(yīng)的 [[Scopes]]? 屬性存儲的是 createCounter? 的作用域和一個(gè)最外層的全局作用域,也就是說這時(shí)候相關(guān)的作用域就已經(jīng)被這個(gè) increment 函數(shù)持有了。
  • createCounter? 執(zhí)行結(jié)束后,該函數(shù)的上下文環(huán)境被銷毀,但是由于 increment? 依舊持有這個(gè)函數(shù)的作用域,而且 increment? 被外部的 counter 變量所引用,不能被 GC,所以 increment? 這時(shí)候也還存在著,并且此時(shí)有個(gè)別名 counter
  • 當(dāng) counter 被調(diào)用的時(shí)創(chuàng)建的執(zhí)行上下文的作用域鏈為:[increment 變量對象 → createCounter 作用域變量對象 → 全局變量對象]。

由于這樣的鏈?zhǔn)疥P(guān)系和引用的持有,最終形成了閉包。

變量遮蔽

內(nèi)存作用域和外層作用域存在同名變量時(shí),內(nèi)層的變量會遮蔽外層變量,在查找時(shí)優(yōu)先訪問內(nèi)層變量。

const x = 10; // 外層變量

function bar() {
  const x = 20; // 內(nèi)層變量,遮蔽外層x
  console.log(x); // 輸出:20(訪問內(nèi)層x)
}

bar();
console.log(x); // 輸出:10(訪問外層x)

模塊化方案

ES Module 的核心就是模塊級作用域:

每個(gè)模塊都是一個(gè)獨(dú)立的詞法作用域,這意味著頂層的變量不會再自動掛載到 window 對象上了,模塊內(nèi)的變量/函數(shù)僅在模塊內(nèi)可以訪問。

需要通過export 導(dǎo)出變量/函數(shù),其他模塊通過 import 導(dǎo)入。

這里有沒有感覺很像閉包,必須將函數(shù)作用域的內(nèi)容 return 后,才可以在外層作用域使用。

一些問題

變量提升

在代碼執(zhí)行前,JavaScript 引擎會將 var? 聲明的變量提升到作用域頂部,提升后的值為undefined,將函數(shù)聲明提升到作用域頂部,值為函數(shù)本身。

值得注意的是,變量提升僅在當(dāng)前作用域上生效,不會出現(xiàn)跨作用域提升的情況,如下:

console.log(a); // 輸出:undefined(var聲明的變量提升)
var a = 1;

function foo() {
  console.log(b); // 輸出:undefined(函數(shù)作用域內(nèi)的變量提升)
  var b = 2;
}

foo();
console.log(b); // 報(bào)錯(cuò):b is not defined(b的提升僅在foo作用域內(nèi))

自 ES6 后,很少會在代碼中再使用 var 關(guān)鍵字了,基本用的是 const/let? 來聲明變量,因?yàn)?code>let/const?聲明的變量不會被提升,而是存在?暫時(shí)性死區(qū),即從作用域開始到變量聲明前,訪問該變量會報(bào)錯(cuò)。這是塊級作用域的特性,避免了變量提升導(dǎo)致的邏輯混亂。

全局作用域污染

function badFunc() {
  // 未聲明直接賦值,隱式創(chuàng)建全局變量
  unDeclaredVar = "我是污染的全局變量";
}

badFunc();
console.log(window.unDeclaredVar); // 輸出:我是污染的全局變量

對于這種沒有聲明就直接賦值的寫法,在非嚴(yán)格模式下,會隱式的創(chuàng)建一個(gè)全局變量,導(dǎo)致全局作用域被污染。

總結(jié)

理解了 JavaScript 的作用域和作用域鏈對我們理解閉包、模塊化、高階函數(shù)等特性能夠有更好的幫助,也能讓我們在實(shí)際開發(fā)中,更合理運(yùn)用塊級作用域(let/const)、模塊化(ES Module),規(guī)避變量污染、邏輯混亂等問題,寫出更健壯、可擴(kuò)展的 JavaScript 代碼。

作用域(Scope)與作用域鏈(Scope Chain)是 JavaScript 的核心概念,它們決定了變量的可訪問范圍、生命周期,以及代碼運(yùn)行時(shí)變量查找的規(guī)則,理解這兩個(gè)概念,可以回答我們 “變量在這里為什么能訪問”,“為什么這里的變量是 undefined” 等諸多疑問,還能幫助我們在開發(fā)過程中規(guī)避變量污染、提升代碼的可維護(hù)性,對于 ES Module 等模塊方案有更好的理解。

作用域的本質(zhì)

很多開發(fā)者會把作用域簡單理解為 “變量的存儲位置”,但這只說對了一半。作用域的核心是一套 “變量訪問規(guī)則” —— 它定義了在代碼的哪個(gè)位置可以訪問哪些變量,同時(shí)也隱含了變量的 “生存空間”(即變量何時(shí)被創(chuàng)建、何時(shí)被銷毀)。

到此這篇關(guān)于深度解析JavaScript中作用域與作用域鏈的文章就介紹到這了,更多相關(guān)JavaScript作用域與作用域鏈內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JS實(shí)現(xiàn)數(shù)組/對象數(shù)組刪除其中某一項(xiàng)

    JS實(shí)現(xiàn)數(shù)組/對象數(shù)組刪除其中某一項(xiàng)

    這篇文章主要介紹了JS實(shí)現(xiàn)數(shù)組/對象數(shù)組刪除其中某一項(xiàng),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • IE瀏覽器兼容Firefox的JS腳本的代碼

    IE瀏覽器兼容Firefox的JS腳本的代碼

    對于經(jīng)常用js的朋友,有時(shí)候一段腳本并不是兩個(gè)瀏覽器都兼容的,下面一些對ie和firefox都兼容的一些代碼,大家可以學(xué)習(xí)下。
    2008-10-10
  • 聊一聊JS中的prototype

    聊一聊JS中的prototype

    function定義的對象有一個(gè)prototype屬性,prototype屬性又指向了一個(gè)prototype對象,注意prototype屬性與prototype對象是兩個(gè)不同的東西,要注意區(qū)別.這篇文章主要介紹了JS中的prototype的相關(guān)資料,需要的朋友可以參考下
    2016-09-09
  • 詳細(xì)聊聊瀏覽器是如何看閉包的

    詳細(xì)聊聊瀏覽器是如何看閉包的

    閉包實(shí)質(zhì)上是函數(shù)作用域的副產(chǎn)物,下面這篇文章主要給大家介紹了關(guān)于瀏覽器是如何看閉包的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2021-11-11
  • 簡介EasyUI datagrid editor combogrid搜索框的實(shí)現(xiàn)

    簡介EasyUI datagrid editor combogrid搜索框的實(shí)現(xiàn)

    這篇文章主要介紹了EasyUI datagrid editor combogrid搜索框的實(shí)現(xiàn),涉及到EasyUI中combogrid的使用方法的相關(guān)知識,非常具有參考價(jià)值,需要的朋友可以參考下
    2016-04-04
  • JavaScript 七大技巧(二)

    JavaScript 七大技巧(二)

    我寫JavaScript代碼已經(jīng)很久了,都記不起是什么年代開始的了。本文給大家分享javascript七大技巧(二),對javascript技巧相關(guān)知識感興趣的朋友一起學(xué)習(xí)吧
    2015-12-12
  • js for終止循環(huán) 跳出多層循環(huán)

    js for終止循環(huán) 跳出多層循環(huán)

    這篇文章主要介紹了js for等循環(huán) 跳出多層循環(huán),終止循環(huán)執(zhí)行的方法,需要的朋友可以參考下
    2018-10-10
  • JS觸摸與手勢事件詳解

    JS觸摸與手勢事件詳解

    這篇文章主要為大家詳細(xì)介紹了JS觸摸與手勢事件的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • JavaScript ES 模塊的使用

    JavaScript ES 模塊的使用

    這篇文章主要介紹了JavaScript ES 模塊的使用,幫助大家更好的理解和使用JavaScript,感興趣的朋友可以了解下
    2020-11-11
  • js實(shí)現(xiàn)簡單手風(fēng)琴效果

    js實(shí)現(xiàn)簡單手風(fēng)琴效果

    這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)簡單手風(fēng)琴效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09

最新評論

靖远县| 涟源市| 宿州市| 南涧| 盖州市| 南涧| 长汀县| 页游| 射洪县| 霍林郭勒市| 清涧县| 文成县| 宜都市| 延寿县| 丰宁| 怀安县| 灌南县| 鄂托克旗| 平邑县| 平昌县| 库尔勒市| 杭锦后旗| 阜南县| 无锡市| 灯塔市| 旬邑县| 海口市| 英德市| 内乡县| 江阴市| 泾川县| 邹平县| 齐齐哈尔市| 武宁县| 乐都县| 延寿县| 合水县| 新宁县| 平武县| 伊宁市| 资源县|