1. 闭包是什么
允许一个函数访问并操作函数外部的变量,即使这个函数在它原本的作用域之外被调用
简单来说:是指有权访问另一个函数作用域中的变量的函数
2. 怎么产生闭包
- 函数嵌套:闭包通常发生在函数内部嵌套另一个函数的情况下。
- 内部函数引用外部函数的变量:内部函数必须引用外部函数作用域中的变量。
- 内部函数在它的词法作用域之外被调用:为了形成闭包,内部函数必须在它被定义的作用域之外被调用。
3. 产生的本质原因
JavaScript 代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。
- 编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。
- 执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。
作用域与作用域链 - 作用域
ES5 中只存在两种作用域:全局作用域和函数作用域。在 JavaScript 中,我们将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套子作用域中根据标识符名称进行变量(变量名或者函数名)查找 - 作用域链
当访问一个变量时,编译器在执行这段代码时,会首先从当前的作用域中查找是否有这个标识符,如果没有找到,就会去父作用域查找,如果父作用域还没找到继续向上查找,直到全局作用域为止,,而作用域链,就是有当前作用域与上层作用域的一系列变量对象组成,它保证了当前执行的作用域对符合访问权限的变量和函数的有序访问。
3. 创建闭包
- 在一个函数内部创建另一个函数
function func(a){
return function (value1, value2){
return (value1 > value2 ? 1 : a);
}
}
let abc = func('A');
console.log(abc(1,2)); //'A'
4. 闭包的应用
- 实现私有变量
function Fun(){
//私有变量
var name = 'tom';
//特权方法
this.getName = function (){
return name;
}
}
var fun = new Fun();
console.log(fun.name);//输出undefined,在外部无法直接访问name
console.log(fun.getName());//可以通过特定方法去访问
- 调用setTimeout
function fun(num){
var age = num;
return function(){
console.log(age);
}
}
var getAge = fun(200);//传入需要的参数,得到函数(闭包)的引用
var age = setTimeout(getAge,1000);//正确输出
- 实现块级作用域(匿名函数自执行)
创建了一个匿名的函数,并立即执行,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,这种机制不会污染全局变量。
for (var i = 1; i <= 10; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
}, 1000);
})(i);
}
执行结果
5. 优缺点与作用
优点:
- 防止对全局作用域的污染
- 私有成员
缺点:
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
作用:
- 数据隐藏和封装:闭包允许你创建私有变量,这些变量只能通过内部函数访问,外部无法直接访问。
- 保持状态:闭包可以用来创建具有持久状态的函数,这在实现模块模式或单例模式时非常有用。
- 回调函数和事件处理器:在异步编程中,闭包经常用于保持对异步操作上下文的引用。
注意事项:
- 内存管理:由于闭包会保留外部函数的作用域,如果不当使用,可能会导致内存泄漏。
- 变量共享:如果多个闭包共享同一个外部函数的变量,它们会互相影响,这可能会导致意外的行为
6. 实际问题
- 实现一个累加器,当每调用一次函数时,就加一
let add = (function() {
let count = 0;
return function() {
return ++count;
}
})()