前言
在前端开发的时候,JavaScript的变量可以是局部变量或全局变量,当需要使用局部私有变量的时候,就涉及到闭包相关的内容。关于JS中闭包的使用是一个非常重要的知识点,也是JS中的一个难点,不管是在实际开发过程中的时候,尤其是在高级应用中必须依靠闭包来操作实现;还是在求职面试的时候,都是必备知识点。那么本篇博文就来分享一下关于JS中闭包的相关知识,记录一下,方便查阅使用。
什么是闭包
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。(释义来源于维基百科)
通俗的说,闭包就是能够读取其他函数内部变量的函数。由于在 JavasSript 语言中,只有函数内部的子函数才能读取局部变量,所以闭包可以简单理解成"定义在一个函数内部的函数"。在本质上看,闭包是将函数内部和函数外部连接起来的桥梁。
作用域
变量的作用域分为:全局变量和局部变量(私有变量)。JS中变量作用域有点特殊,那是因为在函数内部可以直接获取到全局变量,但是在函数外面无法获取函数内部的局部变量(私有变量。在Javascript中,每一个函数体对应于一个作用域,这有点像原型链,而且JS中的变量是遵从作用域链规则,这就是 JavasSript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之,则不成立。
在访问一个变量的时候,会先访问当前作用域内是否包含有定义的该变量,若没有,就会在该作用域外的作用域内继续寻找是否包含有该变量,依此类推,直到寻找到全局变量;若全局变量中依然没有定义该变量,那么就会返回undefined。
注意:在函数内部声明变量的时候,必须要使用 var 命令,若不使用var,实际上声明的是一个全局的变量。
闭包作用
- 为的是可以读取函数内部的变量;
- 让函数体内定义的变量的值始终保持在内存中;
- 访问函数内部变量、保持函数在环境中一直存在,在调用结束后不被垃圾回收机制回收处理;
- 模拟出可以实现面向对象,使得不同对象拥有独立的成员和状态,达到互不干涉的目的。
私有方法
在编程语言里面,如Java语言是支持将方法声明为私有的,即它这些私有方法只能被同一个类中的其它方法所调用。但是JavaScript语言没有这种原生的支持,但开发者可以使用闭包来模拟私有的方法。
私有方法不仅有利于限制对代码的访问;而且还提供了能够管理全局命名空间的功能,避免非核心方法污染了代码中的公共接口部分内容。
闭包的优点
- 使用起来非常灵活和方便,方便调用上下文的局部变量;
- 加强程序的封装特性,可以对变量起到保护作用;
- 保护函数内部的变量的安全性,防止被环境污染;
- 在内存中,维持一个变量 ;
- 可以保持逻辑连续性,当闭包作为另一个函数调用的参数的时候,可以避免脱离当前逻辑而另行编写其他额外逻辑。
闭包的缺点
- 由于闭包会使得函数中的变量都被保存在内存中,使得内存消耗很大,是常存内存,会增加内存的使用量,若使用不当,很容易引起内存泄露;
- 内存空间浪费对问题,这个内存浪费不仅因为它常驻内存,更重要的是对闭包的使用不当会造成无效内存的产生;
- 闭包会在父函数外部,改变父函数内部变量的值,如果把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定不要随便改变父函数内部变量的值,否则会引起严重的问题。
- 由于闭包自己的局部活动对象和依赖到的活动对象都会被包含在它自身的作用域链里面,所以它的体量往往是比普通函数大很多,性能消耗大;
闭包的特性
- 闭包一般是函数嵌套函数;
- 闭包内部函数可以访问外部函数的变量;
- 闭包的参数和变量在使用后不会被垃圾回收机制所回收。
使用闭包的注意点
- 由于闭包会使得函数中的变量都被保存在内存中,会使得内存消耗很大,所以不能滥用闭包,否则会造成页面的性能问题,在IE中可能导致内存泄露。解决方法:在退出函数前,把不使用的局部变量全部删除释放即可。
- 闭包会在父函数的外部,改变父函数内部变量的值。所以,如果把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,此时一定要千万小心,不要随便改变父函数内部变量的值。
示例代码
1、从外部读取函数内部的局部变量
需要获取到函数内部的局部变量,只有通过变通的方法才能实现,那就是在函数内部再定义一个函数,具体如下所示:
function fun1(){
var num=111;
function fun2(){
alert(num); // 输出结果为:111
}
}
上述代码中,函数 fun2被包括在函数fun1内部,此时fun1内部的所有局部变量,对fun2都是可见的;但是反过来就不行,fun2内部的局部变量,相对 fun1是不可见的。
2、使变量一直保存在内存之中,不被垃圾回收机制所回收
具体如下所示:
var changed;
function includ() {
var fruit = ‘苹果’
function food() {
console.log(‘红的’ + fruit)
}
changed = function () {
fruit = ‘樱桃’
}
return food;
}
var result = includ()
result() //输出结果:红的苹果
changed()
result() //输出结果:红的樱桃
上述代码中,includ()执行之后,fruit变量能一直被访问到,原因就是result引用了includ()内部的food() 函数,food()函数又引用了fruit变量,所以它一直不被垃圾回收机制所回收。
3、模拟实现私有变量
具体如下所示:
function calculate(start){
var num = start;
return{
increase: function(){
num++;
},
get: function(){
return num;
}
}
}
var result = calculate(4);
result.increase();
result.get(); //输出结果:5
上述代码中,calculate()函数返回两个闭包:increase()和get(),该两个函数都维持着对外部作用域 calculate ()的引用,所以总是可以访问此作用域内定义的变量num。
拓展:作用域规则
- 大括号{}不产生一个作用域,定义函数才会产生一个函数作用域;
- 函数在执行的过程中,先从函数自身内部找变量;
- 若函数自身内部找不到变量,才会再从创建当前函数所在的作用域里去找变量, 依此往上查找变量。
总结
1、闭包是可以访问另一个函数作用域中的变量的函数,创建闭包的最常方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量。
2、闭包的缺点之一就是常驻内存,会增大内存使用量,若使用不当就很容易引起内存泄露。
3、闭包不适用场景:返回闭包的函数是个体积比较大的函数,所以对函数体积要求小的情景下不适合使用闭包。
4、闭包是JavaScript语言的经典特点之一,应用闭包场合主要是为了设计私有的方法和变量。
5、在做框架的时候闭包的运用更普遍,有些方法和属性只在运算逻辑过程中使用,不想让外部修改这些属性,所以就可以设计一个闭包来只提供方法获取,闭包的典型框架非jquery莫属了。
6、不用纠结到底在什么情况下才算闭包,其实在写下的每一个函数都可以算作闭包,即便是全局函数在访问函数外部的全局变量时,也属于闭包的体现。
最后
通过上面介绍的JS中闭包的使用详解,常用闭包的相关知识点通过本文内容就可涵盖,在Vue.js开发中的使用就游刃有余了,这也是在开发过程中必用的功能,尤其是对于初中级开发者来说,更应该掌握这些情况的使用,还有在前端面试求职的时候,闭包的使用以及相关知识点也是必考内容,所以一定要完全掌握闭包相关的内容,这里不再赘述。