什么是闭包?
闭包的概念是指有权访问另一个函数作用域中的变量的函数,即使另一个函数已经执行完毕。
示例1:
function parent() {
var space = 'Parent Context';
return function child() {
console.log(space)
}
}
var c = parent();
c(); // Parent Context
上面这段代码就是一个简单的闭包函数。要理解闭包,首先要对js的执行环境和作用域链有了解。
执行环境
执行环境(execution context)也叫执行上下文
- 全局执行环境 - 即全局的默认环境,被认为是
window对象,所有全局变量和函数都是作为window对象的属性和方法创建的。 - 函数执行环境 - 函数体内的执行环境。当函数被调用时函数的执行环境被创建,执行完毕后,该环境被销毁,同时保存在其中的所有变量和函数定义也随之被销毁。
- eval执行环境 - eval(...)函数中的动态执行环境(略)。
执行环境(
execution context)定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
当代码在一个环境中执行时,都会创建一个作用域链。 作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。整个作用域链是由不同执行位置上的变量对象按照规则所构建一个链表。作用域链的最前端,始终是当前正在执行的代码所在环境的变量对象。
作用域链
示例2:
var space1 = 'Global Context';
function parent() {
var space2 = 'Parent Context';
console.log(space1, space2, space3);// 可访问 space1 和 space2;无法访 space3
function child() {
var space3 = 'Child Context';
console.log(space1, space2, space3);// 可访问 space1、space2、space3
}
child(); // child 调用时 child 内部执行环境被创建
}
parent(); // parent 调用时 parent 内部执行环境被创建
console.log(space1, space2, space3); // 只可访问 space1;无法访 space2 和 space3
示例2中我们可以看到,内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。全局执行环境是作用域链的末端,内部环境都可以访问。
我们对示例1进行修改得到示例3:
function parent() {
var i = 0;
return function child() {
console.log(++i)
}
}
var c = parent();
c(); // 1
c(); // 2
c(); // 3
示例3中,在函数 parent函数体内声明变量 i ,执行函数 parent并将回调赋值给变量 c。函数 parent 的执行环境 Parent Context 中的变量 i 只有函数 child 能够访问,但是我们能够通过执行函数 c 实现对变量 i 的修改操作。
假如我们省略掉【执行函数 parent并将回调赋值给变量 c】这步操作,可以得到示例4:
var child = null;
function parent() {
var i = 0;
child = function () {
console.log(++i)
}
}
parent()
child() // 1
child() // 2
child() // 3
是不是发现 示例3 和 示例4 其实结果是一样的?
剩下的懂得都懂,代码附上自行参悟去吧。
应用场景
防抖
function debounce(fn, delay){
let timer = null;
return function() {
const _this = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(function(){
fn.apply(_this, args);
}, delay)
}
}
节流
function throttle(fn, delay) {
let timer = null;
return function () {
const _this = this;
const args = arguments;
if (!timer) {
timer = setTimeout(function () {
fn.apply(_this, args);
timer = null;
}, delay);
}
}
}
参考: