Skip to content

闭包

定义

  • 简单定义:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的参数与变量,那么函数 B 就是闭包
  • MDN定义(理论上)

闭包是指那些能够访问自由变量(在函数中使用的,但既不是函数参数也不是函数的局部变量的变量)的函数。

  • 实践

使创建它的上下文已经销毁,它仍然存在,在代码中引用了自由变量 垃圾回收机制没有把当前变量和参数回收掉,这样的操作带来了内存泄漏的影响

通常就是通过闭包间接访问函数内部的变量,也就是说闭包就是能够读取其它函数内部变量的函数

js
function A() {
    let a = 1
    windos.B = function() {
        console.log(a)
    }
}
A()
B() // 1
function A() {
    let a = 1
    windos.B = function() {
        console.log(a)
    }
}
A()
B() // 1
js
function a() {
    let i = 0;
    return function b(){
        console.log(++i)
    }
}
let fn = a()
fn() // 1
fn() // 2
fn() // 3
function a() {
    let i = 0;
    return function b(){
        console.log(++i)
    }
}
let fn = a()
fn() // 1
fn() // 2
fn() // 3

使用时期

需要重用一个变量,又要保护变量不会被污染

特点

  • 参数与变量不会被垃圾回收机制回收

与作用域相比较

  • 全局变量
    • 优:可重用
    • 缺:容易污染
  • 局部变量
    • 优:不会被污染,仅函数内部可用
    • 缺:不可重用

缺点

  • 比普通函数占用更多的内存。
    • 释放:将引用内层函数对象的变量赋值为null
    • 内存泄漏的影响,当内存泄漏到一定程度会影响你的项目运行变得卡顿等等问题

内存泄露

指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

用途

  • 将一个变量长期驻扎在内存当中可用于循环取值
  • 私有变量计数器,外部无法访问,避免全局变量额污染
  • 重用一个变量,又保护变量不被污染的一种机制。

经典例题

js
// 本意输出1 2 3 4 5
for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}
// 错误输出 6 6 6 6 6
// 本意输出1 2 3 4 5
for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}
// 错误输出 6 6 6 6 6
    1. 闭包解决----IIFE(立即执行函数)
js
for (var i = 1; i <= 5; i++) {
    (function (j) {
        setTimeout(function timer() {
            console.log(j)
        }, j * 1000)
    })(i)
}
for (var i = 1; i <= 5; i++) {
    (function (j) {
        setTimeout(function timer() {
            console.log(j)
        }, j * 1000)
    })(i)
}
    1. 闭包解决----函数嵌套
js
for (var i = 1; i <= 5; i++) {
    function b(j){
        setTimeout(function timer() {
            console.log(j)
        }, j * 1000)
    }
    b(i)
}
for (var i = 1; i <= 5; i++) {
    function b(j){
        setTimeout(function timer() {
            console.log(j)
        }, j * 1000)
    }
    b(i)
}
    1. setTimeout的第三个参数
js
for (var i = 1; i <= 5; i++) {
    setTimeout(function timer(j) {
        console.log(j)
    }, i * 1000, i)
}
for (var i = 1; i <= 5; i++) {
    setTimeout(function timer(j) {
        console.log(j)
    }, i * 1000, i)
}
    1. 使用let定义i
js
for (let i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i)
    }, i * 1000)
}
for (let i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i)
    }, i * 1000)
}

结合作用域链

js
var data = [];
for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}
data[0](); // 3
data[1](); // 3
data[2](); // 3
var data = [];
for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}
data[0](); // 3
data[1](); // 3
data[2](); // 3
  1. 执行到 data[0]
js
globalContext = {
    VO:{
        data:[...],
        i:3
    }
}
globalContext = {
    VO:{
        data:[...],
        i:3
    }
}
  1. 执行data[0]
js
data[0]Context = {
    AO:{
        arguments:{
            length:0
        }
    },
    Scope:[AO,globalContext.VO]
}
data[0]Context = {
    AO:{
        arguments:{
            length:0
        }
    },
    Scope:[AO,globalContext.VO]
}
  1. 因为data[0]的AO中没有i,所以从 globalContext.VO 中去查找
  2. data[1],data[2] 同理

使用匿名函数包裹形成闭包

js
var data = [];
for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}
data[0](); // 0
data[1](); // 1
data[2](); // 2
var data = [];
for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}
data[0](); // 0
data[1](); // 1
data[2](); // 2
  1. 在执行到 data[0]
js
globalContext = {
    VO:{
        data:[...],
        i:3
    }
}
globalContext = {
    VO:{
        data:[...],
        i:3
    }
}
  1. 执行data[0]
js
data[0]Context = {
    AO:{
        arguments:{
            length:0
        }
    },
    Scope:[AO,匿名函数Context.AO,globalContext.VO]
}
匿名函数Context= {
    AO:{
        arguments:{
            0:0,
            length:1
        },
        i:0
    }
}
data[0]Context = {
    AO:{
        arguments:{
            length:0
        }
    },
    Scope:[AO,匿名函数Context.AO,globalContext.VO]
}
匿名函数Context= {
    AO:{
        arguments:{
            0:0,
            length:1
        },
        i:0
    }
}
  1. data[0]沿着作用域链找到了匿名函数Context.AO上的i,此时就不会再继续查找了
  2. data[1],data[2] 同理

更新于: