面试题: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 }

闭包的注意事项

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

文章参考:

相关推荐
腾讯TNTWeb前端团队2 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰5 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪5 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪6 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy6 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom7 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom7 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom7 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom7 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom7 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试