作用域和闭包
1.作用域和执行环境的关系
2.立即执行函数
3.闭包【重点】
- 要求:清楚的说出来闭包的概念,作用和优缺点
01-作用域和执行环境的关系
`标识符`:变量,函数名,形参,对象的属性名
`作用域`:标识符起作用的范围。全局作用域和局部作用域
`执行环境`:代码运行的环境。全局执行环境和局部执行环境
`栈`:浏览器内存开辟的一个空间,用来存储执行环境。记住'先入后出'的原则
`作用域链`:规定了标识符的查找规则。'先在当前作用域内查找,找到就使用,并终止查找,没有找出就在更外层的作用域查找,如果在全局作用域都没有找到,就会报错'。
`js代码的执行过程【理解】`:当打开浏览器的时候,在栈中默认就会有一个全局执行环境,这个环境中保存了全局的标识符;当需要执行函数时,就会在内存中开辟一个新的空间,用来存储函数执行环境,该环境中保存了局部的标识符,这个过程就叫'入栈',当函数执行完成,就会'出栈',对应的标识符会被销毁,释放内存空间。'需要注意的是':全局的执行环境没有入栈和出栈的过程,也就是说全局的标识符会一直存储在栈中,得不到销毁,会造成内存浪费,甚至内存泄漏。
`结论`:当函数执行完成后,对应的标识符就会被销毁,而全局声明的标识符,没办法被自动销毁,会造成内存浪费。
02-立即执行函数【理解】
`概念`:不需要调用,当声明好之后,就会立即执行的函数
`语法`:
(function(){})() | (function(){}())
`特点`:
1. 不需要调用,会立即执行
2. 立即执行函数自带作用域
`作用`:通过立即执行函数,将更多的全局标识符变成了局部的标识符,减少全局标识符的声明,一定程度上节省了内存空间;同时还可以避免因为变量名冲突造成的全局污染
'全局污染':声明了多个全局变量,有时候会产生变量名冲突的问题
`注意`:建议手动在立即执行函数的前面添加';', 否则可能会报错
03-闭包【重点】
`概念`:跨作用于访问了变量,这种现象就是闭包
`标准语法【了解】`:在父函数内部声明变量,在子函数内部访问该变量,再将子函数通过return关键词返回出去,或者将子函数挂载到全局window对象上的任意属性上即可
`作用`: 早期模块化的实现思想
`优点`:
1. 减少了全局标识符的声明,一定程度上节省了内存空间,同时可以避免因为变量名冲突造成的全局污染
2. 可以保证闭包内部私有标识符的安全性
`缺点`:那个挂载到全局的变量,一直得不到释放,会在一定程度上造成内存浪费
04-防抖和节流【面试题】
这两个都是和短时间内频繁多次触发同一事件相关的性能优化
`防抖`: 短时间内频繁触发同一事件时,只有最后一次生效. 例如电梯关门的效果
`案例`: 获取输入框的内容:在2s内,如果用户一直输入,就不获取,2s后,如果用户没有输入内容,再去获取输入框的内容
'功能拆分': 设置一个延时定时器,在2s后获取输入输入的内容,如果2s内,用户输入了新的内容,就清除定时器,并重新开启一个定时器
'代码实现':
var timer = 0
inpEle.addEventListener('input', () => {
// 1. 清除上一个定时器
clearTimeout(timer)
// 开启一个新的定时器
timer = setTimeout(function(){
console.log('输入框的内容', inpEle.value)
}, 2000)
})
`节流`: 短时间内频繁触发同一个事件时,在单位时间内,只生效一次。例如lol英雄的大招
`案例`: 点击按钮,获取输入框的内容:在3s内只能获取一次输入框的内容
'功能拆分':记录下当前的时间,当点击按钮后,记录下点击按钮时的时间;如果两次的时间间隔大于3s,就获取输入框的内容,并将当前时间保存给上一次的时间
'代码实现':
// 1. 记录最开始的时间
let prevTime = +new Date()
btn.addEventListener('click', () => {
// 2. 获取点击按钮的时间
let curTime = +new Date()
// 3. 判断是否两次时间间隔超过3s
if(curTime - prevTime > 3000) {
console.log('输入框的内容', inpEle.value)
// 4. 将当前时间保存给上一次的时间
prevTime = curTime
}
})