作用域与闭包
作用域的基本概念
作用域决定了变量、函数和对象的可访问范围。JavaScript采用词法作用域(静态作用域),意味着作用域在代码编写阶段就已经确定,而非运行时决定。常见的作用域类型包括全局作用域、函数作用域和块级作用域。
var globalVar = '全局变量'; // 全局作用域
function exampleFunction() {
var functionVar = '函数内变量'; // 函数作用域
console.log(globalVar); // 可访问
console.log(functionVar); // 可访问
if (true) {
let blockVar = '块级变量'; // 块级作用域
console.log(blockVar); // 可访问
}
console.log(blockVar); // ReferenceError
}
变量提升与暂时性死区
var声明的变量会经历变量提升,而let/const则存在暂时性死区(TDZ)。变量提升意味着声明会被提升到作用域顶部,但赋值保留在原位。
console.log(hoistedVar); // undefined
var hoistedVar = '值';
console.log(tdzVar); // ReferenceError
let tdzVar = 'TDZ示例';
函数声明也会提升,且优先级高于变量提升:
foo(); // "函数声明"
function foo() {
console.log("函数声明");
}
var foo = function() {
console.log("函数表达式");
};
闭包的形成原理
闭包是指有权访问另一个函数作用域中变量的函数。当内部函数引用外部函数的变量时,即使外部函数执行完毕,这些变量也不会被垃圾回收。
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
};
}
const counter = outer();
counter(); // 1
counter(); // 2
闭包的实际应用包括模块模式、私有变量和函数柯里化:
// 模块模式
const calculator = (function() {
let memory = 0;
return {
add: function(x) {
memory += x;
},
getResult: function() {
return memory;
}
};
})();
作用域链的运作机制
当访问变量时,JavaScript引擎会沿着作用域链逐级查找。作用域链在函数定义时就已经确定,与调用位置无关。
let global = '最外层';
function outer() {
let middle = '中间层';
function inner() {
let local = '最内层';
console.log(global + middle + local); // 可访问所有层级
}
return inner;
}
const closure = outer();
closure();
常见的内存泄漏场景
不当使用闭包可能导致内存无法释放。典型场景包括意外全局变量、未清理的DOM引用和循环引用。
// 意外全局变量
function leak() {
leakedVar = '本应是局部变量'; // 未声明直接赋值
}
// DOM引用未清理
let elements = {
button: document.getElementById('button')
};
function removeButton() {
document.body.removeChild(elements.button);
// elements.button仍保留引用
}
闭包在异步编程中的应用
闭包在异步操作中能有效保存上下文状态,常见于事件处理、定时器和Promise等场景。
// 事件处理
function setupButtons() {
for (var i = 0; i < 5; i++) {
(function(index) {
document.getElementById(`btn-${index}`).addEventListener('click', function() {
console.log(`按钮 ${index} 被点击`);
});
})(i);
}
}
// Promise链
function fetchSequentially(urls) {
let results = [];
return urls.reduce((chain, url) => {
return chain.then(() => fetch(url))
.then(result => results.push(result));
}, Promise.resolve());
}
块级作用域的实际影响
ES6引入的let/const带来了真正的块级作用域,解决了var在循环和条件语句中的变量泄露问题。
// var的问题
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出3次3
}, 100);
}
// let的解决方案
for (let j = 0; j < 3; j++) {
setTimeout(function() {
console.log(j); // 输出0,1,2
}, 100);
}
块级作用域也影响了函数声明行为:
if (true) {
function blockFunc() { console.log('块内函数'); }
blockFunc(); // ES6环境中可调用
}
blockFunc(); // 可能报错(取决于环境)
闭包与this绑定的关系
闭包中的this绑定需要特别注意,箭头函数可以继承外层this值,而普通函数有自己的this绑定。
const obj = {
value: '对象属性',
traditionalMethod: function() {
return function() {
console.log(this.value); // undefined(非严格模式为window)
};
},
arrowMethod: function() {
return () => {
console.log(this.value); // '对象属性'
};
}
};
obj.traditionalMethod()();
obj.arrowMethod()();
性能优化的考量
虽然闭包功能强大,但过度使用可能影响性能。每个闭包都会维持其作用域链的引用,可能增加内存消耗。
// 低效实现
function heavyClosure() {
const largeData = new Array(1000000).fill('数据');
return function() {
console.log(largeData.length);
};
}
// 优化方案
function optimized() {
const neededData = '仅需的数据';
return function() {
console.log(neededData);
};
}
模块系统与闭包
现代模块系统利用闭包实现封装,CommonJS和ES Modules都基于类似原理。
// CommonJS模块模拟
function require() {
const exports = {};
(function(module, exports) {
// 模块代码
exports.sayHi = function() {
console.log('Hello Module');
};
})({}, exports);
return exports;
}
// ES Module转译后
var _module_ = (function() {
let privateVar = '私有';
return {
publicMethod: function() {
console.log(privateVar);
}
};
})();
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:函数基础
下一篇:函数参数与arguments对象