闭包
闭包的概念以及相关应用.
定义
1 | function greet(whattosay){ |
调用sayHi
函数时,greet
函数已执行完成,跳出执行栈(execution stack),但greet
函数内定义的whattosay
变量的值依然能被sayHi
函数获取, 这样的情况可能发生是因为 JavaScript中存在 闭包.
代码开始执行时创建了一个全局执行环境, 当执行到var sayHi = greet('Hi')
时, 调用greet()
函数, 新的执行环境被创建, 传给greet函数的whattosay
变量处于greet()
执行环境中, 一边执行一边创建了所返回的函数, 函数返回之后, greet()
函数跳出执行栈,这个执行环境不再存在, 但存储变量的内存空间以及变量依然存在.
当调用sayHi()
时,又创建了一个新的执行环境, 但这个执行环境中没有whattosay
变量,于是JavaScript引擎通过作用域链在sayHi
函数外部寻找该变量.
闭包定义: The phenomenon of closing in all the variables that it is supposed to have access to is called a closure.
闭包是JavaScript语言本身的特性.
应用
闭包与for循环
1 | function buildFunctions(){ |
代码执行到行{1}时, buildFunctions
被调用, 执行环境被创建, 执行之后赋值给fs, fs是个数组, 其中包含3个元素的均为函数function(){ console.log(i) }
. 当调用数组中的函数时, 由于闭包, i值从函数定义时的外部获取, 即在buildFunctions
中, 因为循环到i=3时结束, 所以i=3被保存于内存空间, 因此三次调用数组内函数的结果均为输出3.
如何让函数输出预期的结果:
1. ES6的解决方式
for循环执行时,因为let关键字创建了块级作用域,i在每次循环中都是内存中的一个新变量.
1 | function buildFunctions2(){ |
2. ES5的解决方式
运用IIFE使函数在创建的时候执行, 这样每次执行都会创建新的执行环境, 于是每个被push到数组中的函数执行时都处于不同的执行环境, 因此数组中的函数每次执行时都会输出预期的不同值, j值从所创建的闭包中获取, 而不是再外一层的循环中.
1 | function buildFunctions2(){ |
利用闭包创造工厂函数
工厂函数 Function Factories
1 | function makeGreeting(language) { |
传入外层函数, 在内层函数返回, 尽管在内层函数执行时makeGreeting函数已经跳出执行栈, 但language变量依然存在于内存空间. 调用不同的language, 创造的是不同的执行环境, 因此变量存在于不同的内存空间.
闭包与回调函数
1 | function sayHiLater(){ |
在JavaScript中, 函数是一等公民并且可以即时创建函数表达式 所以可以将函数function(){ console.log(greeting); }
作为参数传入setTimeout
函数, 又因为闭包, greeting
的值从sayHiLater
执行时保存在内存空间的greeting
变量中获取. 因此上述代码成立. 这里的函数function(){ console.log(greeting); }
是回调函数.