对于JS 闭包的理解

106 阅读2分钟

说来惭愧,从事前端开发这么久了,其实一直没怎么理解JS的闭包。直到前几天,才恍然大悟,发现其实闭包存在于前端代码的方方面面,尤其在很多框架都是广泛使用的。因此,今天来记录下。

闭包的定义:

  • 闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment词法环境)的引用的组合。

要理解闭包,我觉得重点在于引用二字,因为如果导出的函数没被引用,那么他就是只是一段代码块,只是被定义了而已,只有被导出的函数被引用了,才会形成真正的闭包。

示例:

let m = () => {
    let s = 1; 
    return () => {
        console.log(s);
        s++;
    } 
}
m()(); // 当m函数return的函数被直接调用时,打印结果一直是1
// 当m函数return的函数被赋值给一个变量时,也就是说当m函数return的函数被`引用`给一个变量时,然后调用变量。
let n = m();
n(); // 当m函数return的函数被直接调用时,打印结果是1,2,3,4,5,6,7,8,9 .......
  • 此时调用n函数m函数的作用域中的s变量并没有因为m函数被调用完毕而被释放,反而与m函数return的函数一起被变量n一起被引用的现象就叫闭包。(只有当变量n被垃圾回收之后,这个闭包才会同时被回收,在此之前,闭包与变量n一起存在于内存中)
  • 而第一种函数m return的函数的自调用,因为函数m的return函数并没有被赋值给一个变量,所以他只是一个普通的函数调用,并没有形成闭包现象,在函数m return的函数被调用之后,函数m出栈,函数m的作用域以及作用域中的变量s一起被释放了,因此,每次函数m的自调用,都会重新给其作用域中的变量s赋值为1,以至于打印出来的结果都是1.

项目的应用:

设想一种场景,一个公共函数根据部署环境的不同,拼接出不同的结果输出。

  • util.js:
export const aliasNameFun = (() => {
    let alias = process.env.alias;
    return (urlBody) => {
        return `https://${urlBody}/${alias}`
    }
})();
  • main.js
import { aliasNameFun } from './util.js';
const urlBody = '127.0.0.1';
const baseUrl = aliasNameFun(urlBody);

这个例子就是闭包的一个在项目中的简单应用,此时在main.js中, 导出的自调用函数,被引用赋值给变量aliasNameFun,因此形成了一个闭包,而每次调用函数aliasNameFun时,都会直接return 出我们需要的baseUrl。而util.js中的aliasNameFun函数只会在项目启动的时候被调用一次,而aliasNameFun函数的中的alias则一直存在。