js作用域链
loyalvi Lv7

js作用域链

作用域

一、概述

在 JavaScript 中,作用域(Scope)定义了变量、函数等标识符的可访问范围。它决定了在代码的哪个部分可以引用这些标识符。JavaScript 主要有两种作用域:全局作用域和局部作用域。

二、全局作用域

  • 定义
    • 当一个变量或函数在代码的最顶层定义时,它就处于全局作用域。在全局作用域中声明的变量和函数在整个程序中都可以访问。
    • 例如,在浏览器环境中,全局作用域是 window 对象。在 Node.js 环境中,全局作用域是 global 对象。
  • 示例
    1
    2
    3
    4
    5
    6
    var globalVar = "I'm global";
    function globalFunc() {
    console.log("I'm a global function");
    }
    console.log(globalVar); // 输出 "I'm global"
    globalFunc(); // 输出 "I'm a global function"
    在这个例子中,globalVarglobalFunc 都是在全局作用域中定义的。无论在代码的哪个位置,都可以访问它们。

三、局部作用域

(一)函数作用域

  • 定义
    • 函数作用域是最常见的一种局部作用域。在函数内部声明的变量和函数参数等只能在该函数内部访问,外部无法直接访问。
  • 示例
    1
    2
    3
    4
    5
    6
    function myFunction() {
    var localVar = "I'm local";
    console.log(localVar); // 输出 "I'm local"
    }
    myFunction();
    console.log(localVar); // 报错,因为 localVar 在函数外部无法访问
    在这个例子中,localVar 是在 myFunction 函数内部定义的局部变量。当在函数外部尝试访问它时,就会出现引用错误。

(二)块级作用域(ES6 引入)

  • 定义
    • 块级作用域是指在代码块(用 {} 包围的部分)内声明的变量(使用 letconst 关键字)只能在该代码块内访问。这是 ES6 新增的特性,使得 JavaScript 的作用域更加灵活。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    {
    let blockVar = "I'm block scoped";
    const blockConst = "I'm also block scoped";
    console.log(blockVar); // 输出 "I'm block scoped"
    console.log(blockConst); // 输出 "I'm also block scoped"
    }
    console.log(blockVar); // 报错,因为 blockVar 在代码块外部无法访问
    console.log(blockConst); // 报错,因为 blockConst 在代码块外部无法访问
    在这个例子中,blockVarblockConst 是在代码块内使用 letconst 声明的变量。它们只能在该代码块内被访问,一旦出了代码块范围,就无法访问了。

四、作用域链

  • 概念
    • 当在某个作用域中访问一个变量时,如果该作用域内没有找到这个变量,就会沿着作用域链向上查找,直到找到该变量或者到达全局作用域为止。作用域链是由多个作用域组成的链状结构,每个作用域都指向它的父作用域。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var globalVar = "global";
    function outerFunction() {
    var outerVar = "outer";
    function innerFunction() {
    var innerVar = "inner";
    console.log(innerVar); // 输出 "inner"
    console.log(outerVar); // 输出 "outer"
    console.log(globalVar); // 输出 "global"
    }
    innerFunction();
    }
    outerFunction();
    在这个例子中,innerFunction 可以访问自己的局部变量 innerVar,也可以访问它的父作用域 outerFunction 中的变量 outerVar,还可以访问全局作用域中的变量 globalVar。这就是作用域链在起作用,它使得嵌套的函数能够访问外层函数的变量。

五、闭包

js闭包

  • 定义
    • 闭包是一个函数和其周围的状态(词法环境)的组合。闭包让你可以从内部函数访问外部函数作用域中的变量。闭包是 JavaScript 中一个非常强大的特性,它使得函数可以“记住”并访问其创建时所在的作用域链中的变量,即使该函数在其创建上下文之外执行。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function createCounter() {
    var count = 0;
    return function() {
    count += 1;
    return count;
    };
    }
    var counter = createCounter();
    console.log(counter()); // 输出 1
    console.log(counter()); // 输出 2
    在这个例子中,createCounter 函数返回了一个匿名函数。这个匿名函数可以访问 createCounter 函数中的变量 count。即使 createCounter 函数执行完毕,返回的匿名函数仍然可以访问 count 变量,这就是闭包的作用。每次调用返回的匿名函数时,count 的值都会增加,因为闭包“记住”了 count 变量的状态。

js中怎么区分作用域

在 JavaScript 中,作用域(Scope) 决定了变量、函数和对象的可访问范围。作用域的划分主要依赖于变量声明的方式(如 varletconst)以及代码的结构(如函数、块级作用域)。以下是详细区分作用域的方法:

一、作用域的类型

作用域类型 特点 示例代码
全局作用域 在函数或块外声明的变量,全局可访问 let globalVar = 10;(全局变量)
函数作用域 在函数内部声明的变量(使用 var),仅函数内可访问 function fn() { var a = 1; }a 仅在 fn 内有效)
块级作用域 {} 内声明的变量(使用 let/const),仅块内有效 if (true) { let b = 2; }b 仅在 if 块内有效)
模块作用域 ES6 模块(<script type="module">)中的顶层变量,仅在模块内有效 export const c = 3;(模块外不可见)

二、区分作用域的关键规则

1. var 的函数作用域

  • var 声明的变量只有函数作用域,没有块级作用域。
  • iffor 等块中使用 var,变量会“泄漏”到外层作用域。
1
2
3
4
5
6
function exampleVar() {
if (true) {
var x = 10; // x 在整个函数内有效
}
console.log(x); // 输出 10(泄漏到函数作用域)
}

2. let/const 的块级作用域

  • letconst 声明的变量只在当前代码块({})内有效
  • 常见于 ifforwhile 等块级结构中。
1
2
3
4
5
6
7
function exampleLet() {
if (true) {
let y = 20; // y 仅在 if 块内有效
const z = 30; // z 同理
}
console.log(y); // 报错:y is not defined
}

3. 函数作用域 vs 块级作用域

  • 函数作用域:使用 var 在函数内部声明,变量在整个函数内有效。
  • 块级作用域:使用 let/const{} 内声明,变量仅在块内有效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 函数作用域
function funcScope() {
var a = 1;
if (true) {
var a = 2; // 覆盖外层的 a
}
console.log(a); // 输出 2
}
// 块级作用域
function blockScope() {
let b = 1;
if (true) {
let b = 2; // 与外层 b 无关
}
console.log(b); // 输出 1
}

三、作用域链(Scope Chain)

JavaScript 查找变量时会沿着作用域链逐级向上查找:

  1. 当前作用域 → 外层函数作用域 → 全局作用域。
  2. 闭包(Closure):内部函数保留对外部函数作用域的引用。
1
2
3
4
5
6
7
8
9
function outer() {
let outerVar = "Outer";
function inner() {
console.log(outerVar); // 访问外层作用域的变量(闭包)
}
return inner;
}
const innerFn = outer();
innerFn(); // 输出 "Outer"

四、常见场景区分

1. 循环中的 var vs let

  • var 在循环中会共享同一个变量。
  • let 在每次循环中创建一个新的块级作用域。
1
2
3
4
5
6
7
8
// 使用 var(函数作用域)
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 输出 3, 3, 3
}
// 使用 let(块级作用域)
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j)); // 输出 0, 1, 2
}

2. 模块作用域

  • 在 ES6 模块中,顶层变量默认属于模块作用域,不会污染全局。
1
2
3
4
// module.js
let moduleVar = "模块内变量"; // 仅在模块内有效
export { moduleVar };
// 其他文件导入时无法直接访问 moduleVar

五、总结:如何区分作用域?

  1. 检查声明方式
    • var → 函数作用域或全局作用域。
    • let/const → 块级作用域。
  2. 观察代码结构
    • 函数内部 → 函数作用域。
    • {} 包裹的代码块(如 iffor)→ 块级作用域(需配合 let/const)。
  3. 模块化环境
    • 使用 <script type="module"> 或 ES6 模块语法 → 模块作用域。

通过理解声明方式和代码结构,可以清晰区分 JavaScript 的作用域,避免变量污染和作用域混淆的问题。

()

在 JavaScript 中,圆括号 () 本身不会创建新的作用域,但括号内的内容可能属于不同的作用域,具体取决于其所在的上下文。以下是常见使用场景的详细分析:

一、函数参数列表中的 ()

函数定义或调用时的参数列表 () 不会创建作用域,参数变量属于函数体作用域的一部分:

1
2
3
4
5
function sum(a, b) { 
// a 和 b 的作用域在函数体内部
return a + b;
}
sum(1, 2);

二、表达式分组中的 ()

圆括号用于表达式分组时,不改变作用域

1
2
const result = (1 + 2) * 3; 
// 括号仅改变运算顺序,不影响作用域

三、立即调用函数表达式(IIFE)中的 ()

通过 () 包裹函数并立即调用(IIFE),会创建一个新的函数作用域

1
2
3
4
(function() {
var privateVar = "内部变量"; // 作用域在 IIFE 内部
})();
console.log(privateVar); // 报错:privateVar 未定义

四、条件语句中的 ()

条件语句(如 ifwhile)中的条件表达式 () 不创建作用域

1
2
3
4
5
if (true) {
// 条件中的括号只是语法,实际作用域由 {} 决定
let x = 10;
}
console.log(x); // 报错:x 未定义(块级作用域生效)

五、箭头函数参数中的 ()

箭头函数的参数列表 () 不会创建新作用域,参数变量属于箭头函数体作用域:

1
2
3
4
const greet = (name) => {
console.log(`Hello, ${name}`);
};
greet("Alice");

六、对象属性或方法中的 ()

对象方法调用时的 () 属于方法调用语法,不影响作用域:

1
2
3
4
5
6
7
const obj = {
name: "Bob",
sayHi: function() {
console.log(this.name); // this 指向 obj
}
};
obj.sayHi(); // 输出 "Bob"

七、特殊语法中的 ()

某些语法(如 for 循环的初始化部分)中的 () 可能隐含块级作用域

1
2
3
4
for (let i = 0; i < 3; i++) {
// i 的作用域在 for 循环的块级作用域内
}
console.log(i); // 报错:i 未定义

总结:如何区分 () 内的作用域?

  1. 括号本身不创建作用域,真正的作用域由代码结构(如函数、块级 {})决定。
  2. 关键场景:
    • 函数调用或定义:参数列表属于函数体作用域。
    • IIFE:括号包裹的函数会创建独立作用域。
    • 表达式或条件:括号仅用于语法分组,不影响作用域。

示例对比

场景 括号作用 是否创建新作用域
function(a, b) { ... } 参数列表 否(属于函数体)
(function() { ... })() IIFE 是(函数作用域)
if (condition) { ... } 条件表达式 否(由 {} 决定)
(1 + 2) * 3 表达式分组
理解作用域的核心是关注代码的结构(如 {}、函数、模块),而非单纯的 ()
由 Hexo 驱动 & 主题 Keep
访客数 访问量