【JS基础系列】闭包是什么

极客班主任
• 阅读 1104

今天是系列第二篇,主要讲一下闭包相关的问题。
我认为闭包的问题,实质是作用域的问题,所以我会先从作用域相关的问题讲起。

什么是作用域?

作用域就是变量可访问的管理范围(或者说变量的管理范围)。举个例子:班级里要准备大扫除了,班长给班级的值日生安排打扫任务。小明同学负责打扫厕所,小红和小丁同学负责打扫教室,小龙同学负责操场的卫生。那么我们可以说小明的管理范围就是厕所区域,小龙同学的管理范围是操场区域;换个说法就是小明的作用域是厕所,小龙的作用域是操场。

一般作用域可以分为词法作用域(也叫静态作用域)和动态作用域,而js中一般是词法作用域。

什么是词法作用域?

先来给个定义:词法作用域就是变量或者函数在定义的时候就确认了其管理范围,不会改变。<span style="color: red;">概括一下理解就是:只要函数看见的就能访问到。</span>干巴巴的概念看着没劲?来看个例子吧

function a(x){
    var y = x + 4
    function fn(z){
        console.log(z)
    }
    fn(10 * y)
}
a(4) //80
console.log(y) //y is not defined

上面是一个很常规的作用域的例子。a(4)输出的值是80,输出y的时候报错,报错信息是变量y没有找到。我们用作用域的知识分析一下这段代码:

  • 全局作用域:
  • 函数a作用域: x, y
  • 函数fn作用域: z

所以我们看到变量y的管理范围是在a函数中,而我们在全局作用域中输出y的值,显然是不在他的管理范围中,因此报错(找不到该变量)。
这里还有一个很重要的知识点,就是变量的生命周期。

什么是变量的生命周期?

我们把变量和人类比一下,比如说人的生命周期就是指人从出生到死亡的整个过程。所以说变量的生命周期就是从变量的创建到死亡的过程。从变量的生命周期上进行区分可以分成全局作用域和函数作用域。先给他们下个定义:

全局作用域

变量的作用域是全局的,称为全局作用域。

函数作用域

变量只在函数体中定义的,称为函数作用域

上面这个例子我们可以看到变量会在全局作用域和函数作用域下,那全局作用域中和函数作用域中的变量在程序运行过程中会经历一个怎样的过程呢?

  • 全局作用域中的变量一旦被定义,除非主动去销毁,否则在程序停止前将一直保存在内存中。
  • 函数作用域中的变量在函数运行中离开函数之后就会被销毁。

所以我们在全局作用域中找不到变量y,是因为在执行了a函数之后,变量y就不存在了。但是从技术实现上有一种方式能实现在离开函数之后还能函数作用域中的变量不被销毁,那就是闭包。

什么是闭包?

千呼万唤始出来,终于见到了本篇文章的主角了。上面说了要实现在离开函数之后还能访问函数作用域中的变量这样一个功能而引入了闭包这个东西。先不说闭包这个功能怎么实现,咱先给这个东西下个定义吧。

网上比较通俗的定义是:能够访问其他函数内部变量的函数。我对比了其他一些说法,还是这个靠谱一些。这个跟我们上面说的在离开函数之后还能访问函数作用域中的变量这个需求相关联。那它是怎么做到的呢?看栗子:

function fn(){
    var a = 10
    return function say(b){
        console.log(a + b)
    }
}
var getVal = fn()
getVal(5)

这是一个很典型的闭包写法:函数内部返回一个的声明函数。在执行fn之后将返回值赋值给getVal,然后执行getVal。我们可以看到,在执行getVal的时候其实执行的就是函数say,而函数say的作用域是在声明它的时候就确定的。我们可以看到它的词法作用域中有权限访问a,b变量,最后打印a + b的值,打印出15。

所以这里的闭包其实是在做这样的一件事:在执行完fn函数后,它还有能力把变量a保存在内存中,让变量a的生命周期跟全局变量一样永远存储在内存中。但是变量a又是私有的,它只有getVal这个函数才能访问到它,它又不像其他全局变量一样可以被随意访问,被随意修改。那么很自然的我们可以看到闭包有这样的优点。

闭包的优点

  • 能够在离开函数之后继续访问该函数的变量,变量一直保存在内存中。
  • 闭包中的变量是私有的,只有闭包函数才有权限访问它。不会被外面的变量和方法给污染。

闭包的常见用法

  • 使用模块化的封装

下面是index.js引用m.js模块的内容

// 文件:m.js
(function(){
    var name ='Lucy'
    var getName = function(){
        ...
    }
    exports.mode = {
        name,
        getName
    }
})()

// 文件index.js
var {mode} = require('./m.js')
mode.getName()
  • 模仿块级作用域
// 改造前:输出5个5
for(var i=0; i<5; i++){
    console.log(i)
}

// 改造后:输出0-4
for(var i=0; i<5; i++){
    (function(j){
        console.log(j)
    })(i)
}

闭包的缺陷

  • 会增加对内存的使用量,影响性能
  • 不正确的使用闭包会造成内存泄漏

