对闭包的粗浅理解

339 阅读3分钟

本文是我在学习闭包时,查阅相关资料,根据自己的理解得出的结论,如有不对,恳请指正。

定义

「函数」与「函数内部能访问到的变量」的总和,就是一个闭包。

或许可以再根据闭包的形成条件简化为:

「子函数」与「子函数能够访问到的父函数的变量」的总和就是闭包。

也有人说闭包是函数,是 「能够访问父函数的变量的」 子函数,我认为不对不错,但不严谨。

闭包是 「子函数」 + 「变量」。

作用

  1. 实现私有成员(变量或方法)
  2. 在内存中维持变量。普通函数执行后销毁内部作用域
  3. 避免全局污染

闭包形成条件

闭包形成条件:函数嵌套函数,且子函数引用父函数的变量。

image-20210914002143782

闭包经典使用场景

1. return 子函数

let n = "global n"

function father() {
    let n = "father n"
    function son() {
      	console.log(n)
        debugger
    }
    return son
}
let foo = father()
foo() // father n

foo 变量存放指向父函数返回值(即子函数)的地址,再调用该地址上的函数。

2. 在父函数中调用子函数(可改写为立即执行函数)

let n = "global n"

function father(){
    let n = "father n"
    function son(){
        console.log(n)
        debugger
    }
    son()
}
father() // father n

内部函数 son 与 外部函数 father 的变量 n 形成了闭包。

可改写为立即执行函数:

let n = "global n";

(function father(){
    let n = "father n"
    function son(){
        console.log(n)
    }
    son()
})() // father n

块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。

提一嘴 立即执行函数:

IIFE(Immediately Invoked Function Expression)

定义:声明一个匿名函数,并立即调用这个函数。

作用:创建一个独立作用域,使得外部访问不了内部变量,同时避免全局污染。

ES5 语法:

( function(){} )()

( function(){}() )

ES6 语法:

( ()=>{} )()

( ()=>{}() ) // 语法错误

3. 函数作为参数

let n = 'global n'

function father() {
    let n = 'father n'
    function son() {
        console.log(n)
    }
    return son
}

function foo(func) {
    let n = 'foo n'
    func()
}
foo(father()) // father n

4. 循环赋值

for(var i = 0; i < 6; i++){
  (function(j){
       setTimeout(function(){
        console.log(j)
    }, 0) 
  })(i)
} // 6 6 6 6 6 6

5. 使用回调函数就是在使用闭包

function father() {
    let n = "father n"
    setTimeout(() => {
        console.log(n)
      	debugger
    }, 0)
}
father() // father n

father 函数包裹 setTimeout 函数,setTimeout 函数引用了其父函数的变量 n,形成了闭包。

6. 包裹多个子函数

function father() {
    let n = "father n"
    function son1() {
        console.log(n) // son1 引用父函数的变量 n
    }
    function son2() {
        // console.log(n)  // son2 不引用父函数的变量 n
        debugger
    }
    son2() // 不调用 son1
}
father()

因为闭包作用域是内部所有子函数共享的,只要有一个子函数使用到了父函数中的变量即可形成闭包。

闭包与类

闭包与类是对同一事物的不同表达。

function person(name) {
    this.name = name
    this.age = "22"

    function sayHi(){
        console.log(`Hello, I'm ${name}`)
      	debugger
        return [name, age]
    }
    return sayHi
}
let p1 = person("Tom")
console.log(p1())
// Hello, I'm Tom
// ["Tom","22"]
// 使用 ES6 class 语法
class Person {
    constructor(name) {
        this.name = name
        // this.sayHi = this.sayHi.bind(this);
    }
    age = "22"
    
    sayHi() {
        console.log(`Hello, I'm ${this.name}`)
    }
}

let p1 = new Person("Tom")
p1.sayHi()
console.log(p1)
// Hello, I'm Tom
// {"age":"22","name":"Tom"}

内存泄露

闭包不会造成内存泄漏,老浏览器(主要是 IE6)由于垃圾回收的 bug,容易出现内存泄漏。

滥用闭包才会造成内存泄漏,因为闭包包含其父函数的作用域,比普通函数占有更多的内存空间。

参考资料