闭包是 JavaScript 中最强大的特性之一。JavaScript 允许函数嵌套,并且内部函数具有定义在外部函数中的所有变量和函数(以及外部函数能访问的所有变量和函数)的完全访问权限。
但是,外部函数却不能访问定义在内部函数中的变量和函数。这给内部函数的变量提供了一种封装。
此外,由于内部函数可以访问外部函数的作用域,因此当内部函数生存周期大于外部函数时,外部函数中定义的变量和函数的生存周期将比内部函数执行的持续时间要长。当内部函数以某一种方式被任何一个外部函数之外的任何作用域访问时,就会创建闭包。
// 外部函数定义了一个名为“name”的变量
const pet = function (name) {
const getName = function () {
// 内部函数可以访问外部函数的“name”变量
return name;
};
return getName; // 返回内部函数,从而将其暴露给外部作用域
};
const myPet = pet("Vivie");
console.log(myPet()); // "Vivie"
实际上可能会比上面的代码复杂的多。它可以返回一个包含用于操作外部函数的内部变量的方法的对象。
const createPet = function (name) {
let sex;
const pet = {
// 在这个上下文中:setName(newName) 等价于 setName: function (newName)
setName(newName) {
name = newName;
},
getName() {
return name;
},
getSex() {
return sex;
},
setSex(newSex) {
if (
typeof newSex === "string" &&
(newSex.toLowerCase() === "male" || newSex.toLowerCase() === "female")
) {
sex = newSex;
}
},
};
return pet;
};
const pet = createPet("Vivie");
console.log(pet.getName()); // Vivie
pet.setName("Oliver");
pet.setSex("male");
console.log(pet.getSex()); // male
console.log(pet.getName()); // Oliver
在上面的代码中,外部函数的 name 变量对内部函数来说是可访问的,而除了通过内部函数本身,没有其他任何方法可以取得内部的变量。内部函数的内部变量就像对外部参数和变量的保险柜。它们会为内部函数保留“稳定”而又“被封装”的数据参与运行。而这些内部函数甚至不会被分配给一个变量,或者有个名称。
const getCode = (function () {
const apiCode = "0]Eal(eh&2"; // 我们不希望外部能够修改的代码......
return function () {
return apiCode;
};
})();
console.log(getCode()); // "0]Eal(eh&2"
备注: 使用闭包时需要注意许多陷阱!
如果一个闭包的函数定义了一个和外部外部的某个变量名称相同的变量,那么这个闭包将无法引用外部作用域中的这个变量。(内部作用域的变量“覆盖”外部作用域,直至程序退出内部作用域。可以将其视作命名冲突。)
const createPet = function (name) {
// 外部函数定义了一个名为“name”的变量。
return {
setName(name) {
// 闭包函数还定义了一个名为“name”的变量。
name = name; // 我们如何访问外部函数定义的“name”?
},
};
};