闭包那些事

21 阅读4分钟

什么是闭包

闭包是指在一个函数内部定义另一个函数,这个内部函数可以访问其外部函数中的变量。即使外部函数已经执行完毕,这个内部函数依然可以访问这些变量。

闭包的优点

  • 延长变量生命周期
    • 闭包可以让变量的生命周期延长,直到闭包不再被引用为止
  • 创建私有数据
    • 通过闭包可以创建私有变量,从而实现数据的封装,避免外部对这些变量的直接访问
  • 避免全局数据污染
    • 通过使用闭包,可以减少全局变量的使用,从而降低全局命名空间被污染的风险

闭包的缺点

  • 可能造成内存泄漏
    • 如果闭包被滥用,特别是在大量创建闭包时,没有正确管理它们的生命周期,可能会导致内存泄漏。这是因为闭包会保留对外部函数作用域的引用,使得垃圾回收机制无法回收这些作用域中的变量。
  • 调试困难
    • 由于闭包的嵌套结构,调试代码时可能会比较复杂,尤其是在深层嵌套的情况下。

闭包的应用

1、封装私有变量

function createCounter() {
  let count = 0;
  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.decrement()); // 0

2、事件处理

function setupHandlers() {
  const message = 'Event handled!';

  document.getElementById('myButton').addEventListener('click', function() {
    console.log(message); // 闭包可以访问外部函数的变量
  });
}

setupHandlers();

3、模块模式

const myModule = (function() {
  let privateVariable = 'I am private';

  function privateMethod() {
    console.log(privateVariable);
  }

  return {
    publicMethod: function() {
      privateMethod();
    }
  };
})();

myModule.publicMethod(); // 输出:I am private

此外最常见的闭包: 防抖、节流函数,函数柯里化...

防抖函数

防抖函数(Debounce)是一种用于控制函数执行频率的技术,特别适用于用户输入或窗口大小调整等频繁触发的场景。防抖函数在一段时间内不执行目标函数,直到事件触发停止后的一段时间才执行。这样可以减少高频事件触发带来的性能问题

function debounce(func, wait) {
    let timer;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => {
            func.apply(this, args);
        }, wait);
    }
}

// 应用: 输入框自动搜索
// const debouncedSearch = debounce(getSearchData, 2000)
// <input type="text" oninput="debouncedSearch(event)" placeholder="请输入...">

节流函数

节流函数(Throttle)是一种用于控制函数执行频率的技术,确保函数在一定时间间隔内只执行一次。节流适用于需要频繁触发但不需要每次都执行的场景,例如滚动事件、窗口调整大小事件、频繁点击按钮等

function throttle(func, wait) {
  let timer;
  return function(...args) {
    if (!timer) {
      timer = setTimeout(() => {
        timer = null;
        func.apply(this, args);
      }, wait);
    }
  };
}

// 使用节流函数可以减少滚动事件处理的频率
// const throttledScroll = throttle(handleScroll, 200);
// window.addEventListener('scroll', throttledScroll);

函数柯里化

函数柯里化(Currying)是一种将接受多个参数的函数转换为一系列接受单一参数的函数的技术

柯里化示例

假设我们有一个简单的加法函数:

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

我们可以将其柯里化,使其变成一个接受一个参数并返回一个接受下一个参数的函数:

function curryAdd(a) {
  return function(b) {
    return a + b;
  };
}

// 使用柯里化的函数
const addFive = curryAdd(5);
console.log(addFive(3)); // 输出 8

通用柯里化函数:

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...nextArgs) {
        return curried.apply(this, args.concat(nextArgs));
      };
    }
  };
}

// 示例函数
function multiply(a, b, c) {
  return a * b * c;
}

// 柯里化后的函数
const curriedMultiply = curry(multiply);

console.log(curriedMultiply(2)(3)(4)); // 输出 24
console.log(curriedMultiply(2, 3)(4)); // 输出 24
console.log(curriedMultiply(2)(3, 4)); // 输出 24

优点

灵活性:柯里化允许你部分应用一个函数的参数,然后在需要时提供其余的参数。

代码复用:你可以创建一系列基于一个基础函数的新函数,这些新函数可以复用相同的基础逻辑。

函数柯里化应用

1、配置函数

function logger(level, message) {
  console.log(`[${level.toUpperCase()}] ${message}`);
}

const infoLogger = curry(logger)('info');
const errorLogger = curry(logger)('error');

infoLogger('This is an info message'); // [INFO] This is an info message
errorLogger('This is an error message'); // [ERROR] This is an error message

2、延迟执行

function calculateVolume(length, width, height) {
  return length * width * height;
}

const curriedVolume = curry(calculateVolume);

const length10 = curriedVolume(10); // 需要传递width和height参数
const area = length10(5); // 需要传递height参数
console.log(area(2)); // 输出100

3、表单验证

function validateInput(type, value) {
  if (type === 'email') {
    return /^\S+@\S+\.\S+$/.test(value);
  } else if (type === 'phone') {
    return /^\d{10}$/.test(value);
  }
  return false;
}

const validateEmail = curry(validateInput)('email');
const validatePhone = curry(validateInput)('phone');

console.log(validateEmail('test@example.com')); // true
console.log(validatePhone('1234567890')); // true

4、参数预设

function fetchData(url, method, headers, body) {
  // 使用fetch API进行请求
  return fetch(url, {
    method,
    headers,
    body: JSON.stringify(body),
  }).then(response => response.json());
}

const getJSON = curry(fetchData)('GET')({ 'Content-Type': 'application/json' });

getJSON('https://api.example.com/data', null).then(data => {
  console.log(data);
});

5、事件处理

function handleEvent(eventType, element, callback) {
  element.addEventListener(eventType, callback);
}

const handleClick = curry(handleEvent)('click');

handleClick(document.getElementById('button1'), () => {
  console.log('Button 1 clicked');
});

handleClick(document.getElementById('button2'), () => {
  console.log('Button 2 clicked');
});

柯里化可以帮助你创建更灵活和可复用的代码,特别是在配置函数、延迟执行、参数预设、表单验证、事件处理等场景中。通过部分应用函数参数,你可以创建更简洁、可读性更高的代码。


编程的世界总是充满未知,期待与你在技术的海洋中再相遇
I just hope my code makes more sense than my life.