iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >一文了解你不知道的JavaScript闭包篇
  • 727
分享到

一文了解你不知道的JavaScript闭包篇

JavaScript 闭包 2022-11-13 19:11:04 727人浏览 独家记忆
摘要

目录前言理解闭包升级版闭包循环和闭包模块小结前言 javascript语言中有一个非常重要又难以掌握,近似神话的概念-闭包。对于有一点JavaScript使用经验但从未真正理解闭包概

前言

javascript语言中有一个非常重要又难以掌握,近似神话的概念-闭包。对于有一点JavaScript使用经验但从未真正理解闭包概念的人来说,理解闭包可以看作是某种意义上的重生。JavaScript中闭包无处不在,我们只需要能够识别并拥抱它。它是基于词法作用域书写代码时所产生的自然结果,在代码中随处可见。

理解闭包

下面用一些代码来解释这个定义:

function foo(){
   var a = 2;
   function bar(){
     console.log(a);//2
   }
   bar();
}
foo()

这是闭包吗?也许是的,但似乎这种方式对必报的定义并不能直接进行观察,也无法明白这个代码片段中闭包是如何工作的。我们很容易地理解词法作用域,而闭包则隐藏在代码之后的神秘阴影里,并不那么容易理解。

下面我们来看一段代码,清晰的展示了闭包:

function foo(){
   var a = 2;
   function bar(){
     console.log(a)
   }
   return bar;
}
var baz = foo();
baz() //2-------这就是闭包的效果。

函数bar的词法作用域能够访问foo()的內部作用域。然后我们将bar()函数本身当作一个值类型进行传递。在这个例子中,我们将bar所引用的函数对象本身当作返回值。在foo()执行后,它的返回值(bar函数)赋值给变量baz并调用baz(),实际上只是通过不同的标识符引用调用了内部的函数baz().foo內部的bar()显示可以被正常执行。

在foo()执行后,通常会期待foo()的整个內部作用域都被销毁,因为我们知道引擎有垃圾回收器来释放不再使用的空间。由于foo()似乎不会在被利用,所以大脑很自然的认为会对其进行回收。

而闭包的神奇之处正是可以阻止这件事的发生。事实上内部作用域依然存在,因此没有被回收。谁在使用这个內部作用域呢?原来是bar()本身在使用。所以拜bar()所声明的位置所赐,它拥有覆盖foo()内部作用域的闭包,使得foo()的作用域能够一直存活,以供bar()在之后任何时间都可以被调用。

bar()依然持有对该作用域的引用,而这个引用就叫做闭包。

当然,传递函数也是可以间接的:

var fn;
function foo(){
    var a = 2;
    function baz(){
        console.log(a);
    }
    fn = baz;
}
function bar(){
   fn()
}
foo();
bar(); //2-------这就是闭包!

无论通过何种手段将內部函数传递到所在的词法作用域之外,它都会持有原始定义作用域的引用,无法在何处执行这个函数都会使用闭包。

升级版闭包

前面的代码片段可能有些死板,并且为了解释如何使用闭包而把代码写的很明显。但其实闭包在实际操作中是个很好玩的工具,而且大家也一定都用过闭包。现在让我们来搞懂这个事实:

function wait(message){
    setTimeout(function timer(){
       console.log(message);
    },1000);
}
wait ("Hello closure")

将一个内部函数(名为timer)传递给setTimeout().timer具有涵盖wait()作用域的闭包,因此还保有对变量message的引用。

wait(...)执行1000毫秒,它的內部作用域并不会消失,timer函数依然保有wait()作用域的闭包。这就是闭包。我不知道你在生活中都会写什么样的代码,但在定时器、事件监听器、ajax请求、跨窗口通信或者任何其他的异步任务,只要你使用了回调函数,实际上就是在使用闭包

var a = 2;
(
  function IIFE(){
    console.log(a)
  }
)()

虽然这段代码可以正常工作,但严格来讲它并不是什么闭包,因为函数并不是在它本身的词法作用域以外执行的。它在定义时所在的作用域中执行(而外部作用域,也就是全局作用域也持有a)。a是通过普通的词法作用域查找而非闭包被发现的。

循环和闭包

要说明闭包,循环for是最常见的例子。

for(var i = 1;i<=5;i++){
  setTimeout(function timer(){
    console.log(i)
  },i*1000)
}

正常情况下,我们对这段代码的行为预期是分别输出数字1~5,每秒一次,每次一个。

但实际上,这段代码在运行时会以每秒一次的频率输出五次6

这是为什么?

首先解释6是从哪里来的。这个循环的终止条件是i不再小于等于5.条件首次成立时i的值是6.因此,输出显示的是循环结束时i的最终值。

其次要清楚,延迟函数的回调会在循环结束才执行。事实上,当给定时器运行时即使每个迭代器中执行的是setTimeout(..,0),所有的回调函数依然是在循环结束后才会被执行,因此会每次输出一个6出来。

