前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
      • 前言
      • 什么是闭包
      • 闭包的生命周期
        • 闭包产生原因
        • 闭包的表现形式🤤
        • 产生的条件
        • 销毁闭包
      • 总结
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

聊一聊闭包|博客系列

# 前言

闭包是什么时候被创建的,什么时候被销毁的?具体的实现又是怎么样的?

		var name = "The Window";
        var object = {
            name: "My Object",
            getNameFunc: function () {
                return function () {
                    return this.name;
                };
            }
        };
        alert(object.getNameFunc()());
@前端进阶之旅: 代码已经复制到剪贴板

👆当然了js基础扎实的,一定明白里面的端详,看完一下内容,对于这道题一定就能游刃有余的解决了✊

<details> <summary>点这里看</summary> <pre><blockcode> obj.getNameFunc()()实际上是在全局作用域中调用了匿名函数,this指向了window。这里要理解*函数名与函数功能(或者称函数值)是分割开的,不要认为函数在哪里,其内部的this就指向哪里。匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window </blockcode></pre> </details>

# 什么是闭包

来看看红宝石怎么说的👇

闭包是指有权访问另外一个函数作用域中的变量的函数
@前端进阶之旅: 代码已经复制到剪贴板

先来看看MDN定义👇

函数和对其周围状态(**lexical environment,词法环境**)的引用捆绑在一起构成**闭包**(**closure**)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。
@前端进阶之旅: 代码已经复制到剪贴板

👆上面定义大概就是:闭包是指那些能够访问自由变量的函数。其中自由变量,指在函数中使用的,但既不是函数参数arguments也不是函数的局部变量的变量,其实就是另外一个函数作用域中的变量。

网上对于闭包的定义各种各样,每个人对闭包的理解不同,自然定义就不同,那么我也不去定义闭包,自己意会吧👊
@前端进阶之旅: 代码已经复制到剪贴板

非要理解的话,有两种理解:

:one:闭包是嵌套的内部函数(绝大数人)

:two:包含被引用变量(或函数)的对象(极少数人)

我觉得闭包存在与嵌套的内部函数中😊

我们针对第二个理解,写一个demo👇

function count () {
            var x = 0
            return {
                add() {
                    x++;
                },
                print() {
                    console.log(x)
                }
            }
        }
        let demo = count();
        demo.add()
        demo.print()
        demo.add()
        demo.print()
@前端进阶之旅: 代码已经复制到剪贴板

嗯🙃 只可意会不可言传,往下再看看吧!

# 闭包的生命周期

# 闭包产生原因

首先要明白作用域链的概念,其实也很简单,在ES5中只存在两种作用域

:one:全局作用域:two:局部作用域

当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链。 值得注意的是,每一个子函数都会拷贝上级的作用域,形成一个作用域的链条。 比如:

var x = 1;
function demo() {
  var x = 2
  function demo2() {
    var x = 3;
    console.log(x);//3
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

:point_up:这段代码中,我个人的理解demo函数作用域指向全局作用域(window)和它本身,而demo2函数的作用域指向全局作用域(window)丶demo 和它本身。并且的话,作用域的查找是从最底层开始向上找的,直到找到全局作用域window为止,如果全局作用域还没有找到的话,就会报错❌

闭包产生的本质💯 当前环境中存在指向父级作用域的引用。还是举上面的例子:👇

		var x = 1;
        function demo() {
            var x = 2

            function demo2() {
                console.log(x); 
            }
            return demo2;
        }
        var h = demo();
        h()   // 2
@前端进阶之旅: 代码已经复制到剪贴板

这里h变量会拿到父级作用域的变量,输出2。在当前的环境中,含有对demo2的引用,demo2恰恰引用了window demo 和 本身的作用域。 因此demo2可以访问到demo作用域中的变量。

问题来了? 是不是只有返回函数才会产生闭包呢?❓

回到闭包实质:只需要让父级作用域的引用存在即可 那么我们可以这么做:point_down:

		var demo2;
        function demo() {
            var x = 2

            demo2 = function () {
                console.log(x); 
            }
            
        }
        demo();
        demo2()    // 2
@前端进阶之旅: 代码已经复制到剪贴板

首先让外部函数执行,给demo2赋值,等于demo2现在拥有了window demo 和 自身的作用域的访问权限,那么查找变量x的时候,会逐级的向上去找,在最近的demo作用域找到标示符就返回结果,输出2。

🤭 在这里外部的变量demo2存在父级作用域的引用,因此产生了闭包,形式变了,本质还是没有改变。

# 闭包的表现形式🤤

明白了本质,我们从真实的场景中出发,究竟在哪些地方能体现闭包的存在❓

:one: 返回一个函数,上面已经举例了

:two:作为函数参数传递

var a = 1;
function foo(){
  var a = 2;
  function baz(){
    console.log(a);
  }
  bar(baz);
}
function bar(fn){
  // 这就是闭包
  fn();
}
// 输出2,而不是1
foo();
@前端进阶之旅: 代码已经复制到剪贴板

:three:在定时器、事件监听、Ajax请求、跨窗口通信、Web Workers或者任何异步中,只要使用了回调函数,实际上就是在使用闭包

以下的闭包保存的仅仅是window和当前作用域。

// 定时器
setTimeout(function timeHandler(){
  console.log('2222');
},100)

// 事件监听
$('#div').click(function(){
  console.log('DIV Click');
})
@前端进阶之旅: 代码已经复制到剪贴板

:four:IIFE(立即执行函数表达式)创建闭包, 保存了全局作用域window和当前函数的作用域,因此可以全局的变量。

var x = 22;
(function IIFE(){
  // 输出22
  console.log(x);
})();
@前端进阶之旅: 代码已经复制到剪贴板

# 产生的条件

:one:函数嵌套

:two:内部函数引用了外部函数的数据(变量/函数)

# 销毁闭包

说到闭包的销毁,得先聊一聊的就是V8的垃圾回收机制

V8垃圾回收机制,我想到时候我会开一个新的章节去聊一聊它🤭

大概说一说垃圾回收机制吧:

在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收 如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收

:point_up: 简单来说,如果一个引用对象不被引用,V8的垃圾回收机制会进行销毁,闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。因此可以手动解除对匿名函数的引用,以便释放内存。

缺点就是生成多个不被销毁的私有作用域(堆内存),除了对性能上有影响,如果当前保存的闭包,或者说它的父级作用域存在html元素,是很危险的🚫

注意

闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

当然了,合理的利用闭包也是可以解决很多问题的,我想函数式编程思想一定跟闭包有很多的联系。

比如 柯里化的知识

# 总结

  • 个人理解:闭包产生的本质💯 当前环境中存在指向父级作用域的引用
  • 被引用的变量直到闭包被销毁时才会被销毁
  • 闭包会生成私有作用域,消耗内存大,所以不能滥用闭包,对于引用类型,存储位置在堆内存中,会造成内存泄漏问题,尤其是当闭包中父级作用域存在着html元素时,是很危险的🚫
  • 闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。因此可以手动解除对匿名函数的引用,以便释放内存。
  • 闭包应用场景:模块化与柯里化

← 聊一聊typeof instanceof 实现原理.高阶函数map reduce filter →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
      • 前言
      • 什么是闭包
      • 闭包的生命周期
        • 闭包产生原因
        • 闭包的表现形式🤤
        • 产生的条件
        • 销毁闭包
      • 总结
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合