一、什么是闭包
1、概念:
闭包:闭包就是每次调用外层函数时,临时创建的函数作用域对象。内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
闭包函数:声明在一个函数中的函数,叫做闭包函数。
因为内层函数作用域链中包含外层函数的作用域对象,且内层函数被引用,导致内层函数不会被释放,同时它又保持着对父级作用域的引用,这个时候就形成了闭包。所以闭包通常是在函数嵌套中形成的。例如以下例子:
function foo (){
var name = 'snail'
return function(){
console.log('my name is '+name)
}
}
var bar = foo();
bar();
2、特点:
让外部访问函数内部变量成为可能;
局部变量会常驻在内存中;
可以避免使用全局变量,防止全局变量污染;
会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
二、运用场景:
闭包找到的是 同一地址中 父级函数中 对应变量最终的值
闭包的实现原理,其实是利用了作用域链的特性,我们都知道作用域链就是在当前执行环境下访问某个变量时,如果不存在就一直向外层寻找,最终寻找到最外层也就是全局作用域,这样就形成了一个链条。
闭包的形成:
- 出现函数嵌套。
- 内部函数运用到的外部函数的变量。
- 外部函数被调用,内部函数的声明被执行。
闭包的目的:
延长变量的声明周期
不会被垃圾回收机制回收,造成内存消耗。可能会导致内存泄露,进而导致内存溢出
这里简单说一下,为什么使用闭包时变量不会被垃圾回收机制销毁呢,这里需要了解一下JS垃圾回收机制
JS规定在一个函数作用域内,程序执行完以后变量就会被销毁,这样可节省内存;
使用闭包时,按照作用域链的特点,闭包(函数)外面的变量不会被销毁,因为函数会一直被调用,所以一直存在,如果闭包使用过多会造成内存销毁。
1、实现块级作用域:
如果所声明函数没有块级作用域,那么其变量不止是属于其所在的作用域,更是属于其父级作用域,那么输出结果就是最后最终的结果,如下列代码:
function foo(){
var result = [];
for(var i = 0;i<10;i++){
result[i] = function(){
console.log(i)
}
}
return result;
}
var result = foo();
result[0](); // 10
result[1](); // 10
var
声明的 i
不只是属于当前的每一次循环,甚至不只是属于当前的 for
循环,因为没有块级作用域,变量 i
被提升到了函数 foo
的作用域中。所以每个函数的作用域链中都保存着同一个变量 i
,而当我们执行数组中的子函数时,此时 foo
内部的循环已经结束,此时 i = 10
,所以每个函数调用都会打印 10
。
接着对 for
循环内部添加一层即时函数(又叫立即执行函数 IIFE
),形成一个新的闭包环境,这样即时函数内部就保存了本次循环的 i
,所以再次执行数组中子函数时,结果就像我们期望的那样 result[0]()
打印 0
,result[1]()
打印 1
...
function foo(){
var result = [];
for(var i = 0;i<10;i++){
(function(i){
result[i] = function(){
console.log(i)
}
})(i)
}
return result;
}
var result = foo();
result[0](); // 0
result[1](); // 1
当然,ES6 引入了let
声明变量方式,让 JavaScript
拥有了块级作用域,可以更方便的解决这样的一个问题。
2、函数柯里数
首先说一下什么是函数柯里化?
柯里化是把接收多个参数的函数变成接收单一参数(最初函数的第一个参数)的函数,并且返回接收余下的参数且返回结果的新函数。
翻译成人话就是可以将一个接受多个参数的函数分解成多个接收单个参数的函数的技术,直到接收的参数满足了原来所需的数量后,才执行原函数的逻辑。
例如一个非常经典的面试题 => 实现 add(x)(y)(z) = x+y+z
中就用到了函数柯里化。代码如下:
function add(x){
return function(y){
return function(z){
return x+y+z
}
}
}
console.log(add(1)(2)(3)) // 6
其实就是要生成一个功能,此功能可以拆分成两部分小功能实现,那么这个时候就可以在实现功能一的基础上再加上功能二,形成最终的功能。
3、模块模式:
先看这段代码
function create(){
var name = 'snail',
hobby = ['eat','sleep','codeing']
function say(){
console.log('my name is '+name+'.')
}
function showHobby(){
console.log(name+' like '+hobby.join(',')+'!')
}
return {
say,
showHobby
}
}
var instance = create();
instance.say(); // my name is snail.
instance.showHobby(); // snail like eat,sleep,codeing!
这个模式在 JavaScript
被称为模块。这里我们调用了 create
函数创建了一个模块实例,实例中含有对内部函数的引用,这样保证了内部数据变量是隐藏和私有状态,而返回值则可以看做是模块暴露出的 API
。当然这里也可以直接返回方法作为模块的公共 API
,·就像 JQuery
中返回的 $
。
三、缺点:
因为闭包会携带包含它的函数的作用域,所以哪怕外部函数执行完成,因为内部函数保存了外部函数中变量的引用,所以外部函数依然不会被释放,所以过度使用闭包会造成内存占用过多。