闭包在个人前端学习中一直也是盲点,以前不少次看到别人提到我都是彻底听不懂。最近一直看书和写demo,对闭包也逐渐有所理解了,在这里写下这篇博客。前端
首先明确几个概念:
1.JavaScript有函数级做用域,但没有块级做用域。
2.当要使用一个变量时,会沿着做用域链一步一步向上查找。
这里有一个demo:es6
var a = 1 function foo () { var a = 2 } foo() console.log(a) // a = 1
结果固然是a = 1
,虽然在foo函数中从新声明了a而且赋给它一个新的值,可是var声明的变量只在foo()函数中有效,函数执行完毕就会销毁,所以全局做用域中a的值没有变化。
接下来再看这个demo:浏览器
for (var i = 0; i < 10; i++) { // code } console.log(i) // i = 10
这里在for循环结束以后仍然能在外部访问到i,就是由于JavaScript没有块级做用域形成的。闭包
闭包的主要特性就是能够从外部访问函数内部的属性和方法。先看一个demo:函数
function foo () { var a = 1 } foo() console.log(a) // 出错
正常状况下,定义在函数内部的局部变量在函数执行完以后就会被销毁,所以在外部是没法访问局部变量的。那应该怎么作才能访问呢?请继续看:性能
function foo () { var a = 1 function bar () { console.log(a) } return bar } var baz = foo() baz() // 1
在这个demo中,咱们在函数foo()内部又定义了一个函数bar(),并把它的函数名返回,这样便能在外部实现对内部变量a的访问。学习
有人问了:不是说函数执行结束以后内部变量会被销毁吗?你这不科学啊。code
是的,原来函数执行结束以后内部变量的确会被销毁,可是这里内部函数bar()在foo()执行时被返回并保存到了外部的baz中。这时候foo()执行完后,baz中依旧保存着对函数bar()的引用,所以bar()的做用域并无被释放,根据以前提到的变量查找方式,在bar()函数的外层做用域中找到了a。ip
以上就是使用闭包能从外部访问内部属性的原理。内存
利用闭包强大的特性,最方便的用途就是实现私有变量和私有方法;另外,由于使用闭包会在内存中保存函数做用域,所以也能保存变量的值。
此话怎解?请看下面的demo:
for (var i = 1; i <= 5; i++) { setTimeout(function timer () { console.log(i) }, i * 1000) } // 6 6 6 6 6
这里的结果并非咱们预想中的每隔一秒依次输出12345,而是每隔一秒输出一个6,为何会这样呢?
首先,这个6是变量i最终退出循环时的值,其次var声明的变量没有块级做用域,所以i实际上在全局做用域中都访问获得。到这里不难理解了,setTimeout在循环结束后执行timer()函数时,访问的i实际上在全局做用域中,此时i=6,所以后面的每一个timer()函数执行访问的i都是6。那么问题又来了,怎样才能让闭包访问的i是咱们想要的呢?
其实很简单,让i变成局部变量,在循环结束以后销毁,这样每一个闭包访问的就是保存在做用域中的局部变量i,也就是对应的12345。请看下面的demo:
for (var i = 1; i <= 5; i++) { (function (j) { setTimeout(function timer () { console.log(j) }, j * 1000) })(i) } // 1 2 3 4 5
这里用到了当即执行函数(IIFE),可能有些难以理解。那我一步一步解释:当即执行函数其实就是JavaScript模仿块级做用域的方法,这里你能够把它简单当作一个函数调用。i就是传给调用函数的参数,内部匿名函数的形参j接收到i的值开始执行setTimeout。此时j就是函数级做用域中的局部变量,在循环结束后,timer()函数开始执行,由于它是一个闭包,因此能访问保存在做用域中的变量j,也就输出了咱们想要的结果。
固然更简单的方式是用ES6的let来声明i,这样也是使得i变成局部变量,其余关于ES6这里就很少提,有兴趣的能够自行看let基本用法。
1.由于闭包会使得函数中的变量都保存在内存中,如不能及时释放会对性能形成影响。
2.在IE9如下的浏览器会有内存泄漏的问题。(关于这块我后续会写文章详细说明)
本人经验尚浅,目前对于前端仍在不断摸索和学习,文章若有错误,欢迎各位指正。最后附上本人博客地址和原文连接,但愿能向各位多多学习。