闭包的概念及使用场景介绍

时间:2024-11-09 21:45:27

概念:在JavaScript中,闭包(Closure)是指一个函数有权利访问定义在它外部作用域的任何变量。

function outerFn(outerVal) {
    return function innerFn(innerVal) {
        console.log('outerVal', outerVal)
        console.log('innerVal', innerVal)
    }
}

const newFunction = outerFn('outside')
newFunction('inside')
// outerVal outside
// innerVal inside

内部函数有权利访问外部作用域的变量outerVal,而外部函数的变量已经被内部函数绑定。

使用场景

  1. 数据封装和私有化
    创建私有变量:闭包可以帮助我们封装私有变量,防止外部直接访问和修改。
    模块模式:使用闭包来实现模块化代码,每个模块都有自己的作用域,不会污染全局命名空间。
    function createCounter() {
        let val = 0;
        return {
            increment() {
                val++;
            },
            getVal() {
                return val;
            }
        }
    }
    let counter = createCounter()
    counter.increment()
    console.log(counter.getVal())// 1
    counter.increment()
    console.log(counter.getVal())// 2
    
  2. 柯里化(Currying)
    通过闭包,可以创建一个函数,这个函数被调用时不会立即执行,而是返回一个新的函数,新函数可以访问到原函数的参数。

function curry(func) {
    const len = func.length;

    // 返回一个新函数,这个新函数会处理参数的收集
    return function curried(...args) {
        // 如果传入的参数数量足够,直接调用原始函数
        if(args.length >= len) {
            return func.apply(this, args)
        } else {
            // 如果参数数量不足,返回一个新的函数,这个函数继续收集参数
            return function(...args2) {
                return curried.apply(this, args.concat(args2))
            }
        }
    }
}

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

const curriedAdd = curry(add);
console.log(curriedAdd(1,2,3))// 6
console.log(curriedAdd(1)(2,3))// 6
console.log(curriedAdd(1,2)(3))// 6

  1. 高阶函数
    高阶函数可以接收函数作为参数或将函数作为返回值,闭包使得这些函数可以访问并操作外部作用域的变量。
function formatNumberToLocaleString() {
    return function(number) {
      // 将数字转换为字符串,并使用正则表达式实现千分位分割
      return number.toLocaleString();
    };
  }
  
  const formatThousandSeparator = formatNumberToLocaleString();
  
  // 使用闭包实例格式化数字
  console.log(formatThousandSeparator(1234567.89)); // 输出 "1,234,567.89"

4.实现工厂函数
工厂函数可以创建并返回一个具有特定功能的对象,闭包可以用来保持每个对象的私有状态。

function carFactory(brand) {
    // 私有变量
    let engineState = 'off';

    return {
        startEngine: function() {
            engineState = 'on';
            console.log(`${brand} engine is now ${engineState}`)
        },
        stopEngine: function() {
            engineState = 'off'
            console.log(`${brand} engine is now ${engineState}`)
        },
    }
}

const ford = carFactory('ford')
ford.startEngine()// ford engine is now on
ford.stopEngine()// ford engine is now off

5.事件处理和异步操作
在事件处理或异步操作中,闭包可以用来保持对特定作用域的引用,即使是在回调函数执行时作用域已经消失。


// 假设有一个元素列表
const items = document.querySelectorAll('.item');

// 创建一个闭包来处理点击事件和异步操作
function createEventHandler(itemIndex) {
  return function(event) {
    // 从服务器获取数据
    fetchDataFromServer(itemIndex).then(data => {
      console.log(`Item ${itemIndex} data:`, data);
    }).catch(error => {
      console.error(`Error fetching data for item ${itemIndex}:`, error);
    });
  };
}

// 为每个项目添加事件监听器
items.forEach((item, index) => {
  // 使用闭包来保持 itemIndex 的状态
  item.addEventListener('click', createEventHandler(index));
});

// 模拟从服务器获取数据的异步函数
function fetchDataFromServer(itemIndex) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 模拟成功或失败的情况
      if (Math.random() > 0.5) {
        resolve(`Data for item ${itemIndex}`);
      } else {
        reject(new Error(`Failed to fetch data for item ${itemIndex}`));
      }
    }, 1000);
  });
}

  1. 迭代器和生成器
    生成器函数利用闭包来保持其在每次迭代时的状态。
function createArrayIterator(array) {
    let index = 0;
    
    return {
      next: function() {
        if (index < array.length) {
          return { value: array[index++], done: false };
        } else {
          return { done: true };
        }
      }
    };
  }
  
  const myArray = [1, 2, 3];
  const iterator = createArrayIterator(myArray);
  
  console.log(iterator.next()); // { value: 1, done: false }
  console.log(iterator.next()); // { value: 2, done: false }
  console.log(iterator.next()); // { value: 3, done: false }
  console.log(iterator.next()); // { done: true }
  

闭包是JavaScript最好的语法之一。