javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS作用域

JavaScript作用域全面总结(附详细代码)

作者:盛夏绽放

JavaScript的作用域一直以来是前端开发中比较难以理解的知识点,下面这篇文章主要介绍了JavaScript作用域全面总结的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

作用域(Scope)是JavaScript中一个核心概念,决定了变量、函数和对象的可访问性。以下是JavaScript作用域的全面总结,结合表格和箭头图进行讲解。

一、作用域类型

JavaScript 作用域类型详解

JavaScript 中有四种主要的作用域类型,每种都有不同的特性和使用场景。下面我将结合具体代码示例详细讲解每种作用域。

1. 全局作用域(Global Scope)

定义:在所有函数和代码块之外声明的变量

特点

• 在任何地方都可以访问

• 生命周期与应用程序相同

• 使用 var 声明的全局变量会成为 window 对象的属性

// 全局作用域示例
var globalVar = '我是全局变量';
let globalLet = '我也是全局变量,但不会挂到window上';
const globalConst = '我是全局常量';

function checkGlobal() {
    console.log(globalVar);  // "我是全局变量"
    console.log(globalLet);  // "我也是全局变量,但不会挂到window上"
    console.log(window.globalVar);  // "我是全局变量" (var特有)
    console.log(window.globalLet);  // undefined (let不会挂载)
}

checkGlobal();

2. 函数作用域(Function Scope)

定义:在函数内部声明的变量

特点

• 只能在函数内部访问

var 声明的变量具有函数作用域

• 函数参数也属于函数作用域

function functionScopeDemo() {
    var funcVar = '函数内的var变量';
    let funcLet = '函数内的let变量';
    
    if (true) {
        var innerVar = 'if块内的var变量';  // 实际上属于函数作用域
        let innerLet = 'if块内的let变量';  // 属于块级作用域
    }
    
    console.log(funcVar);    // "函数内的var变量"
    console.log(funcLet);    // "函数内的let变量"
    console.log(innerVar);   // "if块内的var变量" (可访问)
    console.log(innerLet);   // ReferenceError: innerLet is not defined
}

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

3. 块级作用域(Block Scope)

定义:由 {} 包围的代码块内部的作用域

特点

letconst 声明的变量具有块级作用域

• 适用于 ifforwhileswitch 等代码块

var 声明的变量不受块级作用域限制

// 块级作用域示例
if (true) {
    var blockVar = '块内的var变量';  // 实际上会提升到函数或全局作用域
    let blockLet = '块内的let变量';  // 真正的块级作用域
    const blockConst = '块内的const常量';
    
    console.log(blockVar);   // "块内的var变量"
    console.log(blockLet);   // "块内的let变量"
    console.log(blockConst); // "块内的const常量"
}

console.log(blockVar);   // "块内的var变量" (可访问)
console.log(blockLet);   // ReferenceError: blockLet is not defined
console.log(blockConst); // ReferenceError: blockConst is not defined

// for循环中的块级作用域
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100); // 输出 0, 1, 2 (每个i有独立作用域)
}

for (var j = 0; j < 3; j++) {
    setTimeout(() => console.log(j), 100); // 输出 3, 3, 3 (共享同一个j)
}

4. 模块作用域(Module Scope)

定义:ES6 模块中声明的变量

特点

• 每个模块文件都有自己的作用域

• 需要使用 export 导出才能被其他模块访问

• 使用 import 导入其他模块的变量

// moduleA.js
const privateVar = '我是模块私有变量'; // 模块作用域,外部无法访问
export const publicVar = '我是模块导出变量'; // 可以被其他模块导入

// moduleB.js
import { publicVar } from './moduleA.js';

console.log(publicVar);  // "我是模块导出变量"
console.log(privateVar); // ReferenceError: privateVar is not defined

5. 作用域的特殊情况

闭包作用域(Closure Scope)

function createCounter() {
    let count = 0; // 闭包作用域
    
    return {
        increment: function() {
            count++;
            return count;
        },
        current: function() {
            return count;
        }
    };
}

const counter = createCounter();
console.log(counter.current()); // 0
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
// count变量被闭包保护,外部无法直接访问

立即执行函数表达式(IIFE)的作用域

(function() {
    var iifeVar = 'IIFE内的变量';
    console.log(iifeVar); // "IIFE内的变量"
})();

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