那为什么没有获得我们预期的结果呢,从1~5输出?

根据作用域原理,尽管循环中五个输出函数是被分别定义出来的,但是他们都被封闭在一个共享全局作用域下,实际上还是共享一个i

那好,知道解决方法咯,那就不共享一个i,让每一次的值都相互独立,每次都获得对应的值。

for(var i = 1;i<=5;i++){
   (
     function(){
       var j = i;
       setTimeout(function timer(){
         console.log(j)
       },j*1000)
     }
   )()
}

这样,将每一次的i值都传给另一个变量,保证i的实时更新,就可以正常输出1~5了!

知道原因了,举一反三,那是不是也会有其他解决办法呢?

仔细思考我们前面的解决方案。我们使用IIFE函数(立即执行函数)每次迭代时都创建了一个新的作用域。换句话说,我们每次迭代都产生新的块作用域。那是不是可以声明块作用域避免变量共享的问题呢?

for(let i = 1;i<=5;i++){
   setTimeout(function timer(){
      console.log(i)
   },i*1000)
}

很酷是吧?块作用域和闭包联手便可“天下无敌”。

模块

function CoolModule(){
  var something = "cool";
  var another = [1,2,3];
  function doSomething(){
    console.log(something);
  }
  function doAnother(){
    console.log(another.join("!"));
  }
  return {
    doSomething:doSomething,
    doAnother:doAnother
  }
}
var foo = CoolModule();
foo.doSomething();//cool
foo.doAnother();//1!2!3

这个模式在JavaScript中被称为模块,最常见的实现模块模式的方法通常被称为模块暴露。首先,CoolModule()只是一个函数,必须要通过它来创建一个模块实例。如果不执行外部函数,內部作用域和闭包都无法被创建。

其次,CoolModule()返回一个用对象字面量语法{key:value,...}来表示的对象。这个返回的对象中含有对內部对象而不是內部数据变量的引用。我们保持內部数据变量是隐藏且私有的状态。可以将这个对象类型的返回值看作本质上模块的公共api

doSomething()和doAnother()函数具有涵盖模块实例內部作用域的闭包(通过调用CoolModule()实现)。

简单描述一下,模块模式的闭包需要具备两个必要条件。

(1)必须有外部的封闭函数,该函数必须至少被调用一次。(每次调用都会创建一个新的模块实例)。

(2)封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

另外,模块也是普通的函数,因此可以接受参数:

function CoolModule(id){
  function identify(){
    console.log(id)
  }
  return {
    identify:identify
  }
}
var foo1 = CoolModule("foo1");
var foo2 = CoolModule("foo2");
foo1.identify();//"foo1"
foo2.identify();//"foo2"

小结

闭包就好像从JavaScript中分离出来的一个充满神秘色彩的未开化世界,只有最勇敢的人才能够到达那里,但实际上他只是一个标准,显然就是关于如何在函数作为值按需传递的此法环境中书写代码的。

当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这就产生了闭包。

如果没能认出闭包,也不了解它的工作原理,在使用它的过程中就很容易犯错,比如在循环中。但同时闭包也是一个非常强大的工具,可以用多种形式来实现模块等模式。

模块主要有两个特征:

(1)为创建内部工作域而调用了一个包含函数。

(2)包装函数的返回值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数內部作用域的闭包。

以上就是一文了解你不知道的JavaScript闭包篇的详细内容,更多关于JavaScript 闭包的资料请关注编程网其它相关文章!

--结束END--

本文标题: 一文了解你不知道的JavaScript闭包篇

