首页/文章/javascript

js闭包的6种应用场景!!!这下会用了

2023-11-30
10554 分钟
...

闭包的基本定义

如果一个函数访问了此函数的父级及父级以上的作用域变量,那么这个函数就是一个闭包。闭包会创建一个包含外部函数作用域变量的环境,并将其保存在内存中,这意味着,即使外部函数已经执行完毕,闭包仍然可以访问和使用外部函数的变量。


function fn1() {
  let a = 1;
  function fn2() {
    a++;
    console.log(a);
  }
  return fn2;
}
const fn2 = fn1();

fn2() 
fn2() 

闭包的优缺点及特性

  • 闭包的优点
  1. 保护变量:闭包可以将变量封装在函数内部,避免全局污染,保护变量不被外部访问和修改。
  2. 延长变量生命周期:闭包使得函数内部的变量在函数执行完后仍然存在,可以在函数外部继续使用。
  3. 实现模块化:闭包可以创建私有变量和私有方法,实现模块化的封装和隐藏,提高代码的可维护性和安全性。
  4. 保持状态:闭包可以捕获外部函数的变量,并在函数执行时保持其状态。这使得闭包在事件处理、回调函数等场景中非常有用。
  • 闭包的缺点
  1. 内存占用:闭包会导致外部函数的变量无法被垃圾回收,从而增加内存占用。如果滥用闭包,会导致内存泄漏问题。
  2. 性能损耗:闭包涉及到作用域链的查找过程,会带来一定的性能损耗。在性能要求高的场景下,需要注意闭包的使用。
  • 闭包的特性
  1. 函数嵌套:闭包的实现依赖于函数嵌套,即在一个函数内部定义另一个函数。
  2. 记忆外部变量:闭包可以记住并访问外部函数的变量,即使外部函数已经执行完毕。
  3. 延长作用域链:闭包将外部函数的作用域链延长到内部函数中,使得内部函数可以访问外部函数的变量。
  4. 返回函数:闭包通常以函数的形式返回,使得外部函数的变量仍然可以被内部函数引用和使用。

自执行函数

let say = (function(){
  let val = 'hello world';
  function say(){
    console.log(val);
  }
  return say;
})()

节流防抖


function throttle(func, delay) {
  let timer = null;
  return function () {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, arguments);
        timer = null;
      }, delay);
    }
  };
}


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

函数柯里化

  • 函数柯里化(Currying)是一种将多个参数的函数转换为一系列接受单个参数的函数的过程。举个简单的例子,我们有一个原始函数add(a, b, c),我们可以将它柯里化为addCurried(a)(b)(c)的形式。

function add(a, b, c) {
  return a + b + c;
}
console.log(add(1, 2, 3)); 

function addCurried1(a) {
  return function (b) {
    return function (c) {
      return a + b + c;
    };
  };
}

const addCurried2 = (a) => (b) => (c) => a + b + c;
console.log(addCurried1(1)(2)(3)); 
console.log(addCurried2(1)(2)(3)); 

链式调用

  • 利用闭包原理封装一个简单的计算器
function calculator() {
  let result = 0;

  function add(num) {
    result += num;
    return this;
  }

  function subtract(num) {
    result -= num;
    return this;
  }

  function multiply(num) {
    result *= num;
    return this;
  }

  function divide(num) {
    result /= num;
    return this;
  }

  function getResult() {
    return result;
  }

  function clear() {
    result = 0;
    return this;
  }

  return {
    add,
    subtract,
    multiply,
    divide,
    getResult,
    clear,
  };
}
const calc = calculator();
const result = calc.add(5).subtract(2).divide(3).multiply(6).getResult();
console.log(result); 

迭代器

function createIterator(arr) {
  let index = 0;

  return {
    next: function() {
      if (index < arr.length) {
        return {
          value: arr[index++],
          done: false
        };
      } else {
        return {
          done: true
        };
      }
    }
  };
}

const myIterator = createIterator([1, 2, 3]);

console.log(myIterator.next()); 
console.log(myIterator.next()); 
console.log(myIterator.next()); 
console.log(myIterator.next()); 

发布-订阅模式

function createPubSub() {
  
  const subscribers = {};

  
  function subscribe(event, callback) {
    
    if (!subscribers[event]) {
      subscribers[event] = [];
    }
    
    subscribers[event].push(callback);
  }

  
  function publish(event, data) {
    
    if (!subscribers[event]) {
      return;
    }
    
    subscribers[event].forEach((callback) => {
      callback(data);
    });
  }

  
  return {
    subscribe,
    publish,
  };
}


const pubSub = createPubSub();


pubSub.subscribe("event1", (data) => {
  console.log("订阅者1收到事件1的数据:", data);
});

pubSub.subscribe("event2", (data) => {
  console.log("订阅者2收到事件2的数据:", data);
});


pubSub.publish("event1", "Hello");


