闭包的原理和实际应用的优缺点及特点

一、闭包的原理
闭包是指函数能够访问其定义时所在的词法作用域,即使该函数在其定义的作用域之外被调用。
核心原理:
1.函数在创建时会捕获其周围的词法环境(变量、函数等)
2.当函数在外部作用域被调用时,依然能访问这些捕获的变量

function outer() {
  // 外部函数的局部变量
  const message = "Hello, Closure!";
  
  // 内部函数(闭包)
  function inner() {
    // 访问外部函数的变量
    console.log(message);
  }
  
  // 返回内部函数(在外部作用域执行)
  return inner;
}

// 接收闭包函数
const closureFunc = outer();
// 即使 outer 已执行完毕,仍能访问 message
closureFunc(); // 输出: "Hello, Closure!"
// 当 outer() 执行结束后,其作用域并未被垃圾回收机制清除,因为 inner() 函数仍在引用其中的 message 变量,形成了闭包。

二、闭包的特点
变量私有化:外部无法直接访问闭包内部的变量,只能通过闭包提供的接口操作
状态保持:闭包可以保留其创建时的环境状态,多次调用可共享同一状态
作用域链延长:使函数能访问外层作用域的变量,即使外层函数已执行完毕
三、实际应用场景
1.模块化与私有变量
模拟类的私有属性和方法,避免全局变量污染。

function createCounter() {
  let count = 0; // 私有变量
  return {
    increment: () => count++,
    decrement: () => count--,
    getCount: () => count
  };
}

const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 1(无法直接访问 count)

2.函数柯里化
将多参数函数转换为一系列单参数函数。

function multiply(a) {
  return function(b) {
    return a * b;
  };
}

const multiplyBy2 = multiply(2);
console.log(multiplyBy2(5)); // 10

3.延迟执行
如定时器、事件处理等场景,保存当前状态。

function setupTimer(message, delay) {
  setTimeout(function() {
    console.log(message); // 闭包捕获 message 变量
  }, delay);
}

setupTimer("延迟执行", 1000);

4.React Hooks 实现基础
React 中的 useState、useEffect 等 Hooks 本质上利用闭包保存组件状态。
四、优点
数据封装:实现私有变量,控制变量访问权限,提高代码安全性
状态持久化:在函数多次调用之间保持状态,如计数器、缓存等场景
模块化:将相关逻辑和数据组织在一起,避免全局变量污染
灵活的函数创建:可动态生成函数,适应不同场景需求
五、缺点
内存消耗:闭包会保留外层作用域,导致变量不会被垃圾回收,可能造成内存泄漏
性能影响:过度使用闭包会增加内存占用,影响程序性能
调试困难:闭包的作用域链复杂,变量来源不直观,增加调试难度
可能导致意外行为:如果不注意,闭包捕获的变量可能会有预期之外的变化

// 常见的闭包陷阱
function createFunctions() {
  const result = [];
  for (var i = 0; i < 3; i++) {
    result.push(function() { return i; });
  }
  return result;
}

const funcs = createFunctions();
console.log(funcs[0]()); // 3(而非预期的 0)
// 解决:使用 let 或 IIFE 创建独立作用域

*闭包的this指向
在闭包中,this 的指向是一个容易混淆的点,它并不像普通函数那样简单地指向调用者,而是由闭包的调用方式和定义环境共同决定。
一、闭包中 this 的默认指向
默认情况下,闭包中的 this 指向全局对象(浏览器中是 window,Node.js 中是 global),而非外层函数的 this。
这是因为闭包本身是一个独立的函数,它没有继承外层函数的 this 绑定,其 this 指向由调用时的上下文决定。

const obj = {
  name: "obj",
  outer: function() {
    // 外层函数的this指向obj
    console.log("outer this:", this); // { name: "obj", outer: [Function] }
    
    // 闭包
    function inner() {
      // 闭包的this默认指向全局对象
      console.log("inner this:", this); // window(浏览器环境)
      console.log("inner this.name:", this.name); // window.name(可能为空)
    }
    
    return inner;
  }
};

const closure = obj.outer();
closure(); // 直接调用,this指向全局

二、如何让闭包访问外层函数的 this?
如果需要在闭包中使用外层函数的 this,可以通过以下两种方式:

  1. 用变量保存外层 this(最常用)
    在外层函数中用变量(如 that、self)缓存 this,闭包通过访问该变量间接使用外层 this。
const obj = {
  name: "obj",
  outer: function() {
    const that = this; // 保存外层this
    function inner() {
      console.log(that.name); // 访问外层this的name → "obj"
    }
    return inner;
  }
};

const closure = obj.outer();
closure(); // 输出: "obj"
  1. 使用箭头函数(ES6+)
    箭头函数没有自己的 this,它会继承外层作用域的 this,因此适合作为闭包直接使用外层 this。
const obj = {
  name: "obj",
  outer: function() {
    // 箭头函数作为闭包,继承外层this
    const inner = () => {
      console.log(this.name); // this指向outer的this(即obj)
    };
    return inner;
  }
};

const closure = obj.outer();
closure(); // 输出: "obj"

三、特殊场景:闭包被绑定 this 时
如果通过 call、apply、bind 等方法调用闭包,this 会被强制修改为指定对象。

const obj = {
  name: "obj",
  outer: function() {
    return function inner() {
      console.log(this.name);
    };
  }
};

const closure = obj.outer();
const otherObj = { name: "other" };

closure.call(otherObj); // 强制this指向otherObj → 输出: "other"

总结:闭包中 this 的指向规则
默认情况:指向全局对象(非严格模式)或 undefined(严格模式)。
箭头函数闭包:继承外层作用域的 this,与外层函数 this 一致。
显式绑定:通过 call/apply/bind 调用时,this 指向绑定的对象。
常见解决方案:用变量缓存外层 this 或使用箭头函数,确保闭包能访问预期的 this。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容