闭包
引子: 面试经常会问,谈谈你对闭包的理解。
网红答案很无聊的:闭包就是能够读取其他函数内部变量的函数,闭包是指那些能访问自由变量的函数,闭包能把变量保存在内部中,blabla。大家都从网上百度一下,浪费时间去背诵一些表象,是一件很无趣的事情。
在JS中,闭包对应的概念就是函数。不要把作用域或执行上下文当作闭包。
执行上下文我会在文末进行讲述。
每次我基本都会给面试官一个不同的角度去看,从源头去看这个东西:从 JS 函数创建执行开始讲,一个函数执行过程前后的内存回收过程,这种过程会存在什么问题,该问题就是闭包产生的原因了,闭包其实就是函数执行的一种保护机制,闭包又会带来哪些问题,如何去解决这些问题,闭包的理论作用(用途),再谈谈自己在项目中用闭包实现了什么需求,再可以谈一下 redux 中改造 dispatch 使用了闭包。
简单总结:JS 函数创建执行,存在的问题,解决方案是闭包,闭包坏处,如何解决,闭包作用,何时使用闭包,项目中闭包的实践,闭包的应用。
个性总结:闭包是 JS 函数执行的一种保护机制。
复述一下上面的相关过程,作为闭包的总结。
1. 函数创建、执行
函数创建时,在堆内存中存储字符串。函数执行时,会创建一块栈内存空间,初始化其中的变量,执行函数体,当函数执行完毕时,这块内存空间会被销毁,其中的变量也随之销毁。下次调用该函数时会重新开辟一块新内存空间,重复上述操作。
详细见文章 0005 JS 函数
2. 存在的问题
但这种机制会产生问题。我们假设当前函数内返回一个函数,或者接收一个函数作为参数,且都引用了外层函数的变量,外层函数执行完毕,其所占用的栈内存空间被销毁,里面的变量也随之销毁。但内层函数消费了外层变量,同时内层函数被抛出到外部,如果此时执行内层函数,因引用变量已经被销毁,无法获取引用变量的值。
此时,这种函数执行机制就产生了问题。这也就是闭包诞生的原因。
3. 解决方案:闭包
这时,会把函数和当前执行上下文一起保存到堆内存中,我们把这个整体成为闭包。
根据标准定义,可以把闭包归纳成两个组成部分:
(1)环境:
环境:函数的词法环境(执行上下文的一部分)
标识符列表:函数中用到的未声明的变量
(2)函数体
4. 闭包坏处
(1)内存消耗大,内存泄漏 (2)改变父函数内部变量的值
5. 如何解决
在退出函数之前,将不使用的全局变量全部删除,即手动解除引用 赋值为 null。
6. 闭包作用
(1)模拟块级作用域,使用立即执行函数 IIFE 即可。 (2)模块化,可以实现对私有变量的封装 (3)可以在函数外部读取到函数内部的变量 (4)将内部变量始终保存在内存中
7. 何时使用闭包
希望重用一个对象,又保护对象不被污染篡改时。
防止变量污染的新方案: ES6 let const
8. 项目中闭包的实践
9. 闭包的应用
分别从 React、Vue、设计模式三个纬度进行分享。
9-1. Redux dispatch
function applyMiddleware(...middlewares) {
// 返回 增强版的createStore方法(增强体现在middlewares对dispatch的改造)
return (createStore) => (...args) => {
// ...arg就是reducer,[preloadedState]
const store = createStore(...args)
// 初始化dispatch,不允许构造中间件过程中 调用dispatch
// 后续会被更改,映射到闭包中