总结

回顾一下本章的主要内容

  • 作用域:指变量可访问的管理范围,与生活中的区域管理结合起来理解就比较容易了。
  • 词法作用域:变量在定义的时候就确定了其管理范围,不会再发生变化。
  • 变量的生命周期:变量从出生到被销毁的过程

    • 全局作用域:作用域在全局的
    • 函数作用域:变量只在函数体中定义的
  • 闭包:能够访问其他函数内部变量的函数
  • 闭包的本质就是在执行函数返回内部的函数赋值给变量A,根据词法作用域,变量A跟内部函数一样有权限对其作用域中的变量进行访问,所以闭包内的变量没有被销毁,依然保存在内存中,从而实现了外部函数对内部变量的访问。

文中还有一些比如变量回收,作用域链,执行上下文,变量提升等知识点因为篇幅问题没有详细的展开,这个在后期会有相关主题的补充。

参考文章

点赞
收藏
评论区
推荐文章
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Dax Dax
4年前
JS核心原理理解闭包
前置概念在正式看闭包之前,我们先来学习一下前置知识,那就是JS中的作用域,我们知道,在ES5之中,作用域分为两种:全局作用域和函数作用域,随着ES6的到来,新增了块级作用域,想更好的理解闭包,那么搞清楚作用域是首要条件全局作用域我们知道,对于变量而言,我们一般会分成两类:全局变量和局部变量,一般定义在最外围环境的为全局变量,定义在函数当中的为局部变量,在we
Souleigh ✨ Souleigh ✨
4年前
JS - 作用域
一、作用域作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合换句话说,作用域决定了代码区块中变量和其他资源的可见性举个例子function myFunction(){    let inVariable  "函数内部变量";}myFunction();//要先执行这个函数,否则根本不知
Karen110 Karen110
3年前
一篇文章带你了解JavaScript作用域
在JavaScript中,对象和函数也是变量。在JavaScript中,作用域是你可以访问的变量、对象和函数的集合。JavaScript有函数作用域:这个作用域在函数内变化。一、本地JavaScript变量一个变量声明在JavaScript函数内部,成为函数的局部变量。局部变量有局部作用域:它们只能在函数中访问。JS://codeherecann
Jacquelyn38 Jacquelyn38
4年前
你不可不知的JS面试题(第三期)
1、什么是闭包?如图所示,闭包就是一个定义在函数内部的函数,其作用是将函数内部和函数外部连接起来。大家知道,作用域的问题,就是在函数内部定义的变量称为局部变量,外部取不到值。下面我们通过代码来更加详细地看一下:function A()       let x  1;       return function B()           c
菜园前端 菜园前端
2年前
一篇文章教会你什么是闭包
原文链接:什么是闭包?闭包的概念并不复杂,但是它的定义比较绕(就像平时经常用到它,却又说不出来是什么)。可以在一个作用域中调用函数的内部函数并访问到该函数中的作用域的成员,这就是闭包。给一个建议,网上闭包的概念可以搜出来一大堆,但是你真的了解它吗?你有去调
Bill78 Bill78
4年前
Python 中的闭包
闭包定义:如果在一个内部函数里,对在外部作用于(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包Python中的闭包原文出处:田小计划(http://www.cnblogs.com/wilber2013/p/4658894.html)闭包(closure)是函数式编程的重要的语法结构
Jacquelyn38 Jacquelyn38
4年前
你所知道的JS变量作用域
变量的作用域,指的是变量在脚本代码中的可读、可写的有效范围,也就是脚本代码中可以使用这个变量的区域。在ES6之前,变量的作用域主要分为全局作用域、局部作用域(也称函数作用域)两种;在ES6及其之后,变量的作用域主要分为全局作用域、局部作用域、块级作用域这3种。相应作用域变量分别称为全局变量、局部变量、块级变量。全局变量声明在所有函数之外;局部变量是在函数体内
Stella981 Stella981
3年前
JavaScript易错知识点整理
前言本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一些ES6的知识点。JavaScript知识点1.变量作用域vara1;functio
Wesley13 Wesley13
3年前
JS作用域和变量提升看这一篇就够了
作用域是JS中一个很基础但是很重要的概念,面试中也经常出现,本文会详细深入的讲解这个概念及其他相关的概念,包括声明提升,块级作用域,作用域链及作用域链延长等问题。什么是作用域第一个问题就是我们要弄清楚什么是作用域,这不是JS独有的概念,而是编程领域中通用的一个概念。我们以下面这个语句为例:letx1;这
Wesley13 Wesley13
3年前
Java连载7
一、变量1.注意点:在同一个“作用域”中,变量名不能重名,但是变量可以重新赋值。2.什么是作用域?答:描述的是变量的有效范围,在范围之内是可以被访问的,只要出了作用域就无法访问(也就是在大括号里面才行)3.关于变量的分类(1)局部变量:在方法体中声明的变量;(2)成员变量:在方法体外声明的变量。4.在不同的作用域中,变量名是可