JavaScript作用域全面總結(jié)(附詳細代碼)
前言
作用域(Scope)是JavaScript中一個核心概念,決定了變量、函數(shù)和對象的可訪問性。以下是JavaScript作用域的全面總結(jié),結(jié)合表格和箭頭圖進行講解。
一、作用域類型
JavaScript 作用域類型詳解
JavaScript 中有四種主要的作用域類型,每種都有不同的特性和使用場景。下面我將結(jié)合具體代碼示例詳細講解每種作用域。
1. 全局作用域(Global Scope)
定義:在所有函數(shù)和代碼塊之外聲明的變量
特點:
• 在任何地方都可以訪問
• 生命周期與應(yīng)用程序相同
• 使用 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. 函數(shù)作用域(Function Scope)
定義:在函數(shù)內(nèi)部聲明的變量
特點:
• 只能在函數(shù)內(nèi)部訪問
• var 聲明的變量具有函數(shù)作用域
• 函數(shù)參數(shù)也屬于函數(shù)作用域
function functionScopeDemo() {
var funcVar = '函數(shù)內(nèi)的var變量';
let funcLet = '函數(shù)內(nèi)的let變量';
if (true) {
var innerVar = 'if塊內(nèi)的var變量'; // 實際上屬于函數(shù)作用域
let innerLet = 'if塊內(nèi)的let變量'; // 屬于塊級作用域
}
console.log(funcVar); // "函數(shù)內(nèi)的var變量"
console.log(funcLet); // "函數(shù)內(nèi)的let變量"
console.log(innerVar); // "if塊內(nèi)的var變量" (可訪問)
console.log(innerLet); // ReferenceError: innerLet is not defined
}
functionScopeDemo();
console.log(funcVar); // ReferenceError: funcVar is not defined
3. 塊級作用域(Block Scope)
定義:由 {} 包圍的代碼塊內(nèi)部的作用域
特點:
• let 和 const 聲明的變量具有塊級作用域
• 適用于 if、for、while、switch 等代碼塊
• var 聲明的變量不受塊級作用域限制
// 塊級作用域示例
if (true) {
var blockVar = '塊內(nèi)的var變量'; // 實際上會提升到函數(shù)或全局作用域
let blockLet = '塊內(nèi)的let變量'; // 真正的塊級作用域
const blockConst = '塊內(nèi)的const常量';
console.log(blockVar); // "塊內(nèi)的var變量"
console.log(blockLet); // "塊內(nèi)的let變量"
console.log(blockConst); // "塊內(nèi)的const常量"
}
console.log(blockVar); // "塊內(nèi)的var變量" (可訪問)
console.log(blockLet); // ReferenceError: blockLet is not defined
console.log(blockConst); // ReferenceError: blockConst is not defined
// for循環(huán)中的塊級作用域
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 導(dǎo)出才能被其他模塊訪問
• 使用 import 導(dǎo)入其他模塊的變量
// moduleA.js
const privateVar = '我是模塊私有變量'; // 模塊作用域,外部無法訪問
export const publicVar = '我是模塊導(dǎo)出變量'; // 可以被其他模塊導(dǎo)入
// moduleB.js
import { publicVar } from './moduleA.js';
console.log(publicVar); // "我是模塊導(dǎo)出變量"
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變量被閉包保護,外部無法直接訪問
立即執(zhí)行函數(shù)表達式(IIFE)的作用域
(function() {
var iifeVar = 'IIFE內(nèi)的變量';
console.log(iifeVar); // "IIFE內(nèi)的變量"
})();
console.log(iifeVar); // ReferenceError: iifeVar is not defined
動態(tài)作用域(this 的綁定)
const obj = {
value: 42,
getValue: function() {
return this.value; // this的綁定在調(diào)用時確定
}
};
console.log(obj.getValue()); // 42 (this指向obj)
const unboundGet = obj.getValue;
console.log(unboundGet()); // undefined (this指向全局或undefined)
總結(jié)表格
| 作用域類型 | 聲明方式 | 可訪問范圍 | 變量提升 | 重復(fù)聲明 | 成為window屬性 |
|---|---|---|---|---|---|
| 全局作用域 | var/let/const | 全局 | var: 是, let/const: 否 | var: 可, let/const: 不可 | var: 是, let/const: 否 |
| 函數(shù)作用域 | var | 函數(shù)內(nèi)部 | 是 | 可 | 否 |
| 塊級作用域 | let/const | 代碼塊內(nèi)部 | 否 | 不可 | 否 |
| 模塊作用域 | const/let | 模塊內(nèi)部 | 否 | 不可 | 否 |
理解這些作用域類型和它們的特性,可以幫助你編寫更清晰、更少bug的JavaScript代碼。
作用域類型對比表
| 作用域類型 | 定義位置 | 可訪問性 | 特點 | 示例 |
|---|---|---|---|---|
| 全局作用域 | 所有函數(shù)和代碼塊之外 | 任何地方都可訪問 | 生命周期與應(yīng)用程序相同 | var globalVar = 'global'; |
| 函數(shù)作用域 | 函數(shù)內(nèi)部 | 僅在函數(shù)內(nèi)部可訪問 | var聲明的變量具有函數(shù)作用域 | function foo() { var local = 'local'; } |
| 塊級作用域 | {}代碼塊內(nèi)部 | 僅在代碼塊內(nèi)部可訪問 | let和const聲明的變量具有塊級作用域 | if(true) { let blockVar = 'block'; } |
| 模塊作用域 | ES6模塊文件內(nèi)部 | 僅在模塊內(nèi)部可訪問 | 每個模塊有自己的作用域 | export const moduleVar = 'module'; |
二、JavaScript 中擁有作用域的元素詳解
JavaScript 中的作用域機制決定了變量和函數(shù)的可訪問性。下面我將詳細講解 JavaScript 中所有擁有作用域的元素及其具體行為。
1. 變量聲明方式的作用域
1.1var聲明
- 作用域類型:函數(shù)作用域
- 特點:
- 在函數(shù)內(nèi)部聲明則為局部變量
- 在函數(shù)外部聲明則為全局變量
- 存在變量提升(hoisting)
- 可重復(fù)聲明
function varExample() {
if (true) {
var x = 10; // 整個函數(shù)內(nèi)都可用
}
console.log(x); // 輸出 10
}
1.2let聲明
- 作用域類型:塊級作用域
- 特點:
- 只在聲明所在的代碼塊內(nèi)有效
- 不存在變量提升
- 不可重復(fù)聲明
- 暫時性死區(qū)(TDZ)
function letExample() {
if (true) {
let y = 20;
console.log(y); // 輸出 20
}
console.log(y); // 報錯: y is not defined
}
1.3const聲明
- 作用域類型:塊級作用域
- 特點:
- 必須初始化
- 不能重新賦值
- 其他特性同
let - 對于對象/數(shù)組,內(nèi)容可修改但引用不可變
const PI = 3.1415; // PI = 3.14; // 報錯 const arr = [1, 2]; arr.push(3); // 允許 // arr = [4,5]; // 報錯
2. 函數(shù)的作用域
2.1 函數(shù)聲明
- 作用域規(guī)則:
- 函數(shù)名綁定在所在作用域
- 函數(shù)體內(nèi)部形成新的作用域
- 存在函數(shù)提升
function outer() {
inner(); // 可以調(diào)用,因為函數(shù)提升
function inner() {
console.log("Inner function");
}
}
2.2 函數(shù)表達式
- 作用域規(guī)則:
- 變量部分遵循變量聲明規(guī)則
- 函數(shù)體內(nèi)部仍是獨立作用域
const myFunc = function() {
// 函數(shù)體作用域
console.log("Function expression");
};
2.3 箭頭函數(shù)
- 特殊作用域特性:
- 沒有自己的
this、arguments、super或new.target - 繼承父級作用域的
this - 更簡潔的詞法作用域綁定
- 沒有自己的
const obj = {
value: 42,
getValue: function() {
setTimeout(() => {
console.log(this.value); // 正確獲取42,因為繼承父作用域this
}, 100);
}
};
3. 代碼塊結(jié)構(gòu)的作用域
3.1if/else語句
if (true) {
let blockScoped = "visible";
var functionScoped = "visible";
}
console.log(functionScoped); // "visible"
console.log(blockScoped); // 報錯
3.2 循環(huán)語句
for (let i = 0; i < 3; i++) {
// i只在循環(huán)體內(nèi)有效
}
console.log(i); // 報錯
for (var j = 0; j < 3; j++) {
// j在整個函數(shù)內(nèi)有效
}
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. 模塊系統(tǒng)的作用域
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 {} // 只在塊內(nèi)可用
}
6. 特殊結(jié)構(gòu)的作用域
6.1 立即執(zhí)行函數(shù)表達式(IIFE)
(function() {
var private = "secret";
})();
console.log(private); // 報錯
6.2with語句(已廢棄)
const obj = { a: 1 };
with (obj) {
console.log(a); // 1
// 創(chuàng)建了一個臨時作用域鏈
}
6.3try/catch語句
try {
throw new Error("test");
} catch (err) { // err只在catch塊內(nèi)有效
console.log(err.message);
}
console.log(err); // 報錯
三、大白話講清楚JavaScript作用域優(yōu)先級
在了解作用域的優(yōu)先級之前,我們首先要知道作用域的鏈式結(jié)構(gòu):
全局作用域 (window/global) │ ├── 函數(shù)A作用域 │ │ │ ├── 函數(shù)B作用域 │ │ │ │ │ └── 塊級作用域 (if/for等) │ │ │ └── 塊級作用域 │ ├── 函數(shù)C作用域 │ └── 塊級作用域
1. 就近原則 - “先看手邊,再找遠處”
想象你在一個多層樓的辦公樓里找打印機:
- 你會先看自己工位旁邊有沒有(當前作用域)
- 沒有的話去部門公共區(qū)找(上一層作用域)
- 還找不到就去公司總打印室(全局作用域)
let printer = "總打印室"; // 全局
function department() {
let printer = "部門打印機"; // 部門級
function employee() {
let printer = "工位打印機"; // 個人
console.log(printer); // 先用"工位打印機"
}
employee();
}
2. 先來后到 - “先聲明者優(yōu)先”
就像排隊買奶茶:
- 先來的顧客先點單(先聲明的變量先被使用)
- 后面的同名聲明會被忽略(
var允許重復(fù)聲明) - 但插隊是不允許的(
let/const不允許重復(fù)聲明)
var drink = "奶茶"; // 第一個顧客 var drink = "咖啡"; // 第二個顧客,替換了前面的 console.log(drink); // 最后買到的是"咖啡" let food = "漢堡"; // let food = "薯條"; // 報錯:不能插隊重復(fù)聲明
3. 內(nèi)外有別 - “里面可以看外面,外面看不到里面”
就像公司保密制度:
- 普通員工(內(nèi)層)可以看到公司公告(外層變量)
- 但公司(外層)看不到員工的私人筆記(內(nèi)層變量)
- 部門之間也互相隔離(不同函數(shù)作用域互不可見)
let companySecret = "今年盈利"; // 公司級
function departmentA() {
let teamNote = "項目進度"; // 部門A私有
console.log(companySecret); // 可以看公司信息
}
// console.log(teamNote); // 報錯:外部不能訪問部門內(nèi)部信息
4. 塊級隔離 - “會議室談話不外傳”
就像公司會議室:
- 在會議室(
{}代碼塊)里討論的事情(let/const變量) - 出了會議室就自動銷毀(不可訪問)
- 但用大喇叭(
var)宣布的全公司都能聽見
{
let meetingTopic = "裁員計劃"; // 只在會議室有效
var announcement = "明年上市"; // 全公司都能聽到
}
// console.log(meetingTopic); // 報錯:會議內(nèi)容不公開
console.log(announcement); // 可以聽到
5. 閉包特例 - “離職員工帶走公司機密”
就像員工離職后:
- 正常應(yīng)該交還門禁卡(銷毀作用域)
- 但如果他記下了密碼(形成閉包)
- 之后還能遠程訪問公司資料(外部訪問內(nèi)部變量)
function createEmployee() {
let salary = 10000; // 公司內(nèi)部數(shù)據(jù)
return {
getSalary: () => salary // 離職員工帶走了訪問權(quán)限
};
}
const exStaff = createEmployee();
console.log(exStaff.getSalary()); // 還能查到工資!
6. 模塊隔離 - “分公司獨立運營”
就像集團子公司:
- 每個子公司(模塊)有自己的資金(變量)
- 要公開的部分得特別聲明(
export) - 其他公司想用必須申請導(dǎo)入(
import)
// 子公司A.js
let budget = 100萬; // 自己知道
export let project = "新項目"; // 對外公開
// 集團公司.js
import { project } from '子公司A';
console.log(project); // 能看見公開項目
// console.log(budget); // 報錯:看不到別家內(nèi)部預(yù)算
記住這些生活化的比喻,下次遇到作用域問題就想想:
- 這個變量是"部門公告"還是"私人筆記"?
- 這個函數(shù)是"在職員工"還是"離職員工"?
- 這段代碼在"工位"、"部門"還是"總公司"層級?
四、作用域最佳實踐
作用域的良好使用是編寫高質(zhì)量JavaScript代碼的關(guān)鍵。下面我將通過具體示例詳細講解作用域的最佳實踐。
1. 變量聲明方式選擇
優(yōu)先使用 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會提升到函數(shù)作用域
}
console.log(temp); // 可以訪問,不符合預(yù)期
}
// 修復(fù)方案 ?
function fixed() {
if (true) {
let temp = '臨時值'; // 限制在塊級作用域
}
console.log(temp); // ReferenceError: 符合預(yù)期
}
2. 縮小變量作用域范圍
將變量聲明在最小必要作用域內(nèi)
// 好 ??
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在這里仍然可訪問
}
循環(huán)中的變量作用域
// 好 ??
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 = '污染全局'; // 直接污染全局命名空間
使用模塊系統(tǒng)
// 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
};
}
}
// 修復(fù)方案 ?
function setupElementsFixed() {
const elements = document.querySelectorAll('.btn');
for (let i = 0; i < elements.length; i++) { // 使用let
elements[i].onclick = function() {
console.log(i); // 正確輸出索引
};
}
}
4. 函數(shù)聲明的位置
避免在塊內(nèi)聲明函數(shù)
// 問題示例 ?
if (true) {
function foo() { console.log('1'); }
} else {
function foo() { console.log('2'); }
}
foo(); // 不同瀏覽器行為不一致
// 修復(fù)方案 ?
let foo;
if (true) {
foo = () => console.log('1');
} else {
foo = () => console.log('2');
}
foo(); // 行為一致
使用函數(shù)表達式
// 好 ??
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'; // 自動創(chuàng)建全局變量
}
總結(jié)表格
| 最佳實踐 | 推薦做法 | 不推薦做法 | 原因 |
|---|---|---|---|
| 變量聲明 | const > let > var | 隨意使用var | 避免變量提升和污染 |
| 作用域范圍 | 最小必要作用域 | 過早聲明或全局聲明 | 減少意外訪問 |
| 全局變量 | IIFE/模塊暴露 | 直接聲明全局變量 | 避免命名沖突 |
| 閉包使用 | 有控制地使用 | 濫用或意外創(chuàng)建 | 內(nèi)存管理 |
| 函數(shù)聲明 | 函數(shù)表達式 | 塊內(nèi)函數(shù)聲明 | 行為一致性 |
| 命名沖突 | 命名空間/模塊 | 簡短無意義命名 | 代碼可維護性 |
| 嚴格模式 | 始終使用 | 不使用 | 避免隱式錯誤 |
通過遵循這些最佳實踐,你可以:
- 減少變量污染和命名沖突
- 提高代碼的可預(yù)測性和可維護性
- 避免常見的作用域陷阱
- 編寫更安全、更高效的JavaScript代碼
通過理解這些作用域規(guī)則,您可以更好地組織代碼結(jié)構(gòu),避免變量污染和命名沖突,編寫出更健壯、可維護的JavaScript代碼。
總結(jié)
到此這篇關(guān)于JavaScript作用域全面總結(jié)的文章就介紹到這了,更多相關(guān)JS作用域內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于jsTree的無限級樹JSON數(shù)據(jù)的轉(zhuǎn)換代碼
基于jsTree的無限級樹JSON數(shù)據(jù)的轉(zhuǎn)換代碼,需要的朋友可以參考下。2010-07-07
Input 特殊事件onpopertychange和oninput
onpopertychange和oninput的區(qū)別。2009-06-06
迅速了解一下ES10中Object.fromEntries的用法使用
這篇文章主要介紹了迅速了解一下ES10中Object.fromEntries的用法使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-03-03
js getBoundingClientRect使用方法詳解
這篇文章主要介紹了js getBoundingClientRect使用方法詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-07-07
如何解決JavaScript中的數(shù)組長度不對的問題
JavaScript?中的數(shù)組長度是一個比較常見的坑,理解數(shù)組長度的工作原理非常重要,下面就跟隨小編一起來了解下如何解決JavaScript中的數(shù)組長度不對的問題吧2024-12-12
javascript 實現(xiàn) 秒殺,團購 倒計時展示的記錄 分享
這篇文章介紹了javascript 實現(xiàn) 秒殺,團購 倒計時展示的記錄方法,有需要的朋友可以參考一下2013-07-07
javascript實現(xiàn)動態(tài)顯示顏色塊的報表效果
本文主要介紹了javascript實現(xiàn)動態(tài)顯示顏色塊的報表效果的相關(guān)知識。具有很好的參考價值。下面跟著小編一起來看下吧2017-04-04

