【前端重温系列】闭包及其涉及知识点的理解
时间已经是2020年了,马上也就2021年了,现如今的发展,ES6的实现几乎现代化浏览器都实现了。红宝书也出第四版了,删除了过旧的知识,引入了ES6,涵盖至ECMAScript 2019新标准。新的基础工具书的推出,自己也该好好重温一遍基础,重新梳理自己脑海的知识点,过时的删除,腾出空间给新知识。当然,内容主要还是从核心知识开始,扩展性涵盖其他知识点。
今天开始从闭包及其涉及知识点开始说起。
闭包的定义:闭包是什么
不同资料对闭包的解释
MDN
函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。
阮一峰
其理解:闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
维基百科
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。捕捉时对于值的处理可以是值拷贝,也可以是名称引用,这通常由语言设计者决定,也可能由用户自行指定(如C++)。
在支持头等函数的语言中,如果函数f内定义了函数g,那么如果g存在自由变量,且这些自由变量没有在编译过程中被优化掉,那么将产生闭包。
闭包和匿名函数经常被用作同义词。但严格来说,匿名函数就是字面意义上没有被赋予名称的函数,而闭包则实际上是一个函数的实例,也就是说它是存在于内存里的某个结构体。如果从实现上来看的话,匿名函数如果没有捕捉自由变量,那么它其实可以被实现为一个函数指针,或者直接内联到调用点,如果它捕捉了自由变量那么它将是一个闭包;而闭包则意味着同时包括函数指针和环境两个关键元素。在编译优化当中,没有捕捉自由变量的闭包可以被优化成普通函数,这样就无需分配闭包结构体,这种编译技巧被称为函数跃升。
自我理解总结
包含了既不是函数参数、也不是函数的局部变量,而是一个不属于当前作用域的变量,相对于当前作用域来说,是一个自由变量的函数,就叫闭包。
闭包包含了自由变量和函数环境
为什么需要闭包,闭包优缺点
闭包作用
- 可以引用外部函数的变量或者参数
- 使该变量或者参数常驻内存,避免被垃圾回收机制所回收
在 js 中变量的作用域属于函数作用域, 在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包可访问上级作用域,即使上级函数执行完, 作用域也不会随之销毁
总结:某个函数在定义时的词法作用域之外的地方被调用,闭包可以使该函数访问定义时的词法作用域
注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
闭包涉及知识:作用域及作用域链
作用域分为词法作用域和动态作用域,Javascript的作用域遵循的就是词法作用域模型
关于词法作用域和动态作用域区别
词法作用域
- 也称为静态作用域。这是最普遍的一种作用域模型
- 在代码书写的时候完成划分,作用域链沿着它定义的位置往外延伸
动态作用域
- 相对“冷门”,但确实有一些语言采纳的是动态作用域,如:Bash 脚本、Perl 等
- 在代码运行时完成划分,作用域链沿着它的调用栈往外延伸
词法作用域及其作用域链
词法(lexical)一词指的是,词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。嵌套函数可访问声明于它们外部作用域的变量。
任何变量(不管包含的是原始值还是引用值)都存在于某个执行上下文中(也称为作用域)。这个上下文(作用域)决定了变量的生命周期,以及它们可以访问代码的哪些部分。执行上下文可以总结如下。
执行上下文(作用域)分全局上下文(全局作用域)、函数上下文(局部作用域)和块级上下文(块级作用域)。
代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数。
函数或块的局部上下文不仅可以访问自己作用域内的变量,而且也可以访问任何包含上下文乃至全局上下文中的变量。
全局上下文只能访问全局上下文中的变量和函数,不能直接访问局部上下文中的任何数据。
变量的执行上下文用于确定什么时候释放内存。
整个代码结构中只有函数可以限定作用域(待考证)
作用域规则优先使用变量提升规则分析
如果当前作用规则中有名字了, 就不考虑外面的名字
闭包涉及知识点:js内存管理
JavaScript是使用垃圾回收的编程语言,开发者不需要操心内存分配和回收。JavaScript的垃圾回收程序可以总结如下。
- 离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除。
- 主流的垃圾回收算法是标记清理,即先给当前不使用的值加上标记,再回来回收它们的内存。
- 引用计数是另一种垃圾回收策略,需要记录值被引用了多少次。JavaScript引擎不再使用这种算法,但某些旧版本的IE仍然会受这种算法的影响,原因是JavaScript会访问非原生JavaScript对象(如DOM元素)。
- 引用计数在代码中存在循环引用时会出现问题。
- 解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。为促进内存回收,全局对象、全局对象的属性和循环引用都应该在不需要时解除引用。
闭包的实现
若函数作为参数被传递
1 | // 函数作为参数被传递 |
函数作为返回值被返回
1 | // 函数作为返回值 |
闭包的应用
实现数据(变量和方法)私有化
函数柯里化(函数式编程
闭包相关例题
1 | for (var i = 1; i < 5; i++) { |
【前端重温系列】闭包及其涉及知识点的理解