动态作用域(this 的绑定)

const obj = {
    value: 42,
    getValue: function() {
        return this.value; // this的绑定在调用时确定
    }
};

console.log(obj.getValue()); // 42 (this指向obj)

const unboundGet = obj.getValue;
console.log(unboundGet()); // undefined (this指向全局或undefined)

总结表格

作用域类型声明方式可访问范围变量提升重复声明成为window属性
全局作用域var/let/const全局var: 是, let/const: 否var: 可, let/const: 不可var: 是, let/const: 否
函数作用域var函数内部
块级作用域let/const代码块内部不可
模块作用域const/let模块内部不可

理解这些作用域类型和它们的特性,可以帮助你编写更清晰、更少bug的JavaScript代码。

作用域类型对比表

作用域类型定义位置可访问性特点示例
全局作用域所有函数和代码块之外任何地方都可访问生命周期与应用程序相同var globalVar = 'global';
函数作用域函数内部仅在函数内部可访问var声明的变量具有函数作用域function foo() { var local = 'local'; }
块级作用域{}代码块内部仅在代码块内部可访问letconst声明的变量具有块级作用域if(true) { let blockVar = 'block'; }
模块作用域ES6模块文件内部仅在模块内部可访问每个模块有自己的作用域export const moduleVar = 'module';

二、JavaScript 中拥有作用域的元素详解

JavaScript 中的作用域机制决定了变量和函数的可访问性。下面我将详细讲解 JavaScript 中所有拥有作用域的元素及其具体行为。

1. 变量声明方式的作用域

1.1var声明

function varExample() {
  if (true) {
    var x = 10; // 整个函数内都可用
  }
  console.log(x); // 输出 10
}

1.2let声明

function letExample() {
  if (true) {
    let y = 20;
    console.log(y); // 输出 20
  }
  console.log(y); // 报错: y is not defined
}

1.3const声明

const PI = 3.1415;
// PI = 3.14; // 报错

const arr = [1, 2];
arr.push(3); // 允许
// arr = [4,5]; // 报错

2. 函数的作用域

2.1 函数声明

function outer() {
  inner(); // 可以调用,因为函数提升
  
  function inner() {
    console.log("Inner function");
  }
}

2.2 函数表达式

const myFunc = function() {
  // 函数体作用域
  console.log("Function expression");
};

2.3 箭头函数

const obj = {
  value: 42,
  getValue: function() {
    setTimeout(() => {
      console.log(this.value); // 正确获取42,因为继承父作用域this
    }, 100);
  }
};

3. 代码块结构的作用域

3.1if/else语句

if (true) {
  let blockScoped = "visible";
  var functionScoped = "visible";
}
console.log(functionScoped); // "visible"
console.log(blockScoped); // 报错

3.2 循环语句

for (let i = 0; i < 3; i++) {
  // i只在循环体内有效
}
console.log(i); // 报错

for (var j = 0; j < 3; j++) {
  // j在整个函数内有效
}
console.log(j); // 3

3.3switch语句

switch (x) {
  case 1:
    let result = "one";
    break;
  case 2:
    // result = "two"; // 报错,因为result已在同一作用域声明
    break;
}

3.4 空代码块

{
  let privateVar = "secret";
  const secretKey = "12345";
}
// privateVar 和 secretKey 在这里不可访问

4. 模块系统的作用域

4.1 ES6 模块

// module.js
const privateData = "hidden";
export const publicData = "visible";

// app.js
import { publicData } from './module.js';
console.log(publicData); // "visible"
console.log(privateData); // 报错

4.2 CommonJS 模块(Node.js)

// module.js
const localVar = "local";
module.exports = { publicVar: "public" };

// app.js
const { publicVar } = require('./module');
console.log(publicVar); // "public"
console.log(localVar); // 报错

5. 类(Class)的作用域

5.1 类声明

class MyClass {
  constructor() {
    this.instanceVar = "instance";
  }
  
  method() {
    let localVar = "local";
  }
}

console.log(MyClass); // 类可用
// console.log(localVar); // 报错

5.2 类表达式

const MyClass = class {
  // 类体作用域
};

{
  class PrivateClass {} // 只在块内可用
}

6. 特殊结构的作用域

6.1 立即执行函数表达式(IIFE)

(function() {
  var private = "secret";
})();
console.log(private); // 报错

6.2with语句(已废弃)

const obj = { a: 1 };
with (obj) {
  console.log(a); // 1
  // 创建了一个临时作用域链
}

6.3try/catch语句

try {
  throw new Error("test");
} catch (err) { // err只在catch块内有效
  console.log(err.message);
}
console.log(err); // 报错

三、大白话讲清楚JavaScript作用域优先级

在了解作用域的优先级之前,我们首先要知道作用域的链式结构:

全局作用域 (window/global)
│
├── 函数A作用域
│   │
│   ├── 函数B作用域
│   │   │
│   │   └── 块级作用域 (if/for等)
│   │
│   └── 块级作用域
│
├── 函数C作用域
│
└── 块级作用域

1. 就近原则 - “先看手边,再找远处”

想象你在一个多层楼的办公楼里找打印机:

let printer = "总打印室"; // 全局

function department() {
  let printer = "部门打印机"; // 部门级
  
  function employee() {
    let printer = "工位打印机"; // 个人
    console.log(printer); // 先用"工位打印机"
  }
  
  employee();
}

2. 先来后到 - “先声明者优先”

就像排队买奶茶:

var drink = "奶茶"; // 第一个顾客
var drink = "咖啡"; // 第二个顾客,替换了前面的
console.log(drink); // 最后买到的是"咖啡"

let food = "汉堡";
// let food = "薯条"; // 报错:不能插队重复声明

3. 内外有别 - “里面可以看外面,外面看不到里面”

就像公司保密制度:

let companySecret = "今年盈利"; // 公司级

function departmentA() {
  let teamNote = "项目进度"; // 部门A私有
  console.log(companySecret); // 可以看公司信息
}

// console.log(teamNote); // 报错:外部不能访问部门内部信息

4. 块级隔离 - “会议室谈话不外传”

就像公司会议室:

{
  let meetingTopic = "裁员计划"; // 只在会议室有效
  var announcement = "明年上市"; // 全公司都能听到
}

// console.log(meetingTopic); // 报错:会议内容不公开
console.log(announcement); // 可以听到

5. 闭包特例 - “离职员工带走公司机密”

就像员工离职后:

function createEmployee() {
  let salary = 10000; // 公司内部数据
  
  return {
    getSalary: () => salary // 离职员工带走了访问权限
  };
}

const exStaff = createEmployee();
console.log(exStaff.getSalary()); // 还能查到工资!

6. 模块隔离 - “分公司独立运营”

就像集团子公司:

// 子公司A.js
let budget = 100万; // 自己知道
export let project = "新项目"; // 对外公开

// 集团公司.js
import { project } from '子公司A';
console.log(project); // 能看见公开项目
// console.log(budget); // 报错:看不到别家内部预算

记住这些生活化的比喻,下次遇到作用域问题就想想:

四、作用域最佳实践

作用域的良好使用是编写高质量JavaScript代码的关键。下面我将通过具体示例详细讲解作用域的最佳实践。

1. 变量声明方式选择

优先使用 const

// 好 👍
const MAX_SIZE = 100; // 不可变的值使用const
const user = { name: '张三' }; // 引用类型也可以用const
user.name = '李四'; // 可以修改对象属性

// 差 👎
var MAX_SIZE = 100; // var没有块级作用域
let user = { name: '张三' }; // 如果引用不会改变,不需要用let

需要重新赋值时使用 let

// 好 👍
let count = 0;
count = 1; // 需要重新赋值时使用let

// 差 👎
var count = 0; // var容易造成变量提升问题

避免使用 var

// 问题示例 ❌
function problematic() {
    if (true) {
        var temp = '临时值'; // var会提升到函数作用域
    }
    console.log(temp); // 可以访问,不符合预期
}

// 修复方案 ✅
function fixed() {
    if (true) {
        let temp = '临时值'; // 限制在块级作用域
    }
    console.log(temp); // ReferenceError: 符合预期
}

2. 缩小变量作用域范围

将变量声明在最小必要作用域内

// 好 👍
function processData(data) {
    if (data) {
        const result = transform(data); // 只在需要的地方声明
        console.log(result);
    }
    // result在这里不可访问
}

// 差 👎
function processData(data) {
    let result; // 过早声明
    if (data) {
        result = transform(data);
        console.log(result);
    }
    // result在这里仍然可访问
}

循环中的变量作用域

// 好 👍
for (let i = 0; i < 10; i++) { // 每次迭代都有独立的i
    setTimeout(() => console.log(i), 100); // 输出0-9
}

// 差 👎
for (var i = 0; i < 10; i++) { // 共享同一个i
    setTimeout(() => console.log(i), 100); // 输出10个10
}

3. 避免全局污染

使用IIFE隔离作用域

// 好 👍
(function() {
    const privateVar = '私有变量';
    window.myLib = { // 有选择地暴露到全局
        publicMethod: function() {
            return privateVar;
        }
    };
})();

// 差 👎
var globalVar = '污染全局'; // 直接污染全局命名空间

使用模块系统

// utils.js
const privateHelper = () => '私有方法';
export const publicUtil = () => privateHelper();

// app.js
import { publicUtil } from './utils.js';
console.log(publicUtil()); // 使用模块化的公共方法

4. 闭包的合理使用

有控制地使用闭包

// 好 👍
function createCounter() {
    let count = 0; // 闭包保护的变量
    return {
        increment: () => ++count,
        get: () => count,
        reset: () => { count = 0; }
    };
}

const counter = createCounter();
counter.increment();
console.log(counter.get()); // 1

// 差 👎
let count = 0; // 直接暴露在全局
function increment() {
    return ++count;
}
// 任何人都可以修改count

避免意外的闭包

// 问题示例 ❌
function setupElements() {
    const elements = document.querySelectorAll('.btn');
    for (var i = 0; i < elements.length; i++) {
        elements[i].onclick = function() {
            console.log(i); // 总是输出elements.length
        };
    }
}

// 修复方案 ✅
function setupElementsFixed() {
    const elements = document.querySelectorAll('.btn');
    for (let i = 0; i < elements.length; i++) { // 使用let
        elements[i].onclick = function() {
            console.log(i); // 正确输出索引
        };
    }
}

4. 函数声明的位置

避免在块内声明函数

// 问题示例 ❌
if (true) {
    function foo() { console.log('1'); }
} else {
    function foo() { console.log('2'); }
}
foo(); // 不同浏览器行为不一致

// 修复方案 ✅
let foo;
if (true) {
    foo = () => console.log('1');
} else {
    foo = () => console.log('2');
}
foo(); // 行为一致

使用函数表达式

// 好 👍
const handler = function() { /* 处理逻辑 */ };

// 差 👎
function handler() { /* 处理逻辑 */ }
// 会被提升,可能影响代码可读性

6. 命名冲突避免

使用有意义的命名

// 好 👍
function calculateOrderTotal(order) {
    const taxRate = 0.1;
    return order.subtotal * (1 + taxRate);
}

// 差 👎
function calc(a) {
    const b = 0.1; // 无意义的命名
    return a * (1 + b);
}

使用命名空间

// 好 👍
const MyApp = {};
MyApp.Utils = {
    formatDate: function(date) { /* ... */ },
    validateEmail: function(email) { /* ... */ }
};

// 差 👎
function formatDate(date) { /* ... */ } // 直接放在全局
function validateEmail(email) { /* ... */ }

7. 严格模式的使用

// 好 👍
'use strict';
function strictFunc() {
    undeclaredVar = 'test'; // 会抛出ReferenceError
}

// 差 👎
function sloppyFunc() {
    undeclaredVar = 'test'; // 自动创建全局变量
}

总结表格

最佳实践推荐做法不推荐做法原因
变量声明const > let > var随意使用var避免变量提升和污染
作用域范围最小必要作用域过早声明或全局声明减少意外访问
全局变量IIFE/模块暴露直接声明全局变量避免命名冲突
闭包使用有控制地使用滥用或意外创建内存管理
函数声明函数表达式块内函数声明行为一致性
命名冲突命名空间/模块简短无意义命名代码可维护性
严格模式始终使用不使用避免隐式错误

通过遵循这些最佳实践,你可以:

  1. 减少变量污染命名冲突
  2. 提高代码的可预测性可维护性
  3. 避免常见的作用域陷阱
  4. 编写更安全、更高效的JavaScript代码

通过理解这些作用域规则,您可以更好地组织代码结构,避免变量污染和命名冲突,编写出更健壮、可维护的JavaScript代码。

总结

到此这篇关于JavaScript作用域全面总结的文章就介绍到这了,更多相关JS作用域内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文