面试题:JS中的闭包定义、使用与注意事项(1)

闭包的定义(Closure

闭包是一个函数,可以访问其外部作用域(即外部函数的变量)。闭包由一个函数和其周围的状态(词法环境)的构成。闭包常用于数据私有化和创建工厂函数等。

举个例子🌰

javascript 复制代码
function outer() {
  let count = 0;
  return function inner() {
    count++;
    return count;
  };
}

const increment = outer();
console.log(increment()); // 输出:1
console.log(increment()); // 输出:2

闭包的用途

  • 数据封装 :闭包可以用来创建私有变量,封装数据,防止外部直接访问
  • 函数工厂:可以创建并返回具有特定行为的函数。
  • 回调函数:在异步编程中,闭包可以用来捕获和使用外部变量。

举例

数据封装和模块化

闭包可以用来创建私有变量和方法,从而实现模块化。这是JavaScript中实现封装的一种常用方式。

javascript 复制代码
const myModule = (function() {
    let privateVariable = 'secret';

    function privateFunction() {
        console.log('这是一个私有函数');
    }

    return {
        publicMethod: function() {
            console.log('私有变量:' + privateVariable);
            privateFunction();
        }
    };
})();

myModule.publicMethod(); // 输出:这是一个私有函数

在这个示例中,privateVariableprivateFunction 是私有变量和方法,只能在自执行函数的作用域内访问。publicMethod 是一个公共方法,可以通过 myModule 对象调用,它可以访问私有变量和方法。

函数工厂

闭包可以用来创建并返回具有特定行为的函数。这些函数可以携带和使用外部作用域中的变量。

示例:创建计数器

javascript 复制代码
function createCounter(initialValue = 0) {
    let count = initialValue;

    return {
        increment: function() {
            count += 1;
            console.log(count);
        },
        decrement: function() {
            count -= 1;
            console.log(count);
        },
        getValue: function() {
            return count;
        }
    };
}

const counter1 = createCounter(5);
counter1.increment(); // 输出:6
counter1.increment(); // 输出:7
counter1.decrement(); // 输出:6

const counter2 = createCounter(10);
counter2.increment(); // 输出:11

事件处理

闭包可以用来在事件处理函数中捕获和使用外部变量。

示例:动态生成按钮并绑定事件

在这个示例中,createButton 函数创建一个按钮并绑定点击事件。每个按钮的点击事件处理函数可以访问外部变量 i

异步编程

闭包可以用来在异步回调中捕获和使用外部变量。

示例:定时器

javascript 复制代码
function setupTimer(message, delay) {
    setTimeout(function() {
        console.log(message);
    }, delay);
}

setupTimer('Hello, world!', 2000); // 2秒后输出:Hello, world!

函数柯里化

闭包可以用来实现函数柯里化。

js 复制代码
function curry(fn, ...args) {
    return function(...newArgs) {
        return fn(...args, ...newArgs);
    };
}

function add(a, b, c) {
    return a + b + c;
}

const add5 = curry(add, 5);
console.log(add5(10, 15)); // 输出:30

const add5And10 = curry(add, 5, 10);
console.log(add5And10(15)); // 输出:30

装饰器模式

闭包可以用来创建装饰器,扩展或修改现有函数的行为。

示例:装饰器模式

js 复制代码
function loggingDecorator(fn) {
    return function(...args) {
        console.log(`函数 ${fn.name}, 参数: ${args.join(', ')}`);
        const result = fn(...args);
        console.log(`结果: ${result}`);
        return result;
    };
}

function add(a, b) {
    return a + b;
}

const loggedAdd = loggingDecorator(add);
console.log(loggedAdd(3, 4)); 

// 输出:函数 add, 参数: 3, 4
// 输出:结果: 7
// 输出:7

在这个示例中,loggingDecorator 函数返回一个新的函数,该函数在调用原始函数 fn 之前和之后添加日志记录。

缓存和 memoization

闭包可以用来实现缓存,避免重复计算。

js 复制代码
function memoize(fn) {
    const cache = new Map();
    return function(...args) {
        const key = JSON.stringify(args);
        // 判断是否存在
        if (cache.has(key)) {
            return cache.get(key);
        }
        const result = fn(...args);
        // 计算的值保存
        cache.set(key, result);
        return result;
    };
}

function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

const memoizedFibonacci = memoize(fibonacci);
console.log(memoizedFibonacci(10)); // 输出:55
console.log(memoizedFibonacci(10)); // 输出:55(从缓存中获取)

防抖和节流

防抖(Debounce)

js 复制代码
function debounce(fn, delay) {
  let timer = null; // 使用闭包存储定时器变量
  return function () {
    if (timer) clearTimeout(timer); // 如果定时器存在,清除上一次的定时器
    timer = setTimeout(() => { // 设置新的定时器
      fn.apply(this, arguments); // 延迟执行函数
    }, delay);
  };
}

节流(Throttle)

js 复制代码
function throttle(fn, delay) {
  let timer = 0;
  return function() {
    if (timer) return;
    timer = setTimeout(() => {
      fn.apply(this, arguments); // 延迟执行函数
      timer = 0; // 执行完毕后重置计时器标志
    }, delay);
  }
}

发布-订阅模式

js 复制代码
function createEventHub() {
  let events = {}; // 使用闭包存储事件对象

  // 订阅事件
  function on(eventName, handler) {
    events[eventName] = events[eventName] || [];
    events[eventName].push(handler);
  }

  // 发布事件
  function emit(eventName, ...args) {
    const handlers = events[eventName];
    if (!handlers) return;
    handlers.forEach(handler => handler(...args));
  }

  return {
    on,
    emit
  };
}

// 使用事件中心
const eventHub = createEventHub();

// 订阅事件
eventHub.on('login', (username) => {
  console.log(`Welcome ${username}!`);
});

// 发布事件
eventHub.emit('login', 'John'); // 输出: "Welcome John!"

迭代器

js 复制代码
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()); // { value: 1, done: false }
console.log(myIterator.next()); // { value: 2, done: false }
console.log(myIterator.next()); // { value: 3, done: false }
console.log(myIterator.next()); // { done: true }

闭包的注意事项

  • 内存泄漏:如果闭包不被正确管理,可能会导致内存泄漏,因为闭包会保持对外部作用域的引用,即使这些变量不再需要。
  • 性能影响:闭包可能会增加内存使用和垃圾回收的负担,因为它们会保持对作用域链的引用。

文章参考:

相关推荐
mldong9 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排10 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby606111 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角12 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!12 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰12 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi14 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen14 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马15 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu15 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss