【前端面试小册】JS-第11节:面试必会 - 作用域专题

一、什么是作用域

1.1 定义

作用域是一套规则,用于确定在何处以及如何查找变量,也就是确定当前执行代码对变量的访问权限。

类比理解:作用域就像房子的房间。每个房间(作用域)都有自己的物品(变量),你不能从客厅直接拿卧室里的东西,必须进入卧室才能拿到。

1.2 基本示例

function demo() {
    var name = "成都巴菲特";
}
demo();
console.log(name);  // Uncaught ReferenceError: name is not defined

分析

  • name 是在函数作用域中声明的
  • 全局作用域中直接取不到
  • 作用域起到了隔离变量的作用

1.3 作用域的作用

  1. 隔离变量:不同作用域下的变量互不干扰
  2. 避免命名冲突:不同作用域下的同名变量不会冲突
  3. 控制变量生命周期:作用域决定变量的创建和销毁时机

1.4 作用域类型

作用域主要包含:

  • 全局作用域
  • 局部作用域
    • 函数作用域
    • 块级作用域(ES6)

二、全局作用域

2.1 概念

全局作用域里面的变量(全局变量),你在任何地方都可以使用它,包括在函数内部。

2.2 注意事项

  1. var 声明的全局变量挂在 window 对象上(浏览器环境下)
  2. let 声明的全局变量挂在 script 上,不是 window
  3. 函数内部的变量,没有用 var 关键字,该变量也是全局变量
  4. 全局变量在页面关闭后,所占用内存才会释放

2.3 全局变量示例

Demo 1:作用域链查找

var name = "成都巴菲特";  // 最外层变量

function demo() {
    var name1 = "知识星球:前端职场圈";
    function innerFn() {  // 内层函数
        console.log(name1);  // 通过作用域链找到外层函数的 name1
    }
    innerFn();
}

console.log(name);  // 成都巴菲特
demo();  // 输出:知识星球:前端职场圈
console.log(name1);  // 输出:name1 is not defined(name1 是内部变量,外部无法直接访问)

作用域链查找流程

graph TD
    A[innerFn 查找 name1] --> B{innerFn 作用域有?}
    B -->|无| C[demo 作用域]
    C --> D{有 name1?}
    D -->|有| E[返回 知识星球:前端职场圈]
    D -->|无| F[全局作用域]
    F --> G{有 name1?}
    G -->|无| H[ReferenceError]

Demo 2:未声明直接赋值的变量

// 未定义直接赋值的变量会被声明为全局变量
function demo() {
    name = "成都巴菲特";  // 没有 var/let/const,成为全局变量
    var name1 = "公众号:前端面试资源";
}
demo();  // 必须先执行
console.log(name);   // 成都巴菲特
console.log(name1);  // name1 is not defined

关键点

  • name = "成都巴菲特" 没有使用 var/let/const,成为全局变量
  • 必须执行 demo() 后,name 才会被声明

Demo 3:未执行函数的情况

function demo() {
    name = "成都巴菲特";
    var name1 = "公众号:前端面试资源";
}
console.log(name);  // name is not defined

原因:因为没有执行 demo(),所以没有去声明 name 这个变量,也不会挂在 window 上,所以访问不到。

三、函数作用域

3.1 概念

属于这个函数的全部变量都可以在整个函数范围内使用。

函数作用域(局部作用域)的优势

  • 避免全局变量命名冲突
  • 减少内存消耗(全局变量只会在页面销毁后所占内存才会释放,而函数作用域一般在函数执行后,局部变量会被销毁)

3.2 局部变量特点

  1. 只能在函数内部访问
  2. 不同函数可以使用相同名称变量(互不干扰)
  3. 在函数执行时创建,函数执行完以后自动销毁

3.3 面试题示例

Demo 1:函数内变量与外部变量重名

var name = '公众号:前端面试资源'

function demo() {
    console.log(name)  // undefined
    var name = '知识星球:前端职场圈'
    console.log(name)  // 知识星球:前端职场圈
    name = '成都巴菲特'
    console.log(name)  // 成都巴菲特
}
console.log(name)  // 公众号:前端面试资源
demo()

