几个概念:
- 作用域:用于确定在何处以及如何查找变量(标识符)的一套规则。
- 词法作用域:词法作用域是定义在词法阶段的作用域。词法作用域是由写代码时将代码和块作用域写在哪里来决定的,因此当词法作用域处理代码是会保持作用域不变(大部分情况)。
- 块作用域:指的是变量和函数不仅可以属于所处的作用域,也可以属于某个代码块(通常用{}包裹)。常见的块级作用域有 with,try/catch,let,const 等。
- 函数作用域:属于这个函数的全部变量都可以在整个函数范围内使用及复用(包括嵌套作用域)。
- 作用域链:查找变量时,先从当前作用域开始查找,如果没有找到,就会到父级(词法层面上的父级)作用域中查找,一直找到全局作用域。作用域链正是包含这些作用域的列表。
- 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。 —— MDN
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
add5
和 add10
都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在 add5
的环境中,x
为 5。而在 add10
中,x
则为 10。
闭包的应用:
1、用来定义模块,限制对代码的访问:下面的privateCounter只能通过Counter.value()去访问
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
var Counter2 = makeCounter();
console.log(Counter2.value()); /* logs 0 */
2、防抖和节流
function throttle(func, duration) {
let shouldWait = false
return function (...args) {
if (!shouldWait) {
func.apply(this, args)
shouldWait = true
setTimeout(function () {
shouldWait = false
}, duration)
}
}
}
const button = document.getElementById('button')
button.addEventListener(
'click',
throttle(function () {
throwBall()
}, 500)
)
function throwBall() {
console.log('throwBall throttle')
}
节流使用场景:
- window
resize
导致UI持续更新 - server端或者client端一些Performance-heavy的用户操作
function debounce(func, duration) {
let timeout
return function (...args) {
const effect = () => {
timeout = null
return func.apply(this, args)
}
clearTimeout(timeout)
timeout = setTimeout(effect, duration)
}
}
const button = document.getElementById('button')
button.addEventListener(
'click',
debounce(function () {
throwBall()
}, 500)
)
function throwBall() {
console.log('throwBall debounce')
}
放抖使用场景:
- 根据输入异步获取搜索建议
- server端批处理(例如多个update重叠)
3、用来做缓存
function keys() {
const cache = {}
return function getRandom() {
let m = Math.floor(Math.random() * 1000)
while (cache[m]) {
m = Math.floor(Math.random() * 1000)
}
cache[m] = m
return m
}
}
// 随机产生不重复的key
generateKeys = keys()
generateKeys()
generateKeys()
4、各种JS库中使用
- Vue watch
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
...
const watcher = new Watcher(vm, expOrFn, cb, options)
...
return function unwatchFn () {
watcher.teardown()
}
}
let unWatch = this.$watch("obj.name",function(newVal, oldVal){
console.log(`${newVal};${oldVal};`)
})
// 取消监听,取消后,obj.name的改变不再会通知
unWatch()
在watch
执行的时候,会返会一个unwatchFn
函数,在这个函数里面,调用了watcher
下的unWatch
方法.这样就不用关心如何去取消监听,直接调用这个方法就可以了.
为什么闭包能起到这些作用呢? 就要从JS的垃圾回收机制去考虑了,下一篇文章一起看一下.