JavaScript夯实基础系列(二):闭包
在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夯实基础系列(二):闭包的更多相关文章
- JavaScript夯实基础系列(四):原型
在JavaScript中有六种数据类型:number.string.boolean.null.undefined以及对象,ES6加入了一种新的数据类型symbol.其中对象称为引用类型,其他数据类 ...
- JavaScript夯实基础系列(三):this
在JavaScript中,函数的每次调用都会拥有一个执行上下文,通过this关键字指向该上下文.函数中的代码在函数定义时不会执行,只有在函数被调用时才执行.函数调用的方式有四种:作为函数调用.作为 ...
- JavaScript夯实基础系列(五):类
JavaScript中没有类,是通过使用构造函数和原型模式的组合来实现类似其它面向对象编程语言中"类"的功能.ES6引入的关键字class,形式上向其它面向对象编程语言靠拢,其 ...
- JavaScript夯实基础系列(一):词法作用域
作用域是一组规则,规定了引擎如何通过标识符名称来查询一个变量.作用域模型有两种:词法作用域和动态作用域.词法作用域是在编写时就已经确定的:通过阅读包含变量定义的数行源码就能知道变量的作用域.Jav ...
- 夯实基础系列四:Linux 知识总结
前言 前三节内容传送门: 夯实基础系列一:Java 基础总结 夯实基础系列二:网络知识总结 夯实基础系列三:数据库知识总结 现在很多公司项目部署都使用的是 Linux 服务器,互联网公司更是如此.对于 ...
- 【C++自我精讲】基础系列二 const
[C++自我精讲]基础系列二 const 0 前言 分三部分:const用法.const和#define比较.const作用. 1 const用法 const常量:const可以用来定义常量,不可改变 ...
- 快速掌握JavaScript面试基础知识(二)
译者按: 总结了大量JavaScript基本知识点,很有用! 原文: The Definitive JavaScript Handbook for your next developer interv ...
- JavaScript设计模式基础(二)
JavaScript 设计模式基础(一) 原型模式 在以类为中心的面向对象编程语言中,类和对象的关系就像铸模和铸件的关系,对象总是从类中创建.而原型编程中,类不是必须的,对象未必从类中创建而来,可以拷 ...
- JavaScript笔记基础篇(二)
基础篇主要是总结一些工作中遇到的技术问题是如何解决的,应为本人属于刚入行阶段技术并非大神如果笔记中有哪些错误,或者自己的一些想法希望大家多多交流互相学习. 1.ToFixed()函数 今天在做Birt ...
随机推荐
- Go的context的问题
Go的context的问题 2017-05-29 最近被由context引发的一个bug坑得不轻,所以反思一下Go的context的问题. context是隐式的约束,没有检测 如果我们写一个函数,比 ...
- 通过jQuery和C#分别实现对.NET Core Web Api的访问以及文件上传
准备工作: 建立.NET Core Web Api项目 新建一个用于Api请求的UserInfo类 public class UserInfo { public string name { get; ...
- 使用Onenote & Evernote & VSC+Markdown构建个人笔记系统
Onenote & Evernote & VSC+Markdown构建个人笔记系统 umeowbing(转载请注明出处) 1 Why 笔记本太多,全部带着太重,查找起来也很麻烦-- 笔 ...
- 认识容器和Docker(一)
前言: 这句话应该是开发人员经常挂在嘴边的吧! “在我的机器上是正常工作的啊,MD,怎么到你这就不行了?” 开发人员就会联想到: 1. 肯定是你环境有问题: 2. 要么就是你个傻*不会用吧: 带着这句 ...
- 浅析JavaScript解析赋值、浅拷贝和深拷贝的区别
文章首发于sau交流学习社区 一.赋值(Copy) 赋值是将某一数值或对象赋给某个变量的过程,分为: 1.基本数据类型:赋值,赋值之后两个变量互不影响 2.引用数据类型:赋**址**,两个变量具有相同 ...
- C++ : 内联函数和引用变量
一.内联函数 内联函数和普通函数的使用方法没有本质区别,我们来看一个例子,下面展示了内联函数的使用方法: #include <iostream> using namespace std; ...
- python接口自动化(十六)--参数关联接口后传(详解)
简介 大家对前边的自动化新建任务之后,接着对这个新建任务操作了解之后,希望带小伙伴进一步巩固胜利的果实,夯实基础.因此再在沙场实例演练一下博客园的相关接口.我们用自动化发随笔之后,要想接着对这篇随笔操 ...
- java~springboot~目录索引
回到占占推荐博客索引 最近写了不过关于java,spring,微服务的相关文章,今天把它整理一下,方便大家学习与参考. java~springboot~目录索引 Java~关于开发工具和包包 Java ...
- 跟踪测试 DbContext ,向"不是真正的 ORM" 说拜拜
FreeSql 发展到现在,已经有两种稳定的开发模式,以下先简单带过一下.后面才是本文的主题. 方法一:基于 helper 的方式,祼用: dotnet add package FreeSql 提供 ...
- Oracle 中的SELECT 关键字(查询、检索)
1. SELECT 关键字用法: 检索单个列:select 列名 from 表名: 例:select ename from emp;检索多个列: select [列1,列2, ... ,列N] fro ...