输出顺序

  1. 公众号:前端面试资源(全局作用域)
  2. undefined(函数作用域,变量提升)
  3. 知识星球:前端职场圈(函数作用域,已赋值)
  4. 成都巴菲特(函数作用域,重新赋值)

涉及知识点

  • 变量提升var 声明的变量会提升到函数顶部
  • 作用域链:优先查找当前作用域,再向上查找

等价代码

var name = '公众号:前端面试资源'

function demo() {
    var name;  // 变量提升
    console.log(name)  // undefined
    name = '知识星球:前端职场圈'
    console.log(name)
    name = '成都巴菲特'
    console.log(name)
}

作用域链查找

graph TD
    A[demo 函数内查找 name] --> B{当前作用域有 name?}
    B -->|有| C[使用当前作用域的 name]
    B -->|无| D[向上查找全局作用域]
    C --> E[变量提升,值为 undefined]

Demo 2:外部无法访问局部作用域

function demo() {
    var name = '成都巴菲特'
    console.log(name)  // 成都巴菲特
}
demo()
console.log(name)  // Uncaught ReferenceError: name is not defined

原因:函数内部定义的局部变量外部无法访问。

四、块级作用域

4.1 概念

函数不是唯一的作用域单位,还有块作用域

块作用域:变量和函数可以属于所处作用域,也可以属于某个代码块(如 {}iffor 等)。

注意:ES5 没有块级作用域,ES6 才有(通过 letconst 实现)。

4.2 块级作用域解决的问题

问题 1:变量提升导致的问题

// ❌ ES5 的问题
var name = '成都巴菲特'

function demo() {
  console.log(name);  // undefined(不是预期的 成都巴菲特)
  if (0) {
    var name = '前端职场圈';
  }
}
demo();  // undefined

问题分析

// 等价于(变量提升)
var name = '成都巴菲特'

function demo() {
  var name;  // 变量提升到函数顶部
  console.log(name);  // undefined
  if (0) {
    name = '前端职场圈';
  }
}

原因var 声明的变量属于函数作用域,即使写在 if 块中,也会提升到函数顶部。

解决方案:使用 let 创建块级作用域

// ✅ ES6 的解决方案
let name = '成都巴菲特'

function demo() {
  console.log(name);  // 成都巴菲特
  if (0) {
    let name = '前端职场圈';  // 块级作用域,不影响外层
  }
}
demo();  // 成都巴菲特

问题 2:循环变量泄露

// ❌ ES5 的问题
var name = '成都巴菲特'

for (var i = 0; i < name.length; i++) {
  console.log(name[i]);
}

console.log(i);  // 5(变量泄露到全局)

问题分析

  • 因为 ES5 没有块级作用域,var 声明的变量,无论在哪里声明,最终都属于外部作用域
  • 这里的外部作用域就是全局,所以 for 循环完毕,变量 i 泄露为全局变量

解决方案:使用 let 创建块级作用域

// ✅ ES6 的解决方案
let name = '成都巴菲特'

for (let i = 0; i < name.length; i++) {
  console.log(name[i]);
}

console.log(i);  // ReferenceError: i is not defined

总结:由于 ES5 没有块级作用域,可能带来一些不合理的场景,所以 ES6 的块级作用域诞生了,可以解决这些问题。

五、面试题补充

5.1 Demo 1:let vs var 在块级作用域

{
    let name = '成都巴菲特';
}
console.log(name);  // ReferenceError: name is not defined

{
    var name = '成都巴菲特';
}
console.log(name);  // '成都巴菲特'

分析

  • let 声明的变量,只在他的代码块内有效
  • var 存在变量提升,相当于在全局声明 var name = undefined;然后代码块内赋值:name = '成都巴菲特'

5.2 Demo 2:未声明的变量

console.log(name);  // ReferenceError: name is not defined
{
    name = '成都巴菲特';
    console.log(name);  // '成都巴菲特'
}
console.log(name);  // '成都巴菲特'

分析

  1. 块级作用域中默认声明的变量(不用 varletconst),只有当代码执行完声明语句(name = '成都巴菲特')之后,才能访问该变量
  2. 默认声明的变量会被提升到全局作用域,但是要在声明语句之后才能被访问到

