在JavaScript中函数是一等公民。所谓一等公民是指函数跟其他对象一样,很普通,可以进行把函数存在数组中、作为参数传递、赋值给变量等操作。当函数作为另一个函数的返回值在外部调用时,跟该函数在函数内部调用时可访问的词法作用域一样,这种现象被称为闭包。

一、什么是闭包

  闭包的定义有很多,比如:闭包是指有权访问另外一个函数作用域中变量的函数。或者更本质的定义:函数对象可以通过作用域链关联起来,函数体内部的变量都可以保存在函数作用域内,也就是说函数变量可以隐藏于作用域链之内,看上去是函数将变量“包裹”了起来。个人比较倾向于一种通俗的定义:闭包就是函数能够记住并访问它的词法作用域,即使当这个函数在它的词法作用域外执行时

  如下代码所示,执行test函数返回print函数,在全局作用域下执行print函数,print函数却能记住自己的作用域,能够引用其在定义时的外层函数test的局部变量。

var a = 1;

function test (){
var a = 2;
function print (){
console.log(a)
}
return print
} test()() // 2

  由于JavaScript中没有块级作用域的概念,因此常常用立即执行函数(IIFE)来模拟块级作用域。

var a = 1;

(function IIFE(){

    var a = 2;
console.log( a ); // 2 })(); console.log( a ); // 1

  从学术意义上来讲,JavaScript中的每个函数都是闭包:它们都是对象,它们都关联到作用域链。但是从闭包可以在词法作用域外调用也能访问词法作用域的角度来说,IIFE并不是闭包。如下代码所示:在函数test执行时,查找变量a是沿着作用域链逐级查询的,并不能体现闭包的特性。

(function IIFE(){
var a = 1; function test () {
console.log(a)
} test() // 1
})();

二、闭包的原理

  当某个函数执行时,先复制其外层函数的作用域链(如果函数是在全局环境中则作用域链中只有一个全局对象的引用),赋值给一个特殊的内部属性(即[[Scope]])。然后使用this、arguments和其它命名参数的值来初始化函数的变量对象,最后将该变量对象的引用加入到该函数的作用域链中。

  当函数执行完之后,函数的作用域链上的会被删除,相应的变量对象没有了作用域链的引用就会被当做垃圾回收掉。但是闭包的情况却不一样,函数虽然执行完毕,但是函数返回了一个内部函数出去,该内部函数的作用域链上拥有对该函数变量对象的引用,因此函数虽然执行完毕,但该函数的变量对象并没有被销毁,依然可以通过返回的内部函数来访问该函数变量对象上的变量。

  需要特别注意的是:闭包只能取得包含函数中任何变量的最后一个值。在for循环中定义函数表达式尤其能体现出这一点。如下代码所示,我们希望test函数返回的函数数组中存放的是可以打印其下标的函数,但是结果却是全部数字10。原因在于我们错误的认为每次循环时都会对i进行一次复制,事实上嵌套的函数不会将作用域内的私有成员复制一份,也不会对所绑定的变量生成静态快照。test函数返回的函数数组中引用的都是同一个变量i,变量i被共享,循环结束时i的值为10,所以执行函数数组中的任意函数结果都是打印出数字10。

function test () {
var arr = []
for(var i=0;i<10;i++){
arr[i] = function () {
console.log(i)
}
}
return arr
}
var print = test()
print[2]() // 10

  我们对代码加以改进,来避免数据共享的情况发生。在下面代码中,并不是直接将闭包赋值给数组,而是定义了函数temp,将执行temp函数后的返回值赋给数组。因为函数参数是按值传递的,所以每次调用temp时会复制一份实参i的副本,函数数组中保存的函数都有各自的变量i不同时间段的副本,打破了原本共享数据i的情况,因此能够返回各自不同的值。

function test () {
var arr = []
for(var i=0;i<10;i++){
function temp (j) {
return function () {
console.log(j)
}
}
arr[i] = temp(i)
}
return arr
}
var print = test()
print[2]() // 2

三、闭包的用途

  闭包在JavaScript代码中无所不在,主要应用于模块模式以及函数式编程中的柯里化

1、模块模式

  在ES6之前,JavaScript中并没有定义用以支持模块的语言结构,但是可以利用闭包很轻松的实现代码模块化。在函数中定义的变量是函数私有的,在函数之外不能直接访问以及修改函数内部的变量,但是通过函数返回的内部函数能够访问这些变量,返回的内部函数如同暴露在外界的共有接口一样,这种模式被称为模块模式。如下代码所示:

function module () {
var value = 0 function get () {
return value
} function set (val) {
value = val
} return {
get: get,
set: set
}
} var test = module()
var modu = module()
console.log(test.get()) // 0
test.set(1)
console.log(modu.get()) // 0
console.log(test.get()) // 1
test.set(10)
console.log(modu.get()) // 0
console.log(test.get()) // 10

  在模块模式中,模块返回值可以是一个对象,也可以仅仅是一个内部函数。模块只是一个函数,所以它可以接收参数。从上面的代码可以看出函数每次执行返回的闭包是独立的,相互不影响。一般模块在使用的时候采用单例模式,可以用IIFE来实现,如下代码所示:

var module =(
function module () {
var value = 0 function get () {
return value
} function set (val) {
value = val
} return {
get: get,
set: set
}
}
)() console.log(module.get()) // 0
module.set(1)
console.log(module.get()) // 1
module.set(10)
console.log(module.get()) // 10

  综上所述,模块要求两个关键性质:1、作为模块的函数被调用执行。2、该函数的返回值至少用于一个内部函数的引用。

2、柯里化

  柯里化是指把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。柯里化的好处在于提高了适用性,能够实现参数复用的效果。如下代码所示:

function test (a,b,c) {
return a+b+c
}
console.log(test(1,2,3)) // 6 function _test (a) {
return function (b) {
return function (c) {
return a+b+c
}
}
}
console.log(_test(1)(2)(3)) // 6

  _test函数利用函数闭包来实现柯里化的效果,每次调用的函数闭包能够访问上次传入的参数并访问。例如lodash等库封装了通用柯里化的函数,传入一个普通函数,返回一个同等功能的柯里化函数,这一部分会在本系列的后续文章详述。

四、总结

  闭包就是函数能够记住并访问它的词法作用域,即使当这个函数在它的词法作用域外执行时。函数执行完毕后,相应的变量对象没有作用域链的引用就会被当做垃圾被回收,但是如果有闭包情况会变得不一样,闭包的作用域链依然对外部函数的变量对象保持引用,因此外部函数的变量对象不会被销毁,闭包依然能够访问外部函数的变量。

  在JavaScript中没有模块的语法(ES6之前),闭包可以用来实现模块模式。在函数式编程中,柯里化十分常见,可以利用闭包来实现柯里化。

如需转载,烦请注明出处:https://www.cnblogs.com/lidengfeng/p/9158827.html

JavaScript夯实基础系列(二):闭包的更多相关文章

  1. JavaScript夯实基础系列(四):原型

      在JavaScript中有六种数据类型:number.string.boolean.null.undefined以及对象,ES6加入了一种新的数据类型symbol.其中对象称为引用类型,其他数据类 ...

  2. JavaScript夯实基础系列(三):this

      在JavaScript中,函数的每次调用都会拥有一个执行上下文,通过this关键字指向该上下文.函数中的代码在函数定义时不会执行,只有在函数被调用时才执行.函数调用的方式有四种:作为函数调用.作为 ...

  3. JavaScript夯实基础系列(五):类

      JavaScript中没有类,是通过使用构造函数和原型模式的组合来实现类似其它面向对象编程语言中"类"的功能.ES6引入的关键字class,形式上向其它面向对象编程语言靠拢,其 ...

  4. JavaScript夯实基础系列(一):词法作用域

      作用域是一组规则,规定了引擎如何通过标识符名称来查询一个变量.作用域模型有两种:词法作用域和动态作用域.词法作用域是在编写时就已经确定的:通过阅读包含变量定义的数行源码就能知道变量的作用域.Jav ...

  5. 夯实基础系列四:Linux 知识总结

    前言 前三节内容传送门: 夯实基础系列一:Java 基础总结 夯实基础系列二:网络知识总结 夯实基础系列三:数据库知识总结 现在很多公司项目部署都使用的是 Linux 服务器,互联网公司更是如此.对于 ...

  6. 【C++自我精讲】基础系列二 const

    [C++自我精讲]基础系列二 const 0 前言 分三部分:const用法.const和#define比较.const作用. 1 const用法 const常量:const可以用来定义常量,不可改变 ...

  7. 快速掌握JavaScript面试基础知识(二)

    译者按: 总结了大量JavaScript基本知识点,很有用! 原文: The Definitive JavaScript Handbook for your next developer interv ...

  8. JavaScript设计模式基础(二)

    JavaScript 设计模式基础(一) 原型模式 在以类为中心的面向对象编程语言中,类和对象的关系就像铸模和铸件的关系,对象总是从类中创建.而原型编程中,类不是必须的,对象未必从类中创建而来,可以拷 ...

  9. JavaScript笔记基础篇(二)

    基础篇主要是总结一些工作中遇到的技术问题是如何解决的,应为本人属于刚入行阶段技术并非大神如果笔记中有哪些错误,或者自己的一些想法希望大家多多交流互相学习. 1.ToFixed()函数 今天在做Birt ...

随机推荐

  1. 安卓----Spinner

    <?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android=" ...

  2. TProfiler部署文档--笔记

    TProfiler是一个可以在生产环境长期使用的性能分析工具.它同时支持剖析和采样两种方式,记录方法执行的时间和次数,生成方法热点 对象创建热点 线程状态分析等数据,为查找系统性能瓶颈提供数据支持. ...

  3. JSON 的小技巧

    有的时候上游传过来的字段是string类型的,但是我们却想用变成数字来使用. 本来用一个json:",string" 就可以支持了,如果不知道golang的这些小技巧,就要大费周章 ...

  4. Java NIO 概览

    Java面试通关手册(Java学习指南) Github地址:https://github.com/Snailclimb/Java_Guide 一 NIO简介 Java NIO 是 java 1.4 之 ...

  5. 【SAP HANA】新建表以及操作数据(3)

    账号和数据库都创建好之后,接下来就可以创建表了.来见识一下这个所谓“列式”存储方式的表是长啥样的! 一.可视化新建表 然后输入所需栏位,设置好类型和长度: 上图右上角可以看到类型是Column Sto ...

  6. 【机器学习基础】对 softmax 和 cross-entropy 求导

    目录 符号定义 对 softmax 求导 对 cross-entropy 求导 对 softmax 和 cross-entropy 一起求导 References 在论文中看到对 softmax 和 ...

  7. Spring Boot 2.0 图文教程 | 集成邮件发送功能

    文章首发自个人微信公众号: 小哈学Java 个人网站: https://www.exception.site/springboot/spring-boots-send-mail 大家好,后续会间断地奉 ...

  8. 一文了解 Hadoop 运行机制

    大数据技术栈在当下已经是比较成熟的了,Hadoop 作为大数据存储的基石,其重要程度不言而喻,作为一个想从 java 后端转向大数据开发的程序员来说,打好 Hadoop 基础,就相当于夯实建造房屋的地 ...

  9. 基于Win10极简SonarQube C#代码质量分析

    博客有些好些时间未更新了,这几个月的时间里,离开了实习的公司.大学毕了业.来了新公司.转了户口,有点忙,最近总算稍微闲下来了,打算重新拾起博客,坚持写下去. 言归正转,什么是SonarQube ? S ...

  10. k8s使用helm打包chart并上传到腾讯云TencentHub

    本文只涉及Helm的Chart操作,不会对其他知识进行过多描述.至于安装这块,麻烦自行百度吧,一大堆呢. 在容器化的时代,我们很多应用都可以部署在docker,很方便,而再进一步,我们还有工具可以对d ...