返回顶部
首页 > 资讯 > 前端开发 > 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://lsjlt.com/news/171245.html(转载时请注明来源链接)

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

猜你喜欢
  • 一文了解你不知道的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+
    2024-04-02
  • 一文了解JavaScript闭包函数
    目录变量作用域闭包的概念闭包的用途闭包的缺点最后总结一下闭包的好处与坏处总结变量作用域 要理解JavaScript闭包,就要先理解JavaScript的变量作用域。 变量的作用域有...
    99+
    2024-04-02
  • 一篇文章带你了解JavaScript-对象
    目录创建对象对象直接量通过new创建对象原型Object.create()属性的查询和设置继承属性访问错误删除属性检测属性序列化对象总结创建对象 对象直接量 对象直接量是由若干名/值...
    99+
    2024-04-02
  • 一篇文章带你了解JavaScript-语句
    目录表达式语句复合语句和空语句复合语句空语句声明语句varfunction条件语句ifif/elseelse ifswitch循环whiledo/whileforfor/in跳转标签...
    99+
    2024-04-02
  • 一篇文章带你了解JavaScript的解构赋值
    目录1. 什么是解构赋值 ?2. 数组的解构赋值2.1) 数组解构赋值的默认值2.2) 数组解构赋值的应用类数组中的应用交换变量的值3. 对象的解构赋值...
    99+
    2024-04-02
  • 一篇文章带你详细了解JavaScript数组
    目录一、数组的作用:二、数组的定义:1.通过构造函数创建数组2.通过字面量的方式创建数组三、数组元素四、数组长度五、数组索引(下标)六、数组注意的问题1.数组中存储的数据可以是不一样...
    99+
    2024-04-02
  • 还不知道Anaconda是什么?读这一篇文章就够了
    目录1 Anaconda介绍概述特点2 conda介绍3 安装Anaconda4 Anaconda的使用配置Anaconda源5 创建虚拟环境并使用5.1 创建虚拟环境5.2 查看所...
    99+
    2023-02-17
    anaconda使用方法 anaconda怎么用 anaconda3使用
  • 一文详解JavaScript中的闭包
    JavaScript 闭包是一种重要的概念,在 JavaScript 编程中被广泛使用。尽管它可能会让初学者感到困惑,但它是理解 JavaScript 语言核心的关键概念之一。本文将深入探讨 JavaScript 闭包,让你了解它是如何工作...
    99+
    2023-05-14
    闭包 前端 JavaScript
  • 不知道该学那一个语言?一文带你了解三门语言
    名字:阿玥的小东东 学习:Python。正在学习c++ 主页:阿玥的小东东 目录 粉丝留言,回答问题 1.首先,初步了解  来源地址:https://blog.csdn.net/m0_64122244/article/detai...
    99+
    2023-09-10
    java 开发语言 python c++
  • JavaScript中你不知道的Object.entries用法
    目录前言1. 使用 for...of 遍历普通对象2. 普通对象与 Map 对象相互转换总结参考前言 平时我们经常会用到 Object 类上的静态方法,例如 Object.keys ...
    99+
    2024-04-02
  • Spring你不知道的一种解耦模式
    目录前言一个例子入门应用Service Locator Pattern剖析Service Locator Pattern总结前言 不知道大家在项目中有没有遇到过这样的场景,根据传入的...
    99+
    2023-01-28
    Spring解耦模式 Spring解耦
  • 一篇小短文让你了解Maven
    简介Apache Maven是一个项目管理及构建工具,主要用于Java项目的构建,Maven还可以用于构建和管理以C#,Ruby,Scala和其他语言编写的项目。Maven解决了构建软件那几方面问题:编译构建传统的项目没有使用Maven大部...
    99+
    2023-06-04
  • 你真的了解JavaScript的作用域与闭包吗
    目录一、作用域二、闭包总结一、作用域 1.作用域总体来说就是根据名称查找变量的一套规则。JS查找变量的方式有两种:LHS和RHS。 LHS(left hand side)大致可以理解...
    99+
    2024-04-02
  • JavaScript的这5个技巧你知道了吗
    JavaScript的这5个技巧你知道了吗,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。谁要是说 JavaScript 是世界上比较好的语言,...
    99+
    2024-04-02
  • 你可能不知道的JavaScript之this指向详解
    目录前言默认绑定,全局对象点石成金,隐式绑定隐式绑定丢失指腹为婚,显式绑定内有乾坤,new 绑定军令如山,箭头函数this 绑定优先级总结相关文献引用链接前言 JavaScript ...
    99+
    2024-04-02
  • 你可能不知道的JavaScript位运算符详解
    目录概览位操作符概览位操作支持多少位?负数的无符号右移-2 >>> 1为什么输出2147483647状态控制权限控制判断奇偶数交换两个变量的值判断整数是否相等判断是...
    99+
    2024-04-02
  • 一篇文章带你了解Spring AOP 的注解
    目录1、xml 的方式实现 AOP①、接口 UserService②、实现类 UserServiceImpl③、切面类,也就是通知类 MyAspect④、AOP配置文件 applic...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作