5.3 Demo 3:块级作用域中的函数声明

console.log(fn);  // undefined

{
    function fn(){}
}

分析:块级作用域的函数声明类似于 var 声明变量,会提升到全局作用域,同时函数声明还会提升到所在的块级作用域头部,所以是 undefined

5.4 Demo 4:函数声明与变量声明的优先级

function demo() {
    console.log(fn);  // [Function: fn]
    var fn = 1;
    function fn() {}
    console.log(fn);  // 1
}
demo();

输出结果

  1. [Function: fn]
  2. 1

关键点

  • 函数声明、变量声明(ES5)都会置顶
  • 函数声明优先级高于变量声明
  • 变量赋值语句(var fn = 1)会被拆分成两部分:
    1. 声明置顶:var fn
    2. 赋值部分:fn = 1
  • 函数与变量重名时,函数优先级高于变量声明,但会被变量赋值覆盖

等价代码

function demo() {
    function fn() {}  // 函数声明提升
    var fn;  // 变量声明提升(但不会覆盖已声明的函数)
    console.log(fn)  // [Function: fn]
    fn = 1;  // 赋值覆盖
    console.log(fn)  // 1
}

六、作用域链

6.1 概念

作用域链是 JavaScript 引擎查找变量的一套规则。

查找规则

  1. 从当前作用域开始查找
  2. 如果找不到,向上一级作用域查找
  3. 一直找到全局作用域
  4. 如果全局作用域也没有,抛出 ReferenceError

6.2 作用域链示例

var global = 'global';

function outer() {
    var outerVar = 'outer';
    
    function inner() {
        var innerVar = 'inner';
        console.log(innerVar);  // inner(当前作用域)
        console.log(outerVar);  // outer(向上查找)
        console.log(global);    // global(继续向上查找)
    }
    
    inner();
}

outer();

作用域链查找流程

graph TD
    A[inner 函数查找变量] --> B{inner 作用域有?}
    B -->|有| C[使用当前作用域]
    B -->|无| D[outer 作用域]
    D --> E{outer 作用域有?}
    E -->|有| F[使用 outer 作用域]
    E -->|无| G[全局作用域]
    G --> H{全局作用域有?}
    H -->|有| I[使用全局作用域]
    H -->|无| J[ReferenceError]

七、总结

核心知识点

  1. 作用域类型

    • 全局作用域
    • 函数作用域(局部作用域)
    • 块级作用域(ES6)
  2. 变量声明

    • var:函数作用域,存在变量提升
    • let/const:块级作用域,不存在变量提升
  3. 作用域链

    • 从当前作用域向上查找
    • 直到找到或到达全局作用域
  4. 优先级

    • 函数声明 > 变量声明
    • 变量赋值会覆盖函数声明

常见面试题

Q1: var、let、const 的区别?

答:

  • var:函数作用域,存在变量提升,可重复声明
  • let:块级作用域,不存在变量提升,不可重复声明,存在暂时性死区
  • const:块级作用域,不存在变量提升,不可重复声明,必须初始化,不可重新赋值

Q2: 什么是作用域链?

答:作用域链是 JavaScript 引擎查找变量的一套规则。从当前作用域开始,向上查找,直到找到或到达全局作用域。

Q3: 为什么需要块级作用域?

答:ES5 只有函数作用域,导致循环变量泄露、变量提升等问题。ES6 的块级作用域(let/const)可以解决这些问题。

实战建议

  • ✅ 新项目优先使用 let/const,避免使用 var
  • ✅ 理解作用域链有助于理解闭包
  • ✅ 注意函数声明和变量声明的优先级
  • ✅ 理解变量提升和暂时性死区的区别
#前端面试##面试题#
前端面试小册 文章被收录于专栏

每天更新3-4节,持续更新中... 目标:50天学完,上岸银行总行!

全部评论

相关推荐

双尔:反手回一个很抱歉,经过慎重考虑,您与我的预期暂不匹配,感谢您的投递
点赞 评论 收藏
分享
gelmanspar...:奖学金删掉,自我评价删掉,简历压缩一下,写一页
如果再来一次,你还会学机...
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务