闭包的作用:变量的私有化,不让它污染全局作用域。使局部变量拥有全局生命周期
var counter = 0;
// 全局变量: 谁都可以访问、修改
function add() {
counter++;
console.log(counter);
}
function add2() {
counter = 0;
}
add();
add2(); // 其他函数来修改变量
add(); // 变量被污染

改进:
在add函数中再定义一个plus函数,如果只看plus函数,counter++,因为没有声明,所有它是一个全局变量。
function add() {
var counter = 0;
// 局部变量
function plus() {
counter++;
// 如果把plus函数单独看作一个函数,counter没有声明,所有它是全局变量
console.log(counter)
}
}
问题:
如果我们能够访问到plus函数,那么就可以执行函数了,所以我们把plus函数写成匿名函数,并给全局变量plus。
function add() {
var counter = 0;
// 局部变量
plus = function() {
// 使plus也成为全局函数
counter++;
console.log(counter)
}
}
add()
plus()
plus()
先执行add()初始化,即可访问plus函数

改进: 优化
使用函数的立即执行
var plus = (function() {
var counter = 0;
return function() {
counter++;
console.log(counter)
}
})()
plus()
plus()
plus()

闭包进阶:
三要素:
- 嵌套结构的函数
- 内部函数访问外部函数的变量
- 在外部函数的外面,调用内部函数
简单的闭包:
// 闭包
function f() {
var a = 1;
function e() {
a++;
console.log(this)
console.log(a)
}
return e;
}
var e = f(); // 在外部函数之外通过外部函数获取的内部this指向
e();

如果缺少了其中一样,比如不在外部函数的外面调用内部函数,而是直接使用。
普通函数:
// 普通函数
function f1() {
var a1 = 1;
function e1() {
a1++;
console.log(this)
console.log(a1)
}
e1(); // 通过调用f1函数让内部函数自己执行
}

问题: 外部函数一定要return吗
答: 不一定,只要在外部函数外面可以访问到内部函数(声明需要声明在外面)也行。比如个内部函数一个名字,在执行外部函数后,再执行内部函数也可以。如
var fee;
function f1() {
var a1 = 1;
fee = function e1() { // 通过赋值,在外面可以访问到
a1++;
console.log(a1)
}
}

概念:
闭包是由函数以及创建该函数的词法环境组合而成,这个环境包含了这个闭包创建时所能访问的所有局部变量。
类似简易类特性: 闭包工厂(函数) -> 闭包对象
new出来的闭包对象,一个与一个之间没有关系。
闭包对象中的值能够调用是因为长存在内存之中,js会自动处理不使用的闭包对象(回收机制)
// 闭包工厂
function f() {
var a = 1;
function e() {
a++;
console.log(a)
}
return e;
}
// 闭包对象1
var f1 = f();
// 闭包对象2
var f2 = f();

作用:
- 让局部变量变成私有化的长存变量;
- 给事件条用函数传递参数;
常见错误: 循环内使用闭包
假设我们有一个li数组,要给每隔dom上添加一个点击事件,事件内容为打印被点击的索引。
function setClick() {
var arr = document.getElementsByTagName('li');
for(var i = 0; i < arr.length; i++) {
arr[i].onclick = function() {
showId(i);
}
}
}
// 给每个li绑定这个点击事件函数
function showId(id) {
console.log(`li索引是: ${id}`)
}
setClick(); // 执行一下

但执行结果发现,不管点击哪里li,它打印的索引都是5。
这是因为li的事件函数虽然绑定了,但是事件函数并未执行,而索引从i=0, 被for循环到了5,这个i都是在同一个内存中的,直接就是被修改修改,当点击其中一个li的时候,自然也就打印了5。
解决办法有两种:
- 使用立即执行函数(定义在函数内部的匿名闭包)
(function setClick() {
var arr = document.getElementsByTagName('li');
for(var i = 0; i < arr.length; i++) {
(function() {
var id = i; // 这一步不能省略 闭包的条件
arr[id].onclick = function () {
showId(id)
}
})()
}
})();
// 事件函数
function showId(id) {
console.log(`li索引是: ${id}`)
}

- 使用多个闭包:每一次循环在闭包工厂中调用创建闭包对象,使其拥有独立的i
function setClick() {
var arr = document.getElementsByTagName('li');
for(var i = 0; i < arr.length; i++) {
arr[i].onclick = clickMake(i);
// 每一次循环在闭包工厂中调用创建闭包对象,使其拥有独立的i
}
}
// 事件函数
function showId(id) {
console.log(`li索引是: ${id}`)
}
// 闭包工厂
function clickMake(id) {
var id = id; // 可省略
function e() {
showId(id)
}
return e;
}
setClick(); // 执行一下

简写:
(function setClick() {
var arr = document.getElementsByTagName('li');
for(var i = 0; i < arr.length; i++) {
arr[i].onclick = clickMake(i);
// 每一次循环在闭包工厂中调用创建闭包对象,使其拥有独立的i
}
})();
// 事件函数
function showId(id) {
console.log(`li索引是: ${id}`)
}
// 闭包工厂
function clickMake(id) {
return function() {
showId(id)
}
}