pubSub.publish("event2", "World");


闭包中的内存泄漏指的是在闭包函数中,由于对外部变量的引用而导致这些变量无法被垃圾回收机制释放的情况。当一个函数内部定义了一个闭包,并且这个闭包引用了外部变量时,如果这个闭包被其他地方持有,就会导致外部变量无法被正常释放,从而造成内存泄漏。
解决闭包中的内存泄漏问题通常需要注意解除外部变量和闭包函数的引用,以及解绑函数本身的引用,使得闭包中引用的外部变量和闭包函数能够被垃圾回收机制释放。

  • 以下是使用闭包时解决内存泄漏的示例
function createClosure() {
  let value = 'Hello';

  
  var closure = function() {
    console.log(value);
  };

  
  var releaseClosure = function() {
    value = null; 
    closure = null; 
    releaseClosure = null; 
  };

  
  return {
    closure,
    releaseClosure
  };
}


var closureObj = createClosure();


closureObj.closure(); 


closureObj.releaseClosure();


closureObj.closure(); 

如有错误欢迎在评论区指正,一起讨论!(๑•̀ㅂ•́)و✧

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
javascript2025-09-18
JavaScript 的多线程能力:Worker
如果你写过一些计算量稍大的 JavaScript 代码,比如图像处理、大量数据排序或者复杂的算法,你几乎肯定遇到过浏览器“卡死”的现象。点击页面没反应,动画也停了,就像整个世界都静止了。 这就是主线程被阻塞的典型后果。因为主线程既要负责执行 JavaScript,又要负责渲染页面、响应用户操作,一旦它被繁重的计算任务占满,就无暇顾及其他,用户体验便直线下降。 这个问题的根源,正是“主线程是单线程的”。那么,如何解决呢? 答案很简单:把这...
学习面试
javascript2025-09-10
手把手教你实现一个EventEmitter
你是不是也遇到过这种情况:代码里各种事件监听和触发,回调函数套了一层又一层,最后自己都理不清哪个事件先触发、哪个后触发了? 别担心,今天我就带你从零开始实现一个自己的事件触发器(EventEmitter),让你彻底掌握事件驱动的精髓,代码清晰度直接提升200%! 为什么要懂EventEmitter? 先说说为什么我们要关心这个。Node.js的核心就是事件驱动,比如文件读写、网络请求,都是通过事件来处理的。不会EventEmitter,...
学习面试
javascript2025-08-09
Web Worker 多线程魔法
你是否曾遇到过网页因为复杂计算而卡顿的情况?今天,让我们一起探索Web Worker的神奇世界,看看如何用多线程技术解决性能瓶颈,并实现流畅的图片压缩功能! 为什么需要Web Worker? 想象一下,你在一个繁忙的餐厅里,只有一位服务员(主线程)负责点餐、上菜、结账所有工作。当客人突然暴增时,这位服务员就会手忙脚乱,其他客人只能干等着… 这就是JavaScript的现状—— 单线程 !所有任务都在一个线程中排队执行,一旦遇到复杂计算(...
学习面试
javascript2025-08-07
🌐AST抽象语法树
AST 是前端开发中一个非常重要的概念,它就像是代码的“编译器”,能够帮助我们理解和操作代码。本文将深入探讨 AST 的原理和应用,带你从零开始了解 AST。 一、什么是 AST? AST(抽象语法树)是一种树形结构,用于表示程序代码的语法结构。它将代码分解为一系列的节点,每个节点代表代码中的一个元素,如变量、函数、表达式等。AST 是编译器和解释器的基础,也是代码分析和转换的关键工具。 1\. AST 的结构 AST 的结构通常包括以...
学习面试
javascript2025-08-05
打字机效果是如何实现的
分析效果 使用 AI 对话的时候,文字是一个字一个字蹦出来的?它不是等所有内容生成完毕后一次性展示,而是以一种流畅、实时的“打字机”效果呈现,这种体验的背后,隐藏着一种高效的Web技术。它就是 服务器推送事件(Server Sent Events,简称 SSE) 什么是 SSE 从根本上说,SSE 是一种允许服务器 单向 、 持续 地向客户端(浏览器)推送数据的Web技术。 可以把它想象成一个 电台广播 : 服务器 是广播电台,持续不断...
学习面试
javascript2025-08-04
Promise 从原理到实现
前言 是 异步编程的核心概念之一,它为我们提供了一种更优雅的方式来处理异步操作。 本文将全面剖析 的工作原理,并逐步实现一个符合 规范的 。 正文 一、Promise 基础概念 代表一个异步操作的最终完成(或失败)及其结果值。它有 种状态: 1. pending : ,既不是成功,也不是失败状态 2. fulfilled :意味着操作 完成 3. rejected :意味着操作 状态一旦改变就不能再变,只能从 变为 ,或从 变为 。 二...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录