js 闭包

4/3/2023 js

# 闭包

闭包其实就是一个可以访问其他函数内部变量的函数。创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以 访问到当前函数的局部变量。

闭包产生的本质就是:当前环境中存在指向父级作用域的引用

# 闭包的特性

  • 函数内再嵌套函数
  • 内部函数可以引用外层的参数和变量
  • 参数和变量不会被垃圾回收机制回收

# 闭包的用途

  • 创建私有变量 闭包可以使我们在函数外部能够访问到函数内部的变量。通过使用闭包,我们可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量
  • 使已经运行结束的函数上下文中的变量对象继续留在内存中 因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收

其实闭包的本质就是作用域链的一个特殊的应用。

原始类型存储位置:局部变量才是被存储在栈上,全局变量存在静态区域上,其它都存储在堆上。

# 闭包产生的原因

闭包产生的本质就是:当前环境中存在指向父级作用域的引用

# 闭包的表现形式

  • 返回一个函数
  • 在定时器、事件监听、Ajax 请求、Web Workers 或者任何异步中,只要使用了回调函数,实际上就是在使用闭包
// 定时器
setTimeout(function handler(){
  console.log('1');
}1000);
// 事件监听
$('#app').click(function(){
  console.log('Event Listener');
});
1
2
3
4
5
6
7
8
  • 作为函数参数传递的形式
var a = 1;
function foo(){
  var a = 2;
  function baz(){
    console.log(a);
  }
  bar(baz);
}
function bar(fn){
  // 这就是闭包
  fn();
}
foo();  // 输出2,而不是1
1
2
3
4
5
6
7
8
9
10
11
12
13
  • IIFE(立即执行函数)(匿名函数)
var a = 2;
(function IIFE(){
  console.log(a);  // 输出2
})();
1
2
3
4

# 高频面试题

# 1、说说你对闭包的理解

  • 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,因为只有函数才会产生作用域的概念
  • 闭包最大的用处
    • 可以读取函数内部的变量
    • 让这些变量始终保持在内存中
  • 另一个用处:封装对象的私有属性和私有方法
var aaa = (function () {
  var a = 1;
  function bbb() {
    a++;
    console.log(a);
  }
  function ccc() {
    a++;
    console.log(a);
  }
  return {
    b: bbb, //json结构
    c: ccc,
  };
})();
console.log(aaa.a); //undefined
aaa.b(); //2
aaa.c(); //3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • 好处:能够实现封装和缓存
  • 坏处:消耗内存,不正当使用会造成内存泄漏,滥用闭包会造成网页性能问题(解决办法:在退出函数之前,将不使用的局部变量全部删除)
  • 应用场景之防抖
// 防抖
function debounce(fn, delay = 300) {
  let timer; //闭包引用的外界变量
  return function () {
    const args = arguments;
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2、如何解决循环输出问题

for(var i = 1; i <= 5; i ++){
  setTimeout(function() {
    console.log(i)
  }, 0)
}
1
2
3
4
5

这段代码从控制台输出结果是5个6

问题1、为什么都是6?

因为setTimeOut是宏任务,因为JS单线程的eventloop机制,在主线程同步任务执行完成后才会去执行宏任务,因此循环结束之后才会去执行SetTimeOut回调函数

setTimeOut函数是一个闭包,根据作用域链网上找的的话,它的父级作用域就是window,变量i就是window上的全局变量,开始执行setTimeOut之前i就已经变成6了,所以最终输出5个6

问题2、如何让它一次输出12345?

  • 使用ES6中的let 因为ES6之后JS有了块级作用域,一个{}就是一个块级作用域,使用let定义变量会使i变为块级局部变量
for(let i = 1; i <= 5; i++){
  setTimeout(function() {
    console.log(i);
  },0)
}
1
2
3
4
5
  • 匿名函数(IIFE,立即执行函数) 当每次 for 循环时,把此时的变量 i 传递到定时器中,然后执行
for(var i = 1;i <= 5;i++){
  (function(j){
    setTimeout(function timer(){
      console.log(j)
    }, 0)
  })(i)
}
1
2
3
4
5
6
7
  • 使用定时器的第三个参数(定时器回调函数的参数)
for(var i=1;i<=5;i++){
  setTimeout(function(j) {
    console.log(j)
  }, 0, i)
}
1
2
3
4
5
Last Updated: 4/18/2023, 2:49:49 PM
晴天
周杰伦