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

闭包的注意事项

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

文章参考:

相关推荐
Elcker7 分钟前
Tauri教程-基础篇-第二节 Tauri的核心概念上篇
javascript·rust
星星不闪包退换19 分钟前
css面试常考布局(圣杯布局、双飞翼布局、三栏布局、两栏布局、三角形)
前端·css
书边事.30 分钟前
Taro+Vue实现图片裁剪组件
javascript·vue.js·taro
疯狂的沙粒42 分钟前
HTML和CSS相关的问题,如何避免 CSS 样式冲突?
前端·css·html
家电修理师1 小时前
HBuilderX打包ios保姆式教程
前端·ios
草木红1 小时前
六、Angular 发送请求/ HttpClient 模块
服务器·前端·javascript·angular.js
baozhengw1 小时前
SpringBoot项目实战(39)--Beetl网页HTML文件中静态图片及CSS、JS文件的引用和展示
javascript·css·html·beetl
kkkkatoq1 小时前
EasyExcel的应用
java·前端·servlet
阿雄不会写代码1 小时前
使用java springboot 使用 Redis 作为限流工具
前端·bootstrap·html
answerball1 小时前
Vue3 开发指南:从零到前端大神的轻松之旅 🚀
前端·vue.js