本文链接: https://www.lsjlt.com/news/171245.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 一文了解你不知道的JavaScript闭包篇
    目录前言理解闭包升级版闭包循环和闭包模块小结前言 JavaScript语言中有一个非常重要又难以掌握,近似神话的概念-闭包。对于有一点JavaScript使用经验但从未真正理解闭包概...
    99+
    2022-11-13
    JavaScript 闭包
  • 一文了解你不知道的JavaScript异步篇
    目录事件循环任务队列回调嵌套回调promisepromise调度技巧错误处理promise.all([...])promise.race([...])all和race的变体无法取消的...
    99+
    2022-11-13
    JavaScript 异步
  • 一文了解你不知道的JavaScript生成器篇
    目录前言了解生成器for...ofiterable(可迭代)生成器+promiseasync与await小结前言 在没有JavaScript的生成器概念之前,我们几乎普遍依赖一个假定...
    99+
    2022-11-13
    JavaScript生成器 JS 生成器
  • 一篇文章带你了解JavaScript的包装类型
    目录1、简介2、String1、创建语法2、常用方法3、更多方法3、Number1、语法2、属性3、常用方法4、Boolean总结1、简介 【解释】: 在 JavaScri...
    99+
    2022-11-13
  • 一文了解JavaScript闭包函数
    目录变量作用域闭包的概念闭包的用途闭包的缺点最后总结一下闭包的好处与坏处总结变量作用域 要理解JavaScript闭包,就要先理解JavaScript的变量作用域。 变量的作用域有...
    99+
    2022-11-12
  • 一篇文章带你了解JavaScript-对象
    目录创建对象对象直接量通过new创建对象原型Object.create()属性的查询和设置继承属性访问错误删除属性检测属性序列化对象总结创建对象 对象直接量 对象直接量是由若干名/值...
    99+
    2022-11-12
  • 一篇文章带你了解JavaScript-语句
    目录表达式语句复合语句和空语句复合语句空语句声明语句varfunction条件语句ifif/elseelse ifswitch循环whiledo/whileforfor/in跳转标签...
    99+
    2022-11-12
  • 一篇文章带你了解JavaScript的解构赋值
    目录1. 什么是解构赋值 ?2. 数组的解构赋值2.1) 数组解构赋值的默认值2.2) 数组解构赋值的应用类数组中的应用交换变量的值3. 对象的解构赋值...
    99+
    2022-11-13
  • 还不知道Anaconda是什么?读这一篇文章就够了
    目录1 Anaconda介绍概述特点2 conda介绍3 安装Anaconda4 Anaconda的使用配置Anaconda源5 创建虚拟环境并使用5.1 创建虚拟环境5.2 查看所...
    99+
    2023-02-17
    anaconda使用方法 anaconda怎么用 anaconda3使用
  • 一篇文章带你详细了解JavaScript数组
    目录一、数组的作用:二、数组的定义:1.通过构造函数创建数组2.通过字面量的方式创建数组三、数组元素四、数组长度五、数组索引(下标)六、数组注意的问题1.数组中存储的数据可以是不一样...
    99+
    2022-11-12
  • 不知道该学那一个语言?一文带你了解三门语言
    名字:阿玥的小东东 学习:Python。正在学习c++ 主页:阿玥的小东东 目录 粉丝留言,回答问题 1.首先,初步了解  来源地址:https://blog.csdn.net/m0_64122244/article/detai...
    99+
    2023-09-10
    java 开发语言 python c++
  • Spring你不知道的一种解耦模式
    目录前言一个例子入门应用Service Locator Pattern剖析Service Locator Pattern总结前言 不知道大家在项目中有没有遇到过这样的场景,根据传入的...
    99+
    2023-01-28
    Spring解耦模式 Spring解耦
  • 一文详解JavaScript中的闭包
    JavaScript 闭包是一种重要的概念,在 JavaScript 编程中被广泛使用。尽管它可能会让初学者感到困惑,但它是理解 JavaScript 语言核心的关键概念之一。本文将深入探讨 JavaScript 闭包,让你了解它是如何工作...
    99+
    2023-05-14
    闭包 前端 JavaScript
  • 一篇文章带你了解Spring AOP 的注解
    目录1、xml 的方式实现 AOP①、接口 UserService②、实现类 UserServiceImpl③、切面类,也就是通知类 MyAspect④、AOP配置文件 applic...
    99+
    2022-11-13
  • 不知道该用Go、Django、NumPy还是IDE?这篇文章为你解答!
    在当今的程序开发领域,有很多种编程语言和工具可供选择。但是对于初学者来说,选择正确的语言和工具可能是一项非常困难的任务。本文将为大家介绍四种常见的编程语言和工具:Go、Django、NumPy和IDE,帮助您了解它们的优点和适用场景,以便...
    99+
    2023-11-11
    django numy ide
  • 一篇文章带你了解Python中的类
    目录1、类的定义2、创建对象3、继承总结1、类的定义 创建一个rectangle.py文件,并在该文件中定义一个Rectangle类。在该类中,__init__表示构造方法。其中,s...
    99+
    2022-11-12
  • 你一定不知道的Java Unsafe用法详解
    目录Unsafe是什么如何正确地获取Unsafe对象Unsafe实现CAS锁使用Unsafe创建对象Unsafe加载类总结Unsafe是什么 首先我们说Unsafe类位于rt.jar...
    99+
    2022-11-12
  • 一篇文章带你了解C++的KMP算法
    目录KMP算法步骤1:先计算子串中的前后缀数组NextC++代码:步骤2:查找子串在母串中出现的位置。总结KMP算法 KMP算法作用:字符串匹配 例如母串S = “aaagoogle...
    99+
    2022-11-12
  • 一篇文章带你了解C++中的异常
    目录异常抛出异常基本操作自定义的异常类栈解旋异常接口声明异常变量的生命周期异常的多态c++的标准异常库编写自己的异常类总结异常 在c语言中,对错误的处理总是两种方法: 1,使用整型的...
    99+
    2022-11-13
  • 一篇文章带你了解mybatis的动态SQL
    目录1、动态SQL:if 语句3、动态SQL:if+set 语句4、动态SQL:choose(when,otherwise) 语句5、动态SQL:trim 语句6、动态SQL: SQ...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作