javascript 之闭包-理解不了来找我
1,闭包是什么
(百度百科定义)--闭包是可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变 量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境 (作用域)。
W3C ECMAScript在闭包的解释中,有这么一句话:闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量就称为闭包。
要了解闭包,在javascript王者归来中,提出了语法域和执行域,来帮助我们理解闭包。
语法域:定义某个程序段落时的区域。执行域:实际调用某个程序段落时所影响的区域()
这里我的理解是:语法域,比如一个变量在函数中的作用域就是在函数内,不能在函数外访问。执行域:通过一些表达式的改变,访问到作用域外的内容,或被作用域外所访问。
所谓闭包:是指语法域位于某个特定的区域,具有持续参照(读写)位于该区域内自身范围之外的执行域上的非持久型变量值能力的段落。这些外部执行域的非持久型变量神奇的保留它们在闭包最初定义(或创建)时的值。
总结闭包的定义:它是某些可以访问外部执行域的段落,在javascript 中,通过定义在函数内部的function来实现
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见方式,就是在一个函数内部创建另一个函数。
警告:不要对闭包和匿名函数充满了疑惑,匿名函数就是一个没有名字的函数而已,而闭包是访问函数之外定义的变量。如果这个匿名函数能够访问函数之外定义的变量,那么它就是一个闭包。
这上面的总结是一个星期前写的,今天看了看,又有新的收获。想想之前去面试,必问一个问题:什么是闭包,现在才知道人家为什么要问你对闭包的理解,因为要完全理解闭包,需要了解 作用域链,执行环境,这样你才能在使用中游刃有余。
2,执行环境
因为闭包也是一个函数,所以要理解闭包,就要理解函数第一次被调用的时候发生了什么?下面来看看javascript的执行环境,就是上面提到的执行域
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中,虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
全局执行环境是最外围的一个执行环境
在浏览器中,全局执行环境被认为是 window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的
某个执行环境中的所有代码执行完毕后,该环境被销毁。保存在其中的变量和函数定义也随之销毁(全局执行环境直到应用程序退出--例如关闭网页或浏览器--时才会被销毁)
举个例子:就像我们一直呆在地球上,而地球就像我们的语法域,而我们的执行域 就像神六发射到月球一样,杨利伟可以在月球行走,这里的地球和月球的空间就是我们所说的执行域,在没有发射神六时,杨利伟也只能在地球上,就像我们函数,没有执行时,它就是被{ }这两个符号包围着,当神六到了月球时,杨利伟就能在月球走动,就想函数运行时,它可能就引用它以外的变量,这就是我们所说的执行环境,我个人理解就是 在执行的瞬间,它所包含的变量对象。
执行环境,故名思议,在执行过程中产生的,执行完毕后这个就消失了,所以你是不可访问,就向神六上月球一样,一旦返回地球,你就没有对访问月球的执行环境了
每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境会被推入一个环境栈中。而在函数执行完毕后,栈将其环境弹出,把控制权返回给之前的执行环境,ECMAScript程序中的执行流正是由这个机制控制着。
我对上面这句话的理解:当函数执行完毕后,然后开始执行包含它的变量对象,这样的一直去执行,直到全局环境。
在javascript中,分为全局和局部(函数)作用域,这里的局部指函数内,说到这,就得提一下变量声明了,使用var在函数声明变量为局部变量,如果在函数内没有使用var 声明变量,将默认为全局变量。
var a = 1; function A(){ alert(a); } A();
一个简单的闭包,你可能会疑问,这么简单的一个函数也是闭包么? 是的,只要使用函数之外定义的变量就是闭包。
最简单的例子,上面的代码中,A() 的作用域链包含两个对象,它自身的变量对象(其中定义着arguments对象)、和全局环境的变量对象,可以在函数内部访问 变量a,是因为可以在作用域链中找到它。
那么它是怎么搜索的呢,首先它会从作用域链的前端开始(也就是当前执行的代码所在环境的变量对象)、也就是A() 函数内,在A函数没有找到,于是到它的包含环境(全局执行环境的变量对象)中查找,发现有a变量,停止搜索。
变量查询不是没有代价的,很明显,访问局部变量要比访问全局变量更快,因为不用向上搜索作用域链。
下面看一个复杂的例子:
var global = "global"; function A(){ var a = "a"; function B(){ var b = "b"; alert(global +","+ a); } B(); } A();
这幅图,大概就能看出什么意思了,参考上面那个简单的代码,如果有什么不明白的,希望留言,我再来补充。
3,作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在的环境的变量对象,
如果这个环境是函数,则将其活动对象作为变量对象,活动对象在最开始时只包含一个变量,即arguments 对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境,这样,一直延续到全局变量,全局执行环境的变量对象始终都是作用域链中的最后一个对象。
标识符解析是沿着作用域链一级一级的搜索标识符的过程,搜索过程始终从作用域链的前端开始,然后逐级的向后回溯,直到找到标识符为止(如果找不到标识符,通常会导致错误发生)。
用大白话来理解,当程序中的函数执行时,会创建一个作用域链,你可以把他当做搜寻范围,这里有一个作用域链前端,这个指什么呢?我的理解就是当前函数,例如在上面的代码中 作用域链的前端就是B 函数,然后下一个变量对象A 函数,然后全局的。
延长作用域链(这个知识了解就行,如需深入了解,自行查找)
通过其他办法来延长作用域链,因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除,
那么函数是怎么释放的呢?来看看垃圾回收(更多了解请自行查找,这个了解就行)
分析函数中局部变量的正常声明周期,局部变量只在函数执行的过程中存在,在这个过程中,会为局部变量在内存上分配相应的空间,以便存储他们,然后在函数中使用这些变量,直至函数执行结束,此时,局部变量就没有存在的必要了,因此可以释放它们的内存供将来使用
4,闭包的用法
学习了执行环境、作用域链、闭包的概念,你是不是不知道怎么使用呢?(把下面的代码拷贝下来,自己执行一下)
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8" /> <title>闭包演示</title> <style type="text/css"> p {background:green;} </style> <script type="text/javascript"> function init() { var pAry = document.getElementsByTagName("p"); for( var i=0; i<pAry.length; i++ ) { pAry[i].onclick = function(){ // 点击事件哦 alert(i); } } } </script> </head> <body onload="init();"> <p>产品 0</p> <p>产品 1</p> <p>产品 2</p> <p>产品 3</p> <p>产品 4</p> </body> </html>
上面的代码执行过程中,你会发现,每次弹出都是5,是不是 不和你想象的一样,这是为什么呢?
因为onclick 这个函数表达式,使用了闭包(使用了函数外的变量 i ),根据执行环境中的作用域链,因为每个函数的作用域中都保存着 init 函数的活动对象,所以它们引用的都是同一个变量i,当 init 函数返回后,变量 i 的值是5,此时每个函数都引用着保存变量 i 的同一个变量对象,所以在每个函数内部 i 的值都是5
简单的 说:这个变量,总能访问到全局作用域的i
解决办法:不要让它总是访问到全局作用域的 i,让它在调用匿名函数时,给它传入一个参数,因为函数是按值传递的(对象也是按值传递哦),所以就会将变量i的值赋给参数_i,因为是按值传递,所以你懂得。
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8" /> <title>闭包演示</title> <style type="text/css"> p {background:green;} </style> <script type="text/javascript"> function init() { var pAry = document.getElementsByTagName("p"); for( var i=0; i<pAry.length; i++ ) { (function(_i){ pAry[_i].onclick = function(){ alert(_i); } })(i); } } </script> </head> <body onload="init();"> <p>产品 0</p> <p>产品 1</p> <p>产品 2</p> <p>产品 3</p> <p>产品 4</p> </body> </html>
上面那种方法,我认为是最好理解的,对于按值传递啊,作用域链
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8" /> <title>闭包演示</title> <style type="text/css"> p {background:green;} </style> <script type="text/javascript"> function init() { var pAry = document.getElementsByTagName("p"); for( var i=0; i<pAry.length; i++ ) { pAry[i].onclick = function(_i){ return function(){ alert(_i); } }(i); } } </script> </head> <body onload="init();"> <p>产品 0</p> <p>产品 1</p> <p>产品 2</p> <p>产品 3</p> <p>产品 4</p> </body> </html>
上面那种方法是别人的,总之重在理解,下次遇到能够很快的去解决
写的好累啊,都是自己的参考和理解,希望能得到博友们的意见
参考资料:
javascript 高级程序设计第三版中的 闭包
https://blog.csdn.net/weixin_33935505/article/details/91460146
2020年再看闭包
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
闭包让你可以在一个内层函数中访问到其外层函数的作用域。
在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
例如:
function fn() {
// outFind = undefine 每创建一个函数就会有一个闭包环境,如果引用外部变量,就会去查找
}
如果您看了本篇博客,觉得对您有所收获,请点击右下角的 [推荐]
如果您想转载本博客,请注明出处
如果您对本文有意见或者建议,欢迎留言
感谢您的阅读,请关注我的后续博客