大家好,我是有一点想法的thinkmars,目前在准备面试与工作,借着间隙时间学习复习,写一点基础文章,欢迎想找工作的人与我一起学习,一起讨饭吃~
在 JavaScript 中,作用域(Scope)是决定变量和函数可访问性的核心机制。
一、作用域核心概念
-
定义
作用域是变量和函数的可访问范围,决定了代码中哪些部分可以访问某个标识符。
-
词法作用域 vs 动态作用域
- 词法作用域(Lexical Scope):JS 采用词法作用域,作用域在代码书写时确定(如函数定义的位置)。
- 动态作用域 :作用域在代码运行时确定(如
this
的绑定)。
二、作用域类型
1. 全局作用域(Global Scope)
- 在函数或代码块外声明的变量,全局可访问。
- 浏览器中,全局对象是
window
;Node.js 中是global
。
javascript
var globalVar = "I'm global";
console.log(window.globalVar); // 浏览器输出: "I'm global"
2. 函数作用域(Function Scope)
var
声明的变量具有函数作用域。- 在函数内部声明的变量,外部无法访问。
javascript
function foo() {
var localVar = "I'm local";
}
console.log(localVar); // 报错:localVar未定义
3. 块级作用域(Block Scope)
- ES6 引入
let
和const
,支持块级作用域({}
内有效)。
javascript
if (true) {
let blockVar = "I'm block-scoped";
const PI = 3.14;
}
console.log(blockVar); // 报错:blockVar未定义
三、作用域链(Scope Chain)
-
定义
函数执行时,会从当前作用域逐层向外查找变量,形成作用域链。
-
创建过程
- 函数定义时,会保存其外层作用域的引用(即
[[Scopes]]
属性)。 - 执行时,将当前变量对象添加到作用域链前端。
javascriptfunction outer() { let a = 1; function inner() { console.log(a); // 通过作用域链找到 outer 中的 a } inner(); } outer(); // 输出 1
- 函数定义时,会保存其外层作用域的引用(即
四、闭包(Closure)
-
定义
函数与其词法环境的引用捆绑,即使函数在其词法作用域外执行,仍能访问外部变量。
-
经典面试题
javascriptfor (var i = 0; i < 5; i++) { setTimeout(() => console.log(i), 100); // 输出 5 五次 } // 解决方案:使用 let 或闭包 for (let i = 0; i < 5; i++) { setTimeout(() => console.log(i), 100); // 输出 0,1,2,3,4 }
五、变量提升(Hoisting)
-
var 的变量提升
var
声明会被提升到作用域顶部,赋值保留在原地。javascriptconsole.log(hoistedVar); // undefined var hoistedVar = "Hoisted!";
-
let/const 的暂时性死区(TDZ)
let
和const
存在暂时性死区,声明前访问会报错。javascriptconsole.log(tdzVar); // 报错:Cannot access 'tdzVar' before initialization let tdzVar = "TDZ!";
六、面试高频问题
-
解释以下代码的输出:
javascriptlet a = 1; function outer() { console.log(a); let a = 2; } outer(); // 报错:Cannot access 'a' before initialization
答案 :块级作用域内
let
声明导致暂时性死区。 -
如何用闭包实现私有变量?
javascriptfunction Counter() { let count = 0; return { increment: () => count++, getCount: () => count }; } const counter = Counter(); counter.increment(); console.log(counter.getCount()); // 1
七、总结与注意事项
- 优先使用
let/const
:避免变量提升和全局污染。 - 闭包内存泄漏:及时解除不再使用的闭包引用。
- 严格模式 :使用
'use strict'
避免意外全局变量。