【JavaScript】🧠JavaScript 的"套娃"艺术:作用域层层嵌套之谜

1,177 阅读3分钟

前言

JavaScript 的作用域和作用域链就像代码世界的"交通规则"🚦,它们决定了变量在哪里能被访问、如何被查找。理解这些概念能让你写出更清晰、更健壮的代码!本文将通过生动的解释和实例,带你轻松掌握这些核心知识点。

一、作用域:变量的"活动范围"📍

1. 什么是作用域?

作用域就是变量的"生活区域"🏠,它决定了:

  1. 变量能被访问的范围

  2. 相同名字的变量在不同区域互不干扰

核心规则:内层作用域可以访问外层变量,外层不能访问内层变量

2. 作用域的类型

🌍 全局作用域:随处可访问

以下情况会创建全局作用域变量:

// 1. 最外层声明的变量
var globalVar = "我是全局变量";

// 2. 未声明直接赋值的变量
function createGlobal() {
  accidentalGlobal = "糟糕,我成全局变量了!";
}
createGlobal();
console.log(accidentalGlobal); // ✅ 输出成功

// 3. 浏览器中的window属性 / Node中的global属性
globalThis.globalProp = "我也是全局的";
console.log(globalProp); // ✅ 输出成功

⚠️ var 的变量提升小秘密:

console.log(hoisted); // undefined ✅ (不会报错)
var hoisted = "我被提升了!";

🏠 函数作用域:函数内的私人空间

var outer = "外部变量";

function myFunction() {
  var inner = "内部变量";
  console.log(outer); // ✅ "外部变量"
  console.log(inner); // ✅ "内部变量"
}

myFunction();
console.log(inner); // ❌ 报错:inner未定义

🧱 块级作用域:ES6的新特性

通过 let 和 const 创建的花括号 {} 作用域:

{
  let blockScoped = "我在块内";
  var notBlockScoped = "我溜出去了";
  console.log(blockScoped); // ✅
}

console.log(notBlockScoped); // ✅
console.log(blockScoped); // ❌ 报错!
var vs let/const 大比拼
特性varlet/const
变量提升✅ 可提前访问❌ 会报错(TDZ)
重复声明✅ 允许❌ 禁止
挂载全局对象✅ 是全局对象属性❌ 不是
作用域类型全局/函数作用域块级作用域
const vs let 小区别
特性constlet
重新赋值❌ 禁止✅ 允许
初始化要求✅ 必须声明时赋值❌ 不需要

🔍 const 的"不变"真相:

const myArray = [1, 2];
myArray.push(3); // ✅ 可以!修改内容没问题
myArray = [4, 5]; // ❌ 报错!不能重新赋值

const myObject = { name: "小明" };
myObject.age = 20; // ✅ 可以!
myObject = { name: "小红" }; // ❌ 报错!

二、作用域链:变量的"寻宝路线图"🗺️

当 JavaScript 需要找一个变量时:

  1. 先在当前作用域找

  2. 找不到就去上一层作用域找

  3. 一直找到全局作用域为止

  4. 如果全局都没有,就报错!

let globalTreasure = "全局宝藏";

function outerFunction() {
  let outerTreasure = "外层宝藏";
  
  function innerFunction() {
    let innerTreasure = "内层宝藏";
    console.log(innerTreasure); // ✅ 内层宝藏
    console.log(outerTreasure); // ✅ 外层宝藏
    console.log(globalTreasure); // ✅ 全局宝藏
  }
  
  innerFunction();
}

outerFunction();

作用域链的实际应用

let name = "全局名字";

function showName() {
  let name = "函数内部名字";
  console.log(name); // 先找当前作用域 → "函数内部名字"
}

showName();
console.log(name); // 全局作用域 → "全局名字"
let drink = "🍵茶";

function prepareDrink() {
  console.log(drink); // 当前作用域没有 → 找外层 → "🍵茶"
}

prepareDrink(); // 输出 🍵茶

总结小贴士 💡

  1. 作用域是变量的"活动范围"

  2. 全局作用域随处可访问,但要小心污染

  3. 函数作用域保护内部变量

  4. 块级作用域用 let/const + {} 创建

  5. 作用域链是从内到外的查找路径