第二十二天:闭包与 this — JS 面试必考的两个概念
蜗牛往上爬 · 前端工业可视化学习笔记 第 22 篇
函数基础学完了,今天挑战两个"难点":闭包和 this。闭包让函数拥有"私有记忆",this 则是 JS 最容易搞混的机制。不用死记定义——用工业场景(计数器、状态缓存)来理解,一目了然。
目录
一、什么是闭包
闭包 = 函数 + 它能访问的外部变量。最直观的形式:函数返回另一个函数。
// 经典闭包:计数器
function createCounter() {
var count = 0; // 被"关"在闭包里,外部无法直接访问
return function () {
count++; // 内部函数可以访问并修改 count
return count;
};
}
var counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count 一直"活着",每次调用都在上一次基础上加 1
// 两个计数器互不干扰
var counter2 = createCounter();
console.log(counter2()); // 1(独立空间)
为什么需要闭包?
// ❌ 不用闭包:count 暴露在全局,任何代码都能改
var count = 0;
function add() { count++; }
// ✅ 用闭包:count 被保护,只能通过 counter() 修改
var counter = createCounter();
二、闭包的工业场景:状态缓存
/**
* 设备状态查询缓存
* 缓存数据被闭包保护,只能通过方法访问
*/
function createStatusCache() {
var cache = {}; // 私有缓存
return {
get: function (deviceId) {
return cache[deviceId] || null;
},
set: function (deviceId, status) {
cache[deviceId] = status;
},
clear: function () {
cache = {};
}
};
}
var statusCache = createStatusCache();
statusCache.set("CNC-001", "运行中");
console.log(statusCache.get("CNC-001")); // "运行中"
另一个常见场景——带前缀的日志器:
function createLogger(prefix) {
return function (message) {
console.log("[" + prefix + "] " + message);
};
}
var warnLogger = createLogger("告警");
var infoLogger = createLogger("信息");
warnLogger("温度过高"); // [告警] 温度过高
infoLogger("系统启动"); // [信息] 系统启动
三、this 指向:谁调用指向谁
// 场景 1:普通函数调用 → this 指向 window(浏览器)/ global(Node)
function showThis() {
console.log(this);
}
showThis();
// 场景 2:对象方法调用 → this 指向调用它的对象
var device = {
name: "CNC-001",
temp: 65,
showInfo: function () {
console.log("设备:" + this.name + ",温度:" + this.temp);
}
};
device.showInfo(); // "设备:CNC-001,温度:65"
// this === device,因为 device 调用了 showInfo
一句话记住 this:看"点号前面是谁",this 就是谁。
四、this 的坑:赋值后丢失
var device = {
name: "CNC-001",
showInfo: function () {
console.log(this.name); // this 取决于"谁调用"
}
};
device.showInfo(); // "CNC-001" ✅(device 调用)
// ❌ 坑 1:赋值给变量 → 变成普通函数调用
var fn = device.showInfo;
fn(); // undefined(this 指向 window,window.name 不存在)
// ❌ 坑 2:作为回调传递
setTimeout(device.showInfo, 1000);
// undefined!内部相当于:var fn = device.showInfo; fn();
这个坑在事件回调和定时器中非常常见。ES6 箭头函数解决了这个问题——箭头函数没有自己的 this,继承外层的 this。第 12 周详细讲。
五、IIFE 立即执行函数
// IIFE:Immediately Invoked Function Expression(立即执行函数)
// 声明的同时立即执行,创建独立作用域
(function () {
var privateVar = "只在这个作用域内有效";
console.log("IIFE 执行了");
})();
// console.log(privateVar); // ❌ 外部无法访问
// 常见用法:只暴露需要的内容
var counterModule = (function () {
var count = 0; // 私有
return {
increment: function () { count++; },
getValue: function () { return count; }
};
})();
counterModule.increment();
counterModule.increment();
console.log(counterModule.getValue()); // 2
// count 无法被外部直接修改 ✅
IIFE 是 ES6 模块化出现之前的主流"模块"写法,很多老代码库(jQuery 插件等)都在用。
六、总结
🎯 闭包和 this 是 JS 进阶的分水岭。第 12 周的箭头函数会再次提到 this——到那时你会彻底明白为什么箭头函数能解决 this 丢失问题。