闭包(Closure)又称为词法闭包和函数闭包,由函数创造的一个词法作用域,创建在词法作用域的变量被引用后,可以在这个词法环境之外使用。
在深入学习闭包之前,我们需要了解与闭包相关的基本知识,词法作用域。
JS的作用域的概念:引擎用来管理当前作用域和嵌套的子作用域中根据标识符名称进行变量查找的一套规则。
一般语言在编译的编译过程主要分为三步:
词法作用域是发生在编译第一阶段即词法阶段,词法作用域代码是由定义变量,函数和块作用域的位置所决定的。我们可以通过JavaScript函数实例理解词法作用域:
function fun(a) {
var b = a + 2;
function secFun(c) {
console.log(a, b , c)
}
secFun(b + 4)
}
fun(1) ; // 1, 3, 7
在函数执行过程中,函数创建了逐级嵌套的作用域:
我们可以看到,JavaScript的词法作用域在编译之前已经确定了,由代码书写的位置决定。
下面的函数是一个完整的闭包:
function closureFun() {
var name = "closureFun";
function getName() {
console.log(name)
}
return getName;
}
var nameFun = closureFun();
nameFun();
我们结合JS的词法作用域的内容,分析一下闭包的执行过程:
我们知道,我们在执行函数的时候,会创建一个新的作用域,称为私有作用域,当函数执行完毕之后为了节约内存JS引擎会将这个私有作用域会被销毁,定义在私有作用域的函数和变量都会被清除。
但是在定义函数词法作用域以外执行函数,可以保持函数内部定义的私有作用域,形成一个闭包。更直观的理解,我们可以在函数closureFun外面访问到函数内部定义的变量。
我们也可以这样理解闭包:访问并记住词法作用域的函数叫闭包。
在前端开发过程中,我们经常使用的闭包应用包括:匿名立即执行函数,存储变量,封装私有变量。
<ul>
<li>dom1</li>
<li>dom2</li>
<li>dom3</li>
</ul>
上面的html代码中,我们设定了一个常见的需要,我们需要当我们点击li元素的时候,获取当前li元素的下标,因为根据li元素的名称可以获取li元素的理解,所以我们的需求可以抽象:
根据上面的需求转化,我们很容易写出来下面的解决方案:
let doms = document.getElementsByTagName('li');
for (var i = 0; i < doms.length; i++) {
doms[i].onclick = function() {
alert(i)
}
}
当时我们点击DOM元素的时候,发现这个是行不通的方案,我们每次获取到的下标都是i变量最后的值。我们获取到的下标i是一个引用值,执行循环运行完成的值。
我们可以使用闭包,完成上面的需求:
let doms = document.getElementsByTagName('li');
for (var i = 0; i < doms.length; i++) {
(function(index) {
doms[index].onclick = function() {
alert(index)
}
})(i)
}
我们通过匿名执行函数,每次遍历获取当前的下标i,匿名函数在内部的作用域获取标识符index,保存下标的副本到变量index,这样每个匿名函数都有一个内部的变量存储执行时的下标i的值。
在我们开发过程中,我们可以使用闭包的特性创建常量:
const person = () => {
let name= "javaScript"
return () => {
return name;
}
}
let personName= person ();
personName() // javaScript
personName() // javaScript
这样我们无论如何去调用personName函数,始终获取到name的变量值,并且无法修改,这样我们就可以在JS开发过程中使用闭包来完成常量的封装。
const personRun = () => {
let stepCount = 0;
return () => {
return ++stepCount
}
}
let run = personRun();
run() //1
run() //2
我们可以使用personRun 函数词法作用域内的变量stepCount ,personRun函数运行,返回引用了stepCount变量的内部函数本身,赋值给外部run标识符,我们可以通过调用run函数完成对stepCount变量的管理。
const jsVersion = () => {
let _version = 'ES5';
_getVerson = () => _version;
_setVerson = (version) => {
_version = version
}
return {
setVersion: _setVerson,
getVerson: _getVerson
}
}
let jsv = jsVersion();
jsv.getVerson() //ES5
jsv.setVersion('ES6')
jsv.getVerson() //ES6
封装私有变量是闭包的一个很实用的应用,也可以理解成闭包的对变量的一种管理,原理是在闭包创建的词法作用域内,外部无法直接访问词法作用域内部定义的变量,也就是说词法作用域定义的变量对外部是完全屏蔽的,相当于强语言类型的私有变量的概念,我们可以通过对外提供接口的方式操作内部封装的私有变量。
我们需要明白闭包使用是有代价的,因为闭包内变量的引用无法被自动释放,所以容易造成内存泄漏问题。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有