1 词法作用域 vs 动态作用域
JavaScript的作用域系统是语言核心机制之一,决定了变量的可见性和生命周期。与其他语言不同,JavaScript采用词法作用域(静态作用域),即作用域在代码书写阶段就已确定,而非运行时确定。
关键区别:
// 词法作用域示例
var x = 10;
functionfoo() {
console.log(x); // 查找定义时的作用域
}
functionbar() {
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;
functionouter() {
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 闭包:原理、应用与风险
闭包是函数与其词法环境的组合,使内部函数能访问外部函数作用域,即使外部函数已执行完毕:
function createCounter() {
let count = 0; // 被闭包"捕获"的变量
returnfunction() {
count++; // 访问外部词法环境
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1(count状态被保留)
console.log(counter()); // 2
关键点:
在 createCounter
执行后本应释放count
返回的匿名函数保持对 count
的引用形成私有状态空间
模块化开发(封装私有变量):
const bankAccount = (() => {
let balance = 0; // 私有变量
return {
deposit: amount => {
balance += amount;
console.log(`存入${amount},余额:${balance}`);
},
withdraw: amount => {
if (amount > balance) thrownewError("余额不足");
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
}
闭包阻止外部变量被回收,不当使用会导致内存累积:
// 潜在泄漏
functioninitHeavyComponent() {
const bigData = newArray(1000000).fill('*'); // 大对象
returnfunction() {
// 即使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绑定规则与控制方法
默认绑定 | foo() |
|
隐式绑定 | obj.foo() |
|
显式绑定 | foo.call(ctx) |
|
new绑定 | new Foo() |
|
箭头函数 | () => {...} |
代码示例:
// 1. 默认绑定
functiondefaultFn() {
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)
functiongreet() {
console.log(`Hello, ${this.name}`);
}
const bob = { name: "Bob" };
greet.call(bob); // Hello, Bob(this强制=bob)
// 4. new绑定
functionPerson(name) {
this.name = name; // this指向新实例
}
const alice = newPerson("Alice");
console.log(alice.name); // Alice
// 5. 箭头函数(无独立this)
const outerThis = this;
constarrowFn = () => {
console.log(this === outerThis); // true
};
arrowFn();
显式绑定方法:
const obj = { value: 100 };
// 1. call/apply(立即执行)
functionlogValue(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
classButton {
constructor() {
this.text = "Click me";
// 箭头函数继承类实例作为this
this.click = () => {
console.log(`Button text: ${this.text}`);
};
}
}
const btn = newButton();
document.addEventListener("click", btn.click); // 始终指向btn实例
优先级:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
4 执行上下文与调用栈
**执行上下文(Execution Context)**JavaScript代码执行的环境,包含变量对象、作用域链和this绑定 **调用栈(Call Stack)**LIFO(后进先出)结构,管理执行上下文的压栈与出栈 **变量对象(Variable Object)**存储上下文中定义的变量和函数声明 **活动对象(Activation Object)**函数执行时的变量对象,包含 arguments
// 示例代码分析
let globalVar = 'global';
functionouter() {
let outerVar = 'outer';
functioninner() {
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
*/
变量提升(Hoisting)原理:
console.log(a); // undefined(非报错)
var a = 10;
// 实际执行顺序:
// var a = undefined;(提升)
// console.log(a);
// a = 10;
// 函数声明提升
foo(); // "hoisted"
functionfoo() {
console.log("hoisted");
}
// 函数表达式不提升
bar(); // TypeError: bar is not a function
var bar = function() {};
作用域链形成:
function createCounter() {
let count = 0; // 被闭包保持的变量
returnfunction() {
count++;
return count;
};
}
const counter = createCounter();
/* 闭包的作用域链:
[返回的匿名函数AO]
[createCounter的AO(含count)] ← 闭包保留此引用
[Global VO]
*/
总结
JavaScript的核心机制围绕词法作用域、闭包、this绑定和执行上下文构建。词法作用域使代码可预测性增强,闭包实现私有状态和模块化但也需警惕内存泄漏,this的动态绑定提供了灵活性,而执行上下文管理着代码执行的基础环境。掌握这些概念能帮助开发者:
编写更可靠、可维护的代码 理解框架底层原理(如React Hooks依赖闭包) 避免常见陷阱(循环闭包、this丢失等) 构建高性能JavaScript应用
通过显式绑定控制this、合理使用闭包、理解执行栈行为,开发者可充分发挥JavaScript的灵活性与表现力。

优网科技秉承"专业团队、品质服务" 的经营理念,诚信务实的服务了近万家客户,成为众多世界500强、集团和上市公司的长期合作伙伴!
优网科技成立于2001年,擅长网站建设、网站与各类业务系统深度整合,致力于提供完善的企业互联网解决方案。优网科技提供PC端网站建设(品牌展示型、官方门户型、营销商务型、电子商务型、信息门户型、微信小程序定制开发、移动端应用(手机站、APP开发)、微信定制开发(微信官网、微信商城、企业微信)等一系列互联网应用服务。