闭包全面理解
一、JS作用域
1.1 作用域理解(范围)
1.1.1 全局作用域
-
全局作用域在页面打开时被创建,页面关闭时被销毁
-
编写在script标签中的变量和函数,作用域为全局,在页面的任意位置都可以访问到
-
在全局作用域中有全局对象window,代表一个浏览器窗口,由浏览器创建,可以直接调用
-
全局作用域中声明的变量和函数会作为window对象的属性和方法保存
1.1.2 函数作用域
- 调用函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁
- 每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的
- 在函数作用域中可以访问到全局作用域的变量,在函数外无法访问到函数作用域内的变量
- 在函数作用域中访问变量、函数时,会先在自身作用域中寻找,若没有找到,则会到函数的上一级作用域中寻找,一直到全局作用域
1.2 作用域深度理解(预编译)
1.2.1 执行期的上下文
- 当函数被调用的时候会先进行预编译且预编译的时候会创建内部对象(我们无法看到的对象)
- 当函数代码执行的前期,会创建一个执行期上下文的内部对象 AO(作用域)
- 在全局代码执行的前期,会创建一个执行期的上下文的内部对象GO
1.2.2 函数作用域预编译
-
创建ao对象AO{}
-
找形参和变量声明将变量和形参名当做AO对象的属性名,值为undefined
-
实参形参相统一
-
在函数体里面找函数声明,值赋予函数体
函数声明: function a() { }
函数表达式:var b = function () { }
1.2.3 全局作用域的预编译
- 创建GO对象
- 找变量声明将变量名作为GO对象的属性名值是undefined
- 找函数声明值赋予函数体
1.2.4 作用域链图解
var global
function a() {
var aa = 234
function b() {
var bb = 123
}
b()
}
a()
- scope chain : 作用域链
- AO:函数作用域预编译
- GO:全局作用域预编译
- 作用域链: 0(本身)-> 1(上一级)-> 2(全局)
a() 定义
a() 执行
b() 定义
b() 执行
1.2.5 预编译实际应用(阿里面试题)
// 函数调用后的打印结果?
function fn(a, c) {
console.log(a) // function a() { }
var a = 123
console.log(a) // 123
console.log(c) // function c() { }
function a() { }
if (false) {
var d = 678
}
console.log(d) // undefined
console.log(b) // undefined
var b = function () { }
console.log(b) // function () { }
function c() { }
console.log(c) // function c() { }
}
fn(1,2)
//理解-函数作用域预编译
//1. 创建ao对象AO{}
// AO{
//2. 找形参和变量声明将变量和形参名当做AO对象的属性名,值为undefined
/*
* a: undefined
* c: undefined
* d: undefined
* b: undefined
*/
//3. 实参形参相统一
/*
* a: undefined 1
* c: undefined 2
* d: undefined
* b: undefined
*/
//4. 在函数体里面找函数声明值赋予函数体
/*
* a: undefined 1 function a() { }
* c: undefined 2 function c() { }
* d: undefined
* b: undefined
*/
// }
二、闭包基础学习
2.1 作用域链理解闭包
- 图中叉掉部分为a()执行完后销毁的作用域链,直线部分为b()定义时创建的作用域链
var global
function a() {
var aa = 234
function b() {
var bb = 123
console.log(aa)
}
return b
}
let fn = a()
fn() //234
- 解释:
易知,a()执行、b()定义作用域链一样(可以访问的数据相同–AO,GO),当 a() 执行完,a作用域链销毁,此时在外部调用b() 函数时重新进行b定义且b()函数重新构建其本身的作用域链(图中直线部分),这就形成了闭包。
2.2 闭包概念
- 闭包就是能够读取其他函数内部变量的函数
- (理解)定义在一个函数内部的函数
2.3 闭包作用
-
可以读取函数内部的变量
-
让变量始终保存在内存中
function createIncrementor(start) { return function () { return start++; }; } var inc = createIncrementor(5); inc() // 5 inc() // 6 inc() // 7
三、闭包实现单例模式
- 单例模式也称为单体模式,规定一个类只有一个实例,并且提供可全局访问点;
- 闭包实现登录弹框(单例)
<body>
<button id="loginBtn">登录</button>
<script>
var createLogin = function () {
var div = document.createElement('div')
div.innerHTML = `
<h1 class = "title">我是登录的弹窗</h1>
<button class = "footerBtn">退出</button>
`
div.style.display = 'none'
document.body.appendChild(div)
return div
}
var getSingle = function (fn) {
var result
return function () {
return result || (result = fn.apply(this, arguments))
}
}
var create = getSingle(createLogin)
document.getElementById("loginBtn").onclick = function () {
var loginLay = create()
loginLay.style.display = 'block'
}
</script>
</body>
/*
当点击登录的时候会出现如下内容
登录
我是登录的弹窗
退出
*/
四、闭包应用
4.1 防抖函数
-
指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间(清除定时器)
-
理解:在一段固定的时间内,只能触发一次函数,在多次触发事件时,只执行最后一次
//要求:输入框的结果只出现一次(是我在键盘抬起不在输入后的1s)
<body>
<input type="text" id="input">
<script>
var input = document.getElementById('input')
//防抖的函数
function debounce(delay) {
let timer
return function (value) {
//保证每一次调用都会清除之前的定时器
/**
* 1.我们想要清除的setTimeout
* 2.所以timer需要一直保存在内存中,否则我们清除的timer为undefined
*/
clearTimeout(timer)
timer = setTimeout(function () {
console.log(value)
}, delay)
}
}
var debounceFunc = debounce(1000)
input.addEventListener('keyup',function(e){
debounceFunc(e.target.value)
})
</script>
</body>
- 防抖前
会打印每一次输入的结果
- 防抖后
输入框的结果只出现一次(是我在键盘抬起不在输入后的1s)
4.2 节流函数
- 限制一个函数在一定时间内只能执行一次
//要求:在2s内,不管你点击多少次,就打印一次结果
<body>
<button id="btn">点击</button>
<script>
//防抖的函数
function thro(func, wait) {
let timerOut
// 将timerOut保存到闭包中
return function () {
if (!timerOut) {
timerOut = setTimeout(function () {
func()
//执行完函数之后进行timerOut初始化
timerOut = null
}, wait)
}
}
}
function handle() {
//打印一个随机数
console.log(Math.random())
}
document.getElementById('btn').onclick = thro(handle, 2000)
</script>
</body>
五、 闭包优缺点
5.1 优点
可以重复使用变量,并且不会造成变量污染
5.2 缺点
比普通函数更占用内存,会导致网页性能变差,在IE下容易造成内存泄露。
-
内存泄漏:
指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
-
闭包造成的内存泄漏:
一旦循环引用或创建闭包,就会占据大量内存,可能会引起内存泄漏
-
解决方案:
在退出函数之前,将不使用的局部变量全部删除,可以使变量赋值为null