一、闭包
1、理解
看这段代码:
function foo() {
var i = 1
function show() {
i += 1
console.log(i)
}
return show
}
var a = foo()
a()
- 正常情况,函数定义时,开辟一个内存空间,使用结束后,该变量取消指向内存地址,内存地址准备被销毁
- 如果内部函数引用了外部函数的值,那么就会形成闭包
2、 概念
-
闭包是一个存在内部函数 的引用关系
-
该引用指向的是 外部函数 的局部变量对象
3、产生条件
- 函数嵌套
- 内部函数引用外部函数的局部变量
- 内部函数被使用
为什么内部函数一定要被使用?
因为函数变量提升的时候,如果内部函数没用被使用,在预解析的过程中,不会被定义
4、优缺点
优点:
- 延长外部函数变量对象的生命周期
- 间接使得函数外部可以操作(读写)到 函数内部的数据(变量/函数)
浏览器为了性能,后期将外部函数中,不被内部函数使用的变量清除了
缺点:
- 延长外部函数变量对象的生命周期**(占内存,如果不及时清除,容易造成内存溢出、泄露)**
5、使用闭包的注意点
- 及时清除闭包
- 让内部的函数成为垃圾对象
6、生命周期
- 产生:在嵌套内部函数定义指向完就产生了
- 死亡:在嵌套内部函数成为垃圾对象时
function fn1() {
// 预解析阶段,闭包产生
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f()
f = null // f指向内部函数发生改变,内部函数成为 垃圾对象,闭包死亡
7、常见的闭包
-
将函数作为另一个函数的返回值
function fn1() { var a = 2 function fn2() { a++ console.log(a) } return fn2 } var f = fn1() f() // 3 f() // 4
-
将函数作为实参传递给另一个函数调用
function showMsgDelay(msg, time) { setTimeout(() => { console.log(msg) }, time); } showMsgDelay('hello', 1000)
二、闭包的应用
作为模块
这种使用方式,就跟module
的使用方式很相似
作用:定义JS模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只想外暴露一个包含 n 个方法的对象或函数
- 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
// js模块
function mathModule() {
var result = 0
function add(x) {
return result += x
}
function subtract(x) {
return result -= x
}
return {
add,
subtract
}
}
使用:
<script type="text/javascript" src="mathModule.js"></script>
<script>
var mathObj = mathModule()
console.log(mathObj.add(1)) // 1
console.log(mathObj.subtract(2)) // -1
</script>
三、闭包的面试题
1、第一道(附解析)
var name = 'The Window'
var obj = {
name: 'My Object',
getName: function() {
return function() {
return this.name
}
}
}
console.log(obj.getName()())
答案: The Window
解析:
/* obj.getName()() 可以分解为以下代码 */ var result = obj.getName() // 1. 返回为: function(){ return this.name } result() // 2. 就是执行 function(){ return this.name } // 3. result() == window.result() // 4. 所以此时,this为 window // this上面有一个变量,name = The Window,在全局作用域就有了 // 所以输出 The Window
2、第二道(附解析)
var name = 'The Window'
var obj = {
name: 'My Object',
getName: function() {
var that = this // 缓存this,这样可以保证this的指向
return function() {
return that.name
}
}
}
console.log(obj.getName()())
答案:My Object
解析:
/* obj.getName()() 可以分解为以下代码 */ var result = obj.getName() // 1. 返回为: function(){ return that.name } result() // 2. 就是执行 function(){ return that.name } // 3. 由于此时的that,闭包对象指定了that = this,而在getName的this为obj // 4. 所以此时,that为 obj // obj上面有一个变量,name = My Object,在局部作用域就有了 // 所以输出 My Object
3、第三道(最难的)
明白这道题,闭包就可以出师了
function fun(n, o) {
console.log(o)
return {
fun: function(m) {
return fun(m, n)
}
}
}
var a = fun(0)
a.fun(1)
a.fun(2)
a.fun(3)
var b = fun(0).fun(1).fun(2).fun(3)
var c = fun(0).fun(1)
c.fun(2)
c.fun(3)
答案:
undefined 0 0 0 undefined 0 1 2 undefined 0 1 1
解析:
解析之前,首先要明确两个语法:
var a = fun(0)、var b = fun(0)
这两个并没有共享同一个 闭包对象,而是分别创建两个不同的闭包对象,也就是对于a、b来说,最开始的
console.log(o),都是undefined
a.fun(1)、a.fun(2) 和 fun(1).fun(2)
这两个并不是等同的,第一个
a.fun(1)、a.fun(2)
都只是使用同一个函数;第二个fun(1).fun(2)
等同于fun(2)在使用fun(1)基础上的函数
/* 如果fun有一个变量是 1, 调用一次就会 +1 */ a,fun(1) //那么这两个都是1 a.fun(2) fun(1).fun(2) // fun(1) 的变量是 1, fun(2) 的变量 就会改变,为2
那么,明确之后,现在正式开始解析
function fun(n, o) { console.log(o) return {// 满足产生闭包的条件,产生了一个闭包引用->外部函数fun的变量对象{n:0,o: undefined} fun: function(m) { return fun(m, n) } } } // 意思是:fun调用后,返回一个对象,对象内有一个函数,函数内返回的是这个外部函数fun // 即 第一个是 window.fun(n, o),第二个是 对象.fun(m),这个返回 window.fun(n, o)
a
的调用方式:/* 开拓一个变量对象,此时: window.fun的 n = 0, o = undefined a.fun 的 o = 0 */ var a = fun(0) // 输出 undefined /* 以下都是调用同一个 a.fun,上面可得知,a.fun 的 o = 0 */ a.fun(1) // 输出 0 a.fun(2) // 输出 0 a.fun(3) // 输出 0
b
的调用方式:var b = fun(0).fun(1).fun(2).fun(3) /* 以上可以分解为: var b0 = fun(0) var b1 = b0.fun(1) var b2 = b1.fun(2) var b3 = b2.fun(3) */
/* 开拓一个变量对象,此时: window.fun的 n = 0, o = undefined b0.fun 的 o = 0 */ var b0 = fun(0) // 输出 undefined /* 开拓一个变量对象,此时: b0.fun 的 o = 0 b1.fun 的 o = 1 */ var b1 = b0.fun(1) // 输出 0 /* 开拓一个变量对象,此时: b0.fun 的 o = 0 b1.fun 的 o = 1 */ var b2 = b1.fun(2) // 输出 1 /* 开拓一个变量对象,此时: b0.fun 的 o = 0 b1.fun 的 o = 1 */ var b3 = b2.fun(3) // 输出 2
c
的调用方式var c = fun(0).fun(1) c.fun(2) c.fun(3) /* 以上可以分解为: var c0 = fun(0) var c1 = c0.fun(1) c1.fun(2) c1.fun(3) */
/* 开拓一个变量对象,此时: window.fun的 n = 0, o = undefined c0.fun 的 o = 0 */ var c0 = fun(0) // 输出 undefined /* 开拓一个变量对象,此时: c0.fun 的 o = 0 c1.fun 的 o = 1 */ var c1 = c0.fun(1) // 输出 0 /* 以下都是调用同一个 c1.fun,上面可得知,c1.fun 的 o = 1 */ c1.fun(2) // 输出 1 c1.fun(3) // 输出 1