前端开发中真的没用到闭包嘛|九个日常案例了解常用闭包

在这里插入图片描述


导文

很多前端都感觉自己没用过闭包,当面试官问到:
“现实开发中哪里会用到闭包”
直接哑语。
我们真的没用到闭包嘛?

闭包是什么?

闭包(Closure) 是 JavaScript 中一个非常重要的概念,它涉及到函数、作用域以及变量的生命周期。简单来说,闭包是一个函数,它能够记住并访问定义时的作用域,即使这个函数在外部作用域执行时,仍然能够访问到原本定义时的局部变量。

闭包的定义

闭包的形成条件是:函数内部引用了外部函数的变量,并且这个函数在外部被调用时依然能够访问这些外部变量。换句话说,闭包是一个 “函数 + 词法作用域” 的组合。

闭包的基本特性

  1. 访问外部变量:闭包允许内部函数访问外部函数的局部变量。
  2. 变量的持久性:即使外部函数已经执行完毕,闭包中的内部函数仍然可以访问外部函数中的变量。

举个例子:

function outer() {
  let counter = 0;

  // 内部函数
  function inner() {
    counter++;
    console.log(counter);
  }

  return inner;
}

const myClosure = outer(); // 返回了一个闭包
myClosure(); // 输出: 1
myClosure(); // 输出: 2
解释:
  • outer 是外部函数,它定义了一个局部变量 counter,并返回了内部函数 inner
  • inner 是一个闭包,它引用了外部函数 outer 中的 counter 变量。
  • 即使外部函数 outer 执行完毕,闭包 inner 仍然能够访问到 counter 变量,保持 counter 的状态。

闭包的注意事项:

  • 内存泄漏:闭包能够访问外部函数的局部变量,但如果闭包持有的变量过多且没有释放,可能导致内存泄漏。为了避免这种情况,应注意管理闭包的生命周期。
  • 变量的共享问题:如果在多个闭包中共享变量,可能会产生预期之外的行为,因此要小心作用域链的使用。

总之,闭包是 JavaScript 中非常强大和灵活的特性,它通过保持对外部变量的引用,使得函数的行为更加灵活和强大。

日常开发中哪些是用到闭包了呢?

在日常开发中,闭包的应用非常广泛,下面列举一些常见的场景。

1. 定时器和延迟执行

setTimeoutsetInterval 中,常常使用闭包来记住当前的变量或状态,进行延迟操作。

例如:

function countdown(seconds) {
  let timeLeft = seconds;
  const timer = setInterval(function() {
    console.log(timeLeft);
    timeLeft--;
    if (timeLeft <= 0) {
      clearInterval(timer);
      console.log("Time's up!");
    }
  }, 1000);
}

countdown(5);

这里,setInterval 中的回调函数是一个闭包,它访问了外部函数 countdown 中的 timeLeft 变量。

2. 防抖和节流(Debouncing & Throttling)

在处理大量的事件(如 scrollresizekeypress)时,常使用闭包来实现防抖(Debounce)和节流(Throttle),从而避免频繁执行。

防抖示例

function debounce(func, delay) {
  let timeout;
  return function() {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(this, arguments);
    }, delay);
  };
}

const log = debounce(() => console.log('Function executed!'), 1000);

window.addEventListener('resize', log);

这里,debounce 函数返回一个闭包,确保在用户停止触发事件后的延迟时间内才会执行实际的回调函数。

3. 回调函数与异步编程

在使用回调函数的异步编程中,闭包可以保持对外部变量的访问,确保异步操作完成时,能正确访问这些变量。

例如,异步请求中的闭包使用:

function fetchData(url) {
  let dataLoaded = false;
  // 假设 fetch 请求完成后会调用回调函数
  fetch(url)
    .then(response => response.json())
    .then(data => {
      dataLoaded = true;
      console.log('Data fetched:', data);
    });

  return function() {
    if (dataLoaded) {
      console.log('Data has been loaded');
    } else {
      console.log('Loading data...');
    }
  };
}

const checkDataStatus = fetchData('https://api.example.com/data');
checkDataStatus(); // 可能输出: 'Loading data...'

这里,checkDataStatus 函数是一个闭包,它能访问 fetchData 中的 dataLoaded 变量。

4. 模块模式(Module Pattern)

闭包广泛应用于 JavaScript 模块化开发,用于将变量和方法封装在函数作用域内,避免污染全局作用域。

例如:

const module = (function() {
  let privateVar = 'I am private';

  function privateMethod() {
    console.log(privateVar);
  }

  return {
    publicMethod: function() {
      privateMethod();
    }
  };
})();

module.publicMethod(); // 输出: I am private

在这个例子中,privateVarprivateMethod 是私有的,外部只能访问公开的 publicMethod
在 Vue.js 中,闭包的使用并不像在 JavaScript 中直接调用函数时那么显而易见,但仍然有一些地方可以体现闭包的特性,主要体现在事件处理、计算属性、生命周期钩子和组件方法中。以下是 Vue 中一些常见的闭包应用场景:

5. ** Vue事件处理中的闭包**

在 Vue 中,经常会使用事件处理方法。由于 Vue 会自动处理事件绑定,这时方法本身可能会变成一个闭包,能够访问它的外部作用域中的变量。

例如,假设有一个按钮,点击时增加一个计数值:

<template>
  <button @click="increment">Click me</button>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count++;
      console.log(this.count);
    }
  }
};
</script>

在这个例子中,increment 方法是一个闭包,它可以访问 Vue 组件的 data 中的 count 变量,即使该方法是通过 @click 事件触发的。即使 increment 方法和事件处理函数被分离,它仍然能访问到 this.count,因为它保存了对外部作用域的引用。

6. 计算属性中的闭包

计算属性是 Vue 中的一个重要特性,它可以依赖其他数据,并在依赖的数据变化时自动重新计算。计算属性背后也有闭包的实现:每当计算属性被访问时,它会闭包保存相关的响应式依赖,直到计算属性的值被更新。

<template>
  <div>
    <p>{{ doubledCount }}</p>
    <button @click="count++">Increase Count</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    };
  },
  computed: {
    doubledCount() {
      return this.count * 2;
    }
  }
};
</script>

在这个例子中,doubledCount 是一个计算属性,它闭包保存了 this.count 的依赖关系。每当 this.count 发生变化时,doubledCount 会自动重新计算。

7. 生命周期钩子函数中的闭包

Vue 组件中的生命周期钩子,如 createdmountedupdated 等,通常会引用组件的数据或方法,因此它们本质上也是闭包。这意味着即使在组件的生命周期内某些状态发生变化,这些钩子函数仍能访问到组件的上下文。

<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: "Hello, Vue!"
    };
  },
  mounted() {
    setTimeout(() => {
      this.message = "Hello, World!"; // 这里的箭头函数是闭包
    }, 1000);
  }
};
</script>

这里,mounted 钩子内的箭头函数是一个闭包,它能够访问到 this.message,即使 setTimeout 是异步执行的。

8. 方法中的闭包

Vue 方法(特别是在 methods 中定义的那些)可能会变成闭包,并且这些方法可以访问到组件实例中的所有数据和方法。在异步操作中,闭包特别有用,因为它们能保证访问到正确的上下文(即 this)。

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="delayedChangeMessage">Change message</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: "Initial message"
    };
  },
  methods: {
    delayedChangeMessage() {
      setTimeout(() => {
        this.message = "Updated message";  // 闭包保存了对this的引用
      }, 2000);
    }
  }
};
</script>

这里,delayedChangeMessage 方法内的箭头函数是闭包,它能够引用到 this.message,即使延迟了两秒钟才执行。

9. vue v-for 循环中的闭包

在 Vue 的 v-for 循环中,也可能会使用闭包,尤其是当每个循环项都绑定了事件时。循环中的每个函数都会形成闭包,保存其循环上下文。

<template>
  <div>
    <ul>
      <li v-for="(item, index) in items" :key="index" @click="showItem(index)">
        {{ item }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: ['Item 1', 'Item 2', 'Item 3']
    };
  },
  methods: {
    showItem(index) {
      alert('Item index: ' + index);
    }
  }
};
</script>

在上面的代码中,showItem 方法通过 v-for 中的每个 li 元素绑定了事件监听器。每个事件处理程序都是一个闭包,它可以访问到 index 变量,即使这些事件处理函数是在不同的上下文中调用的。

您好,我是肥晨。
欢迎关注我获取前端学习资源,日常分享技术变革,生存法则;行业内幕,洞察先机。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奶糖 肥晨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值