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

闭包的注意事项

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

文章参考:

相关推荐
MickeyCV36 分钟前
Nginx学习笔记:常用命令&端口占用报错解决&Nginx核心配置文件解读
前端·nginx
祈澈菇凉1 小时前
webpack和grunt以及gulp有什么不同?
前端·webpack·gulp
十步杀一人_千里不留行1 小时前
React Native 下拉选择组件首次点击失效问题的深入分析与解决
javascript·react native·react.js
zy0101011 小时前
HTML列表,表格和表单
前端·html
初辰ge1 小时前
【p-camera-h5】 一款开箱即用的H5相机插件,支持拍照、录像、动态水印与样式高度定制化。
前端·相机
HugeYLH1 小时前
解决npm问题:错误的代理设置
前端·npm·node.js
六个点2 小时前
DNS与获取页面白屏时间
前端·面试·dns
道不尽世间的沧桑2 小时前
第9篇:插槽(Slots)的使用
前端·javascript·vue.js
bin91532 小时前
DeepSeek 助力 Vue 开发:打造丝滑的滑块(Slider)
前端·javascript·vue.js·前端框架·ecmascript·deepseek
uhakadotcom3 小时前
最新发布的Tailwind CSS v4.0提供了什么新能力?
前端