1 词法作用域 vs 动态作用域
JavaScript的作用域系统是语言核心机制之一,决定了变量的可见性和生命周期。与其他语言不同,JavaScript采用词法作用域(静态作用域),即作用域在代码书写阶段就已确定,而非运行时确定。
关键区别:
// 词法作用域示例
var x = 10;
function foo() {
console.log(x); // 查找定义时的作用域
}
function bar() {
var x = 20;
foo(); // 输出10(定义时决定)
}
bar();
# 动态作用域(Bash示例)
x=1
function foo() { echo $x; }
function bar() { local x=2; foo; }
bar # 输出2(调用时决定)
核心差异:
- 词法作用域:基于代码的物理结构(函数/块的位置),变量查找路径在编写时固定
- 动态作用域:基于函数的调用栈,变量查找路径在运行时确定
JavaScript实现机制:
// 遮蔽(Shadowing)现象
let y = 1;
function outer() {
let y = 2; // 遮蔽外部的y
if (true) {
let y = 3; // 块级作用域遮蔽
console.log(y); // 3
}
console.log(y); // 2
}
outer();
console.log(y); // 1
说明:内部声明会遮蔽外部同名变量,ES6的let/const支持块级作用域
2 闭包:原理、应用与风险
2.1 形成原理
闭包是函数与其词法环境的组合,使内部函数能访问外部函数作用域,即使外部函数已执行完毕:
function createCounter() {
let count = 0; // 被闭包"捕获"的变量
return function() {
count++; // 访问外部词法环境
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1(count状态被保留)
console.log(counter()); // 2
关键点:
createCounter执行后本应释放count- 返回的匿名函数保持对
count的引用 - 形成私有状态空间
2.2 应用场景
模块化开发(封装私有变量):
const bankAccount = (() => {
let balance = 0; // 私有变量
return {
deposit: amount => {
balance += amount;
console.log(`存入${amount},余额:${balance}`);
},
withdraw: amount => {
if (amount > balance) throw new Error("余额不足");
balance -= amount;
return amount;
}
};
})();
bankAccount.deposit(100); // 存入100,余额:100
bankAccount.withdraw(30); // 成功取出30
// bankAccount.balance 无法直接访问(真正私有)
函数工厂(动态生成函数):
function createMultiplier(factor) {
return num => num * factor; // factor被闭包捕获
}
const double = createMultiplier(2);
console.log(double(5)); // 10(保留factor=2)
const triple = createMultiplier(3);
console.log(triple(5)); // 15(保留factor=3)
解决循环陷阱:
// 问题代码(var无块作用域)
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出3,3,3
}
// 闭包解决方案
for (var i = 0; i < 3; i++) {
(function(j) { // 立即执行函数创建作用域
setTimeout(() => console.log(j), 100); // 输出0,1,2
})(i);
}
// 现代方案(let块作用域)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出0,1,2
}
2.3 内存泄漏风险
闭包阻止外部变量被回收,不当使用会导致内存累积:
// 潜在泄漏
function initHeavyComponent() {
const bigData = new Array(1000000).fill('*'); // 大对象
return function() {
// 即使bigData不再需要,仍被闭包引用
render(bigData);
};
}
const renderFn = initHeavyComponent();
// 即使组件卸载,bigData仍占用内存
优化策略:
// 解决方案1:适时解除引用
function cleanUp() {
renderFn = null; // 手动解除闭包引用
}
// 解决方案2:弱引用
const weakMap = new WeakMap();
function setup(data) {
weakMap.set(element, data); // 弱引用可被GC回收
}
3 this绑定规则与控制方法
3.1 绑定规则
| 规则类型 | 示例 | this指向 |
|---|---|---|
| 默认绑定 | foo() | 全局对象(非严格模式) |
| 隐式绑定 | obj.foo() | 调用对象(obj) |
| 显式绑定 | foo.call(ctx) | 指定对象(ctx) |
| new绑定 | new Foo() | 新创建实例 |
| 箭头函数 | () => {...} | 定义时的外层this |
代码示例:
// 1. 默认绑定
function defaultFn() {
console.log(this); // 浏览器中为window
}
defaultFn();
// 2. 隐式绑定
const person = {
name: "Alice",
greet() {
console.log(`Hello, ${this.name}`);
}
};
person.greet(); // Hello, Alice(this=person)
// 3. 显式绑定(call/apply)
function greet() {
console.log(`Hello, ${this.name}`);
}
const bob = { name: "Bob" };
greet.call(bob); // Hello, Bob(this强制=bob)
// 4. new绑定
function Person(name) {
this.name = name; // this指向新实例
}
const alice = new Person("Alice");
console.log(alice.name); // Alice
// 5. 箭头函数(无独立this)
const outerThis = this;
const arrowFn = () => {
console.log(this === outerThis); // true
};
arrowFn();
3.2 控制this指向
显式绑定方法:
const obj = { value: 100 };
// 1. call/apply(立即执行)
function logValue(msg) {
console.log(`${msg}: ${this.value}`);
}
logValue.call(obj, "通过call"); // 通过call: 100
logValue.apply(obj, ["通过apply"]); // 通过apply: 100
// 2. bind(永久绑定)
const boundFn = logValue.bind(obj);
boundFn("通过bind"); // 通过bind: 100
// 3. 箭头函数固定定义时的this
class Button {
constructor() {
this.text = "Click me";
// 箭头函数继承类实例作为this
this.click = () => {
console.log(`Button text: ${this.text}`);
};
}
}
const btn = new Button();
document.addEventListener("click", btn.click); // 始终指向btn实例
优先级:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
4 执行上下文与调用栈
4.1 核心概念
- 执行上下文(Execution Context):JavaScript代码执行的环境,包含变量对象、作用域链和this绑定
- 调用栈(Call Stack):LIFO(后进先出)结构,管理执行上下文的压栈与出栈
- 变量对象(Variable Object):存储上下文中定义的变量和函数声明
- 活动对象(Activation Object):函数执行时的变量对象,包含
arguments
4.2 生命周期
// 示例代码分析
let globalVar = 'global';
function outer() {
let outerVar = 'outer';
function inner() {
let innerVar = 'inner';
console.log(`${globalVar}-${outerVar}-${innerVar}`);
}
inner();
}
outer();
/* 执行上下文创建过程:
1. 全局上下文创建
- VO: { globalVar: undefined, outer: function }
- Scope Chain: [Global VO]
- this: window
2. 执行outer()时
- 创建outer的AO: { outerVar: undefined, inner: function }
- Scope Chain: [outer AO, Global VO]
- this: window
3. 执行inner()时
- 创建inner的AO: { innerVar: undefined }
- Scope Chain: [inner AO, outer AO, Global VO]
- this: window
4. 执行完成后出栈顺序:inner → outer → global
*/
4.3 关键特性
变量提升(Hoisting)原理:
console.log(a); // undefined(非报错)
var a = 10;
// 实际执行顺序:
// var a = undefined;(提升)
// console.log(a);
// a = 10;
// 函数声明提升
foo(); // "hoisted"
function foo() {
console.log("hoisted");
}
// 函数表达式不提升
bar(); // TypeError: bar is not a function
var bar = function() {};
作用域链形成:
function createCounter() {
let count = 0; // 被闭包保持的变量
return function() {
count++;
return count;
};
}
const counter = createCounter();
/* 闭包的作用域链:
[返回的匿名函数AO]
[createCounter的AO(含count)] ← 闭包保留此引用
[Global VO]
*/
总结
JavaScript的核心机制围绕词法作用域、闭包、this绑定和执行上下文构建。词法作用域使代码可预测性增强,闭包实现私有状态和模块化但也需警惕内存泄漏,this的动态绑定提供了灵活性,而执行上下文管理着代码执行的基础环境。掌握这些概念能帮助开发者:
- 编写更可靠、可维护的代码
- 理解框架底层原理(如React Hooks依赖闭包)
- 避免常见陷阱(循环闭包、this丢失等)
- 构建高性能JavaScript应用
通过显式绑定控制this、合理使用闭包、理解执行栈行为,开发者可充分发挥JavaScript的灵活性与表现力。
1570

被折叠的 条评论
为什么被折叠?



