一、 引出闭包
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
//需求: 点击某个按钮, 提示"点击的是第n个按钮"
<script type="text/javascript">
var btns = document.getElementsByTagName('button')
//注意[btns]不是一个数组,它是一个伪数组
//每次获取[btns.length]其实都是需要进行计算的(因为它是伪数组)
//所以为了性能更好,在此处赋值,就只需要计算一次
for (var i = 0,length=btns.length; i < length; i++) {
var btn = btns[i]
btn.onclick = function () { //遍历加监听
alert('第'+(i+1)+'个') //结果 全是[4]
}
}
</script>
可得点击每个按钮的结果都是4。原因:此处的【i】是一个全局变量,点击函数执行的时候,for循环已经结束,此时i等于3,最后加1得4。
将变量挂载到自身来解决
解决方式1:将btn所对应的下标保存在btn上。
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
//需求: 点击某个按钮, 提示"点击的是第n个按钮"
<script type="text/javascript">
var btns = document.getElementsByTagName('button')
for (var i = 0,length=btns.length; i < length; i++) {
var btn = btns[i]
//将btn所对应的下标保存在btn上
btn.index = i
btn.onclick = function () {
alert('第'+(this.index+1)+'个') //注意此处this.index
}
}
</script>
解决方式2:利用闭包。
var btns = document.getElementsByTagName('button')
//利用闭包
for (var i = 0,length=btns.length; i < length; i++) {
//此处的j是局部的,它将传入的[i]存入局部的[j]中,这样就能实现效果
(function (j) {
var btn = btns[j]
btn.onclick = function () {
alert('第'+(j+1)+'个')
}
})(i)//此处的i读取的全局的
}
二、 闭包的含义
闭包是指有权访问另外一个函数作用域中的局部变量的函数。声明在一个函数中的函数,叫做闭包函数。而且内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
三、产生闭包的条件
当一个嵌套的内部函数引用了嵌套的外部函数的变量(函数)时,就产生了闭包
(1)函数嵌套
(2)内部函数引用了外部函数的数据(变量/函数)
function fn1(){
var a= 2;
var b = 'abc';
function fn2(){//执行函数定义就会产生闭包(不用调用内部函数)
console.log(a)
}
fn2()
}
fn1()
四、常见的闭包
(1)将函数作为另一个函数的返回值
// 1. 将函数作为另一个函数的返回值
function fn1() {
var a = 2//此时已经有闭包了,函数定义已经执行过了(函数提升)
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
//创建内部函数对象时产生闭包,整个过程创建了一个内部函数对象,只有一个闭包
fn1()//此时产生两个闭包,共调用了两次外部函数
(2)将函数作为实参传递给另一个函数调用
// 2. 将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
setTimeout(function () {
alert(msg)
}, time)
}
showDelay('atguigu', 2000)
(3)高阶函数与柯里化(待编辑)
五、闭包的作用
1.使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
2.让函数外部可以操作(读写)到函数内部的数据(变量/函数)
问题:
1. 函数执行完后, 函数内部声明的局部变量是否还存在?
答:一般是不存在, 存在于闭包中的变量才可能存在
2. 在函数外部能直接访问函数内部的局部变量吗?
答:不能,但我们可以通过闭包让外部操作它
六、闭包的生命周期
1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
2. 死亡: 在嵌套的内部函数成为垃圾对象时。即没有人指向它时死亡,通常置为[null],当然指向其他也行,但不安全(容易污染变量)
//闭包的生命周期
function fn1() {
//此时闭包就已经产生了(函数提升,实际上[fn2]提升到了第一行, 内部函数对象已经创建了)
var a = 2
function fn2 () { //如果时[let fn2=function(){}],那么在这行才会产生闭包
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
七、 闭包的应用场景
最常见的是函数封装的时候,再就是在使用定时器的时候,即:当我们需要在模块中定义一些变量,并希望这些变量一直保存在内存中但又不会 “污染” 全局的变量时,就可以用闭包来定义这个模块。它的最大用处有两个,一个是它可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
闭包的应用 : 定义JS模块
(1)具有特定功能的js文件
(2)将所有的数据和功能都封装在一个函数内部(私有的)
(3)只向外暴露一个包含n个方法的对象或函数
(4)模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
模块定义
//myModule.js
function myModule() {
//私有数据
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
-----------------------------------------------------------------
// myModule2.js
(function () {
//私有数据
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
window.myModule2 = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})()
模块调用
//调用示例
------------ 模块调用1 --------------------------------------------
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
var module = myModule()
module.doSomething()
module.doOtherthing()
</script>
------------ 模块调用2 --------------------------------------------
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
myModule2.doSomething()
myModule2.doOtherthing()
</script>
八、 闭包的缺点及解决
1. 缺点:
(1)函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
(2)容易造成内存泄露
2. 解决:
(1)能不用闭包就不用
(2)及时释放
function fn1() {
var arr = new Array(100000)
function fn2() {
console.log(arr.length)
}
return fn2
}
var f = fn1()
f()
f = null //让内部函数成为垃圾对象-->回收闭包
九、 内存溢出与内存泄漏
1. 内存溢出
(1)一种程序运行出现的错误
(2)当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
2. 内存泄露
(1)占用的内存没有及时释放
(2)内存泄露积累多了就容易导致内存溢出
常见的内存泄露:
(1)意外的全局变量
(2)没有及时清理的计时器或回调函数
(3)闭包
<script type="text/javascript">
// 1. 内存溢出
var obj = {}
for (var i = 0; i < 10000; i++) {
obj[i] = new Array(10000000)
console.log('-----')
}
// 2. 内存泄露
// 意外的全局变量
function fn() {
a = new Array(10000000) //不使用var let const去承接
console.log(a)
}
fn()
// 没有及时清理的计时器或回调函数
var intervalId = setInterval(function () { //启动循环定时器后不清理
console.log('----')
}, 1000)
// clearInterval(intervalId)
// 闭包
function fn1() {
var a = 4
function fn2() {
console.log(++a)
}
return fn2
}
var f = fn1()
f()
// f = null
</script>
十、 常见闭包面试题
//代码片段一 -->没有产生闭包:因为内部函数没有调用外部变量
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //? the window
//函数体的this是window
//代码片段二
var name2 = "The Window";
var object2 = {
name2 : "My Object",
getNameFunc : function(){
//此处的this指向是[getNameFunc],他是对象中的属性,所以this指向就是object
var that = this;
return function(){
//此处用的是保存的 that
return that.name2;
};
}
};
alert(object2.getNameFunc()()); //? my object
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n)
}
}
}
var a = fun(0) //undefined
a.fun(1) //0
a.fun(2) //0
a.fun(3) //0
var b = fun(0).fun(1).fun(2).fun(3) //undefined 0 1 2
var c = fun(0).fun(1) //undefined 0
c.fun(2)//1 -->经过上方定义后 n固定为1
c.fun(3)//1 -->此处是陷阱!!! 一直没有改到n,所以一直是1