引例:
遍历加监听
<button>第一个</button>
<button>第二个</button>
<button>第三个</button>
var btns = document.getElementsByTagName('button') // btns是伪数组
// for(var i = 0;i< btns.length;i++){ //这样写,每次循环都要计算一次
for(var i = 0,length = btns.length;i< length;i++){ // 仅计算一次
var btn = btns[i]
btn.onclick = function () {
alert('点击了第' + (i+1) + '个按钮')
}
}
// 结果:每个点击都弹出:点击了第4个按钮
// 原因:在for循环中,自始至终只有一个i,所以每次打印输出时都是遍历结束后i的值(为4)
// 解决方案1——下标的唯一值
for(var i = 0,length = btns.length;i< length;i++){ // 仅计算一次
var btn = btns[i]
btn.index = i // 把i赋值给数组饿下标
btn.onclick = function () {
alert('点击了第' + (this.index+1) + '个按钮') // 根据下标输出,下标是唯一的
}
}
// 解决方案2 —— 闭包
(外部函数子内部函数中调用了几次就产生多少个闭包)
for(var i = 0,length = btns.length;i< length;i++){
(function (i) { // 匿名自运行函数内部的i是局部变量,每次传入的i都不一样
var btn = btns[i]
btn.index = i // 把i赋值给数组饿下标
btn.onclick = function () {
alert('点击了第' + (this.index+1) + '个按钮') // 根据下标输出,下标是唯一的
}
})(i)
}
1.什么是闭包?
闭包满足条件:(缺一不可)
(1)一个函数包含了另一个函数,被包含的函数内部调用了外部函数的属性或函数(与内部函数执行与否没有关系,因为有函数提升这个过程)
(2)外部函数执行(不执行只是定义了,也不会产生闭包——可以通过Chrome的调试工具,打断点在scope查看过程:在执行外部函数时闭包才产生)
(注意:闭包存在于嵌套的函数内部,但是嵌套的函数不一定产生闭包)
code:
function fn1() {
var a = 2
function fn2() {
console.log(a)
}
fn2()
}
fn1()
2.常见的闭包
(1)将函数作为另一个函数的返回值
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2 // 函数一颗作为返回值因为函数也是数据,数据就可以传递,作为返回值
}
var f = fn1() // 按理来说fn1()在这一步已经执行,其内部的局部变量就要释放内存了,但是由于闭包的存在,它内部的局部变量并没有先消失,否则f()就不会有数据 a 打印
f() // 3 -- fn()调用的是fn2,间接操作了fn1内部的变量a
f() // 4 -- fn()调用的是fn2,间接操作了fn1内部的变量a
// 分析:该栗产生了一个闭包(只调用了一次外部函数——var f = fn1(),f()调用的不是fn1而是fn2),闭包函数只要没有执行结束--延长了局部变量的生命周期,外部函数的变量就不会消失。
(2)将一个函数作为另一个函数的参数
function showDelay(msg,time) {
setTimeout(function () { // 匿名函数调用了外部函数的smg,因此也产生的闭包
alert(msg)
},time)
}
showDelay('hello world',3000)
3.闭包有什么作用?
(1)延长了函数局部变量的生命周期——在内部函数(前提调用了外部函数的属性或方法)结束调用之前,外部函数相关的变量就不会消失
(2)可以实现在函数外部可以操作函数内部的数据(变量、函数)——通常情况下函数内部的局部变量,在函数外部是不可以访问的(作用域的限制)
Q1:函数执行完毕后,函数内部的局部变量是否还存在?
A:一般不会存在,只有存在在闭包中的那个变量才有可能继续存在
function fn1() { // 闭包中只包含了a
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1() // 如果不把fn1()保存在变量中,fn1执行后就会释放函数内部的变量的内存
f() // 3
f() // 4
Q2:函数外部可以直接访问函数内部的局部变量吗?
A:不能,但是可以通过利用闭包让外部函数操作它(在函数内部用另一个函数封装起来,然后把这个内部函数return出去)
4.闭包的生命周期
(1)闭包何时产生:包含嵌套函数的函数执行时产生(尽管被包含的函数没有被执行,因为函数提升的存在)
(2)闭包何时死亡:
function fn1() {
// 函数执行上下文产生时闭包产生,闭包内已经包含了变量a,只是值为undefined ---由于函数提升的作用,fn2已经得到提升
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1() // fn1被调用
f() // 3
f() // 4
f = null // 闭包死亡:包含闭包的函数对象成为垃圾对象(f不在指向/调用fn1)
4.闭包的应用场景
定义JS模块
步骤:
a。开辟一个具有特定功能的js文件
b。在文件中将私有的变量和方法封装在一个函数中
c。(在函数中)向外暴露包含n个对象或方法
d。外部模块使用时,只需要引入相关文件,然后调用即可
栗子:
fileA:
function myModule() {
// 1.定义私有数据
var msg = "pipixi"
// 2.定义操作数据的函数1
function sayHello() {
console.log(('hello')+ msg.toLocaleUpperCase())
}
// 2.定义操作数据的函数2
function sayGoodbye() {
console.log(('goodbye')+ msg.toLocaleUpperCase())
}
return { // 3.向外暴露对象——供外部使用的方法
sayHello: sayHello,
sayGoodbye: sayGoodbye
}
}
fileB:
// 引入外部js文件
<script type="text/javascript" src="./myModule.js"></script>
<script>
// 利用闭包访问外部函数的变量(可以在谷歌的调试工具中查看到使用了闭包--scope:Closure (myModule) {...})
var module = myModule()
module.sayHello() //hello PIPIXI
module.sayGoodbye() //goodbye PIPIXI
</script>
另一种向外暴露函数的方法:window
(此方法更便利,仅需要引入文件,就可以直接使用)
file1:
(function () { // 使用匿名函数
// 1.定义私有变量
var msg = "pipijie"
// 2.定义操作变量的函数1
function greet() {
console.log(('hello')+ " "+ msg.toLocaleUpperCase())
}
// 2.定义操作变量的函数2
function bye() {
console.log(('goodbye')+ " "+ msg.toLocaleUpperCase())
}
// 将包含内部函数的对象(给外部使用)暴露到window
window.myModule1 = {
greet: greet,
bye: bye
}
})()
file2:
<script type="text/javascript" src="./myModule.js"></script>
<script>
// 因为定义js模块函数时,已经将将函数放到window中,因此可以直接调用
myModule1.greet() // hello PIPIJIE
myModule1.bye() // goodbye PIPIJIE
</script>
5.闭包的缺点
(1)函数执行完毕后,由于内部函数调用外部函数还没有结束而导致外部函数被内部被内部函数调用的局部变量的内存没办法释放,而使占用内存的时间变长
(2)容易导致内存泄漏
结局方案:
(1)尽量少使用闭包
(2)调用结束后及时释放
function fn() {
var arr = new Array(100000)
function fn2() {
console.log(arr.length)
}
return fn2
}
var f = fn()
f()
f = null // 调用结束后,让fn内部函数成为垃圾对象,以便垃圾回收器回收闭包
6.补充知识点:内存溢出和内存泄漏
(1)内存溢出:程序运行所需要的内存不足,导致程序运行出错——因为内存是有限的
(2)内存泄漏:
占用的内存没有及时释放,内存泄漏容易导致内存溢出
常见的内存泄漏的情况:
a。意外的全局变量,如,
function (fn){
a = 5 // 意外的全局变量,在函数fn执行完毕后仍然不释放,继续占内存
console.log(a)
}
b。没有及时清除的定时器或回调函数
setInterval (function(){ // 启动循环定时器后不清除
console.log('hello world')
},1000)
解决:在调用定时器结束后应该及时清除
var interValId = setInterval (function(){ // 启动循环定时器后不清除
console.log('hello world')
},1000)
// clearInterval(interValId) 不需要定时器时应该清除
c。闭包
function fn1 (){
var a = 2
function fn2(){
console.log(a)
}
return fn2
}
var f = fn1()
f() // 此时f指向fn1,f()执行的是fn2,所以变量a的内存不会被自动释放
f = null // 切断f的指向,使f变成垃圾变量
7.闭包相关面试题
题目1:
// 没有涉及闭包——没有涉及内部函数调用外部函数的变量
var name = 'outerName'
var obj = {
name: 'interName',
getName:function (){
return function () {
return this.name //this指向window
}
}
}
alert(obj.getName()()) //outerName
// 涉及了闭包
var name = 'outerName'
var obj = {
name: 'interName',
getName:function (){
var that = this // this保存(指向)的是调用getName的对象即obj!!!!!!——涉及闭包:函数嵌套、内部函数调用外部函数的变量
return function () {
return that.name
}
}
}
alert(obj.getName()()) //interName
题目2:
输出的情况?
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) //undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3) //undefined,?,?,?
var c = fun(0).fun(1)
c.fun(2)
c.fun(3) //undefined,?,?,?