00 引入闭包的概念
需求:点击某个按钮,提示弹出的是第n个按钮.
实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>测试一</button>
<button>测试二</button>
<button>测试三</button>
<script>
//第一种实现方法(不能实现,因为i循环后才执行onclick)
// var btn = document.getElementsByTagName('button')
// for(var i = 0, length = btn.length; i < length ; i++) { //i是全局变量,i只有一个
// var btns = btn[i]
// btns.onclick = function () { //i循环后才执行onclick
// alert('第'+(i+1)+'个') //i = 4
// }
// }
//第二种实现方法
// var btn = document.getElementsByTagName('button')
// for(var i = 0,length = btn.length; i < length ; i++){
// var btns = btn[i]
// btns.index = i //给出每个按钮对应的下标
// btns.onclick = function(){
// alert('第'+(this.index+1)+'个')
// }
// }
//使用闭包的实现方法
var btn = document.getElementsByTagName('button')
for(var i = 0, length = btn.length; i < length ; i++) {
(function(i) { //i是局部变量 全局i传给局部i,i就进入闭包
var btns = btn[i] //i是局部变量
btns.onclick = function () {
alert('第'+(i+1)+'个')
}
})(i) //i是全局变量
}
</script>
</body>
</html>
01理解闭包
1.如何产生闭包
*当一个嵌套的内部函数(子)引用了嵌套的外部函数(父) 的 变量(函数)时,就产生了闭包
2.闭包到底是什么
* 使用chrome调试查看
* 理解一: 闭包是嵌套的内部函数(绝大部分人)
* 理解二: 包含被引用变量(函数)的对象(极少数人) 即a
* 注意: 闭包存在于嵌套的内部函数中
3.产生闭包的条件
*函数嵌套
*内部函数引用了外部函数的数据(变量/函数)
*函数调用
实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function fn1(){
var a = 2
var b = 'abc'
function fn2() { //执行函数定义,调用发fn1就执行函数定义,而不需要调用函数fn2 //执行函数定义就会产生闭包,不需要调用内部函数
console.log(a)
}
}
fn1()
</script>
</body>
</html>
闭包的两种理解(附图解释):
* 理解一: 闭包是嵌套的内部函数(绝大部分人)
* 理解二: 包含被引用变量(函数)的对象(极少数人) 即a
02常见的闭包
常见的闭包形式:
1.将函数作为另一个函数的返回值
2.将函数作为实参传递给另一个函数调用
产生多少个闭包:外部函数调用几次,产生多少个内部函数对象
实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 1.将函数作为另一个函数的返回值
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1() //存返回值fn2函数
f() //3 //调用内部函数fn2 //操作内部函数的变量
f() //4 //再执行一遍 //调用内部函数fn2 //操作内部函数的变量
//因为执行外部函数才会创建内部函数对象,跟内部函数执行几次没有关系
fn1() //执行fn1()就把内部函数又创建了一遍 //看外部函数执行几次就创建几个闭包
// 2.将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
setTimeout(function () {
alert(msg) //用到了外部函数变量msg
}, time)
}
showDelay('kate', 2000)
</script>
</body>
</html>
03.闭包的作用
1.使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
2.让函数外部可以操作(读写)到函数内部的数据(变量/函数) 存在才可以操作
问题:
1.函数执行完后,函数内部声明的局部变量是否还存在? 一般是不存在,存在于闭包中的变量才可能存在
2.在函数外部能直接访问函数内部的局部变量吗 不能,但可以通过闭包让外部操作它
实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>闭包的作用</title>
</head>
<body>
<script>
function fn1() {
var a = 2
function fn2() { //fn2和fn3都是内部变量,执行完后会被释放
a++
console.log(a)
}
function fn3() { //fn2和fn3都是内部变量,执行完后会被释放,fn3没有成为垃圾对象,因为f引用着
a-- //a被fn3函数引用了,所以进入闭包的是a
console.log(a)
}
return fn3
}
var f = fn1() //f指向函数对象,把返回值存起来了,函数对象关联着闭包,闭包里有a //让内部函数中的变量存活在内存
f() //1 //操作内部函数的变量
f() //0 //操作内部函数的变量
</script>
</body>
</html>
04.闭包的生命周期
1.产生: 在嵌套内部函数定义执行完时就产生了(不是在调用的时候产生)
2.死亡: 在嵌套的内部函数成为垃圾对象时
实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function fn1() {
//调用fn1,闭包就已经产生了(因为函数提升,内部函数对象已经创建了)
var a = 2
// function fn2() {
// a++
// console.log(a)
// }
var fn2 = function() {
a++
console.log(a)
}
//产生闭包,函数定义执行,前面是函数执行
return fn2
}
var f = fn1()
f() // 3
f() // 4
f = null //闭包死亡(包含闭包的函数对象成为了垃圾对象)
</script>
</body>
</html>
05.闭包的应用
闭包的应用: 定义js模块
* 具有特定功能的js文件
* 将所有的数据和功能都封装在一个函数内部(私有的)
* 只向外暴露一个包含n个方法的对象或函数
* 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
实现代码:
myModule.js
function myModule() {
//私有的数据
var msg = 'bigface'
//操作数据的函数
function doSomething() {
console.log('doSomething()'+ msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing()'+ msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="myModule.js"></script>
<script>
// var fn = myModule()
// fn()
//要先执行myModule(),返回对象,对象再调用方法
var module = myModule()
module.doSomething()
module.doOtherthing()
</script>
</body>
</html>
实现代码2:
myModule2.js
(function () {
//私有的数据
var msg = 'bigface'
//操作数据的函数
function doSomething() {
console.log('doSomething()'+ msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing()'+ msg.toLowerCase())
}
//要暴露的东西添加为window的属性
window.myModule2 = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})()
// // 好处:代码压缩,如window可以压缩成w
// (function (window) { //+window
// //私有的数据
// var msg = 'bigface'
// //操作数据的函数
// function doSomething() {
// console.log('doSomething()'+ msg.toUpperCase())
// }
// function doOtherthing() {
// console.log('doOtherthing()'+ msg.toLowerCase())
// }
// //要暴露的东西添加为window的属性
// window.myModule2 = {
// doSomething: doSomething,
// doOtherthing: doOtherthing
// }
// })(window) //+window
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 第一种方式需要先执行myModule(),第二种直接使用 两种方式都有闭包-->
<script src="myModule2.js"></script>
<script>
myModule2.doSomething()
myModule2.doOtherthing()
</script>
</body>
</html>
06.闭包的缺点及解决
1.缺点
* 函数执行完后,函数内的局部变量没有释放,占用内存时间会变长
*容易造成内存泄露
2.解决:
*能不用闭包就不用
*及时释放
实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>闭包的缺点及解决</title>
</head>
<body>
<script>
function fn1() {
var arr = new Array[100000]
function fn2() {
console.log(arr.length)
}
return fn2
}
var f = fn1() //必须让f指向内部函数对象,不然fn1执行完arr局部变量就被释放掉了
f()
f = null //让内部函数成为垃圾对象-->回收闭包
</script>
</body>
</html>
07.面试题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>07_面试题1</title>
</head>
<body>
<script>
// 代码片段一
//没有闭包
var name = "the window"
var object = {
name: "my object",
getNameFunc: function() {
return function() {
return this.name
}
}
}
alert(object.getNameFunc()()) //the window //object.getNameFunc() 是return 的fun,然后fun(),所以this是指window
// 代码片段二
//有闭包:函数嵌套 和 内部函数引用外部函数变量
var name2 = "the window"
var object2 = {
name: "my object",
getNameFunc: function() {
var that = this //object2
return function() {
return that.name2
}
}
}
alert(object2.getNameFunc()()) //my object
</script>
</body>
</html>
补充:内存溢出与内存泄露
1.内存溢出
* 一种程序运行出现的错误
* 当程序运行需要的内存 超过了 剩余的内存时,就出抛出内存溢出的错误
2.内存泄露
*占用的内存没有及时释放
*内存泄露积累多了就容易导致内存溢出
*常见的三种内存泄露:
**意外的全局变量
**没有及时清理的计时器或回调函数
**闭包
实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>内存溢出与内存泄露</title>
</head>
<body>
<script>
// 1.内存溢出
var obj = {}
for (var i = 0;i < 10000; i++) {
obj[i] = new Array(10000000)
console.log('------')
}
// 2.内存泄露
// 意外的全局变量
function fn() {
a = 3 //用var去定义变量
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>
</body>
</html>