目录
词法(lexical)作用域
A.函数作为参数:IIFE匿名闭包
B.函数返回函数,且子函数 调用 父级作用域的变量
优点
私有变量/方法:封装在函数内部,避免全局污染,保护变量不被外部访问和修改
缺点
内存占用:无法被垃圾回收。滥用,会内存泄漏
性能损耗:作用域链的查找,性能损耗
使用场景
函数柯里化(返回函数):参数复用,延迟执行
防抖节流
私有变量
发布-订阅
链式调用
内存泄漏:内存浪费->慢->崩溃
不再使用/为空的引用未被移除:闭包/DOM移除,子节点引用没移除
IIFE:()()(立即调用函数表达式)/自执行匿名函数
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合
根据源代码中声明变量的位置来确定该变量在何处可用。
function init() {
var name = "Mozilla"; // name 是一个被 init 创建的局部变量
function displayName() {
// displayName() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
displayName();
}
init();
嵌套函数可访问声明于它们外部作用域的变量。
闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。在本例子中,myFunc
是执行 makeFunc
时创建的 displayName
函数实例的引用。displayName
的实例维持了一个对它的词法环境(变量 name
存在于其中)的引用。
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
在一些编程语言中,一个函数中的局部变量仅存在于此函数的执行期间。一旦 makeFunc()
执行完毕,你可能会认为 name
变量将不能再被访问。然而,因为代码仍按预期运行,所以在 JavaScript 中情况显然与此不同。
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, 1000);
})(i);
}
//或者
for(var i = 0;i < 5;i++)
{
setTimeout((function(i){
return () => console.log(i);
})(i),1000)
}
因为js作用域生命周期在于内部脚本是否全部执行完毕才会销毁,并且不会带到父级作用域;
因为被下级作用域内 引用,而没有被释放。就导致上级作用域内的变量,等到下级作用域执行完后 或者 当闭包(子函数)不再被引用时才会被释放。
function createCounter() {
let counter = 0
const myFunction = function() {
counter = counter + 1
return counter
}
return myFunction
}
const increment = createCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log('example increment', c1, c2, c3)//1 2 3
JavaScript 没有私有变量的原生支持
var Counter = (function () {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function () {
changeBy(1);
},
decrement: function () {
changeBy(-1);
},
value: function () {
return privateCounter;
},
};
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
function createPubSub() {
// 存储事件及其对应的订阅者
const subscribers = {};
// 订阅事件
function subscribe(event, callback) {
// 如果事件不存在,则创建一个新的空数组
if (!subscribers[event]) {
subscribers[event] = [];
}
// 将回调函数添加到订阅者数组中
subscribers[event].push(callback);
}
// 发布事件
function publish(event, data) {
// 如果事件不存在,则直接返回
if (!subscribers[event]) {
return;
}
// 遍历订阅者数组,调用每个订阅者的回调函数
subscribers[event].forEach((callback) => {
callback(data);
});
}
// 返回订阅和发布函数
return {
subscribe,
publish,
};
}
// 使用示例
const pubSub = createPubSub();
// 订阅事件
pubSub.subscribe("event1", (data) => {
console.log("订阅者1收到事件1的数据:", data);
});
pubSub.subscribe("event2", (data) => {
console.log("订阅者2收到事件2的数据:", data);
});
// 发布事件
pubSub.publish("event1", "Hello");
// 输出: 订阅者1收到事件1的数据: Hello
pubSub.publish("event2", "World");
// 输出: 订阅者2收到事件2的数据: World
function calculator() {
let result = 0;
function add(num) {
result += num;
return this;
}
function subtract(num) {
result -= num;
return this;
}
function multiply(num) {
result *= num;
return this;
}
function divide(num) {
result /= num;
return this;
}
function getResult() {
return result;
}
function clear() {
result = 0;
return this;
}
return {
add,
subtract,
multiply,
divide,
getResult,
clear,
};
}
const calc = calculator();
const result = calc.add(5).subtract(2).divide(3).multiply(6).getResult();
console.log(result); // 输出:6
()
运算符闭合起来。这样不但阻止了外界访问 IIFE 中的变量,而且不会污染全局作用域。()
,通过它,JavaScript 引擎将立即执行该函数。(function () {
// …
})();
(() => {
// …
})();
(async () => {
// …
})();