JavaScript 闭包的深入探索

1,071 阅读4分钟

哈哈,终于又能让大家看到我的文章了,开心😊。感觉又有好长时间没写了,各位看官,请跟随我一起畅游知识的海洋吧。

上次跟大家分享了 JavaScript作用域 的相关知识点,也不知道大家看下去多少,那可是基础中的基础,学习就是这样,只有基础牢固了,接收其他知识的时候才能如鱼得水,如胶似漆,如梦如幻,如……………………

(请原谅作者语文知识的欠缺)

今天主要是跟大家分享一下javascript中闭包的问题,如你所料,又是一篇基础分享,赶紧止住这些呼之欲出的废话,开始我们的表演 ↓↓↓↓

1.什么是闭包

在我们看到的各种技术书籍中,都会把闭包描述的很抽象,这里我们先给闭包一个定义 闭包就是可以在函数外部访问函数作用域中的的内部变量,同时它也可以将变量长久的保存在内存中 。在javascript中,闭包是一个重难点,是因为它确实很难理解,重要是因为在javascript中很多高级写法都需要闭包来实现。

先来个开胃小栗子吧。

1.1 调用函数,实现每次+1的输出操作。

function a () {
  let number = 10;
  number++;
  console.log(number);
}
a() // 11
a() // 11
a() // 11

上述代码中,并不能实现我们所需要的效果。这是为什么呢?

因为函数a在执行完成后,就会将内部变量number释放掉。每次执行a函数都会重新创建执行 变量创建 -- 使用 -- 销毁 的整个过程,所以函数调用会一直输出同一个结果。那么如何才能达到题目的要求?

改进版

function a () {
  let number = 10;
  return function () {
    number++;
    console.log(number)
  }
}
let result = a()

result(); // 11
result(); // 12
result(); // 13

通过改进,已经实现了题目所要求的效果。

这里是因为执行a之后,还存在number变量的引用,会将变量长久的保存在内存中,不会释放掉。所以可以实现依次累加的效果。

1.2 实现下列代码从 0-9 打印输出

问题

for(var i = 0;i < 10 ; i ++) {
	setTimeout(function () {
    console.log(i)
  }, 0);
}
// 输出10个10

问题剖析:

  1. setTimeout是异步执行的,(这里与JavaScript事件队列相关,后续会出相关文章。)。等到循环执行完成之后才会执行打印函数。
  2. var定义的是全局变量,i++修改的也是全局变量
  3. 在打印函数执行的时候,i 的值已经为10。所以打印函数打印的都是10

改进

for(var i = 0;i < 10 ; i ++) {
  function a(i){
    return function (){
      console.log(i)
    }
  }
	setTimeout(a(i), 0);
}

解决方法剖析:

  1. 由于setTimtout执行的是闭包函数,每次传递到 a 函数中的 i 值都会被保存,所以打印函数打印的是自己保存的 i

相信你看到这里已经累了。先休息下吧,眺望一下远方,舒缓一下眼睛。如果你是在车上,请闭上双眼休息一下。


2.闭包的用途

2.1 使用闭包实现 setTimeout 传参

形如这种方式使用,我们可以用闭包实现 setTimeout 传参的效果

for(var i = 0;i < 10 ; i ++) {
  function a(i){
    return function (){
      console.log(i)
    }
  }
	setTimeout(a(i), 0);
}

2.2 函数防抖和节流

这里只通过实例代码让大家看下,后期会出文章说明。

函数防抖实现

function debounce(fn) {
  let timeout = null;
  return function () {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      fn.apply(this, arguments);
    }, 500);
  };
}

函数节流实现

function throttle(fn) {
  let canRun = true;
  return function () {
    if (!canRun) return;
    canRun = false;
    setTimeout(() => {
      fn.apply(this, arguments);
      canRun = true;
    }, 500);
  };
}

3.使用闭包的注意点

3.1 内存泄漏问题

因为闭包会将变量长久的保存在内存中,所以会造成内存泄漏,所以需要在使用完成之后销毁变量,释放内存。

3.2 this指向问题

此问题我们在讨论this指针的时候讨论过,闭包的this是指向window的。

var obj = {
  getName: functioin() {
		return function () {
      console.log(this)
    }
  }
}
obj.getName()() // window

4.实现篇

4.1 使用闭包实现一个三击响应事件

第一步,实现点击输出123

let box = document.getElementById('box')
box.onclick = (function () {
  let count = 1;
  return function () {
    console.log(count)
    count ++
  }
})()

点击 box 的元素,可以看到控制台输出123

第二步,添加时间控制,1s内连续点击三次后执行事件

let box = document.getElementById('box')
box.onclick = (function () {
  let count = 0;

  let start = Date.now() // 添加开始时间
  return function () {
    count ++
    if (count === 3) {
      let end = Date.now() // 结束时间
      if (end - start <= 1000) { // 如果三次点击事件在1s内,输出成功
        console.log('三击事件响应成功')
      }
      count = 0; // 三击之后将条件重置
      start = Date.now() // 开始时间为当前时间
    }
  }
})()

第三步,友情提示

将下方代码修改之后,可由三击事件变为任意多击事件。

if (count === n) // n代表点击次数

好了亲爱的看官朋友们,本次分享的内容就到这里了。希望你们能在自己喜欢的道路上越走越远。🤗🤗🤗