Javascript详细闭包知识点指南

188 阅读4分钟

闭包(Closure)是JavaScript中一个重要而强大的概念。它是指在一个函数内部定义的函数,它可以访问外部函数的变量,即使外部函数已经执行完毕。闭包使得函数内的变量在函数执行完后仍然可以被访问,这种机制有助于实现封装、模块化和函数式编程。

开始

  1. 定义: 闭包是指一个函数可以访问其包含函数(外部函数)中定义的变量,即使在外部函数执行完毕后。
  2. 创建闭包: 闭包通常在一个函数内部定义另一个函数,并将内部函数作为返回值。当内部函数被返回时,它仍然可以访问外部函数的变量。
function outerFunction() {
  let outerVariable = 'I am from outer function';
  
  function innerFunction() {
    console.log(outerVariable);
  }
  
  return innerFunction;
}

const closureExample = outerFunction();
closureExample(); // 输出:I am from outer function
  1. 访问外部变量: 内部函数可以访问外部函数的参数和局部变量,甚至可以访问外部函数返回的其他函数。
function outerFunction(x) {
  function innerFunction(y) {
    console.log(x + y);
  }
  return innerFunction;
}

const closureExample = outerFunction(5);
closureExample(3); // 输出:8
  1. 保持状态: 闭包可以用于保持状态,因为它们可以访问外部函数的变量,并且这些变量在外部函数执行后不会被销毁。
function counter() {
  let count = 0;
  return function() {
    count++;
    console.log(count);
  };
}

const increment = counter();
increment(); // 输出:1
increment(); // 输出:2
  1. 实现私有变量: 闭包可以用于实现私有变量,因为内部函数可以访问外部函数的变量,但外部无法直接访问内部函数的变量。
function createPerson(name) {
  let privateName = name;
  return {
    getName: function() {
      return privateName;
    }
  };
}
const person = createPerson('John');
console.log(person.getName()); // 输出:John

涉及的其他相关高级知识点

1. this 关键字与闭包

在 JavaScript 中,this 关键字的值在函数被调用时确定。在闭包中,this 的值可能会引起一些意外行为,因为它取决于函数的调用方式。

示例:

function Counter() {
  this.count = 0;

  setInterval(function() {
    // 在此处的 this 指向全局对象而不是 Counter 实例
    this.count++;
    console.log(this.count);
  }, 1000);
}

const counter = new Counter();
// 输出 NaN(因为全局对象没有 count 属性)

解决方法:

  • 使用额外的变量 self 来捕获正确的 this 值。
  • 使用箭头函数,因为箭头函数没有自己的 this,它会继承自外部作用域。
function Counter() {
  this.count = 0;

  // 使用额外的变量 self 解决
  var self = this;
  setInterval(function() {
    self.count++;
    console.log(self.count);
  }, 1000);
}

// 或者使用箭头函数
function Counter() {
  this.count = 0;

  setInterval(() => {
    this.count++;
    console.log(this.count);
  }, 1000);
}

2. 作用域链

作用域链是指在 JavaScript 中,每个函数都有一个与之相关联的作用域链,用于查找变量。闭包通过作用域链实现对外部变量的引用。

示例:

function outer() {
  let outerVar = 'I am outer';

  function inner() {
    console.log(outerVar); // 内部函数可以访问外部函数的变量
  }

  inner();
}

outer(); // 输出:I am outer

3. 循环中的闭包

在循环中使用闭包时,需要注意循环变量在闭包中的值可能不是你期望的值,因为闭包捕获的是变量的引用,而不是值。 示例:

for (var i = 1; i <= 5; i++) {
  setTimeout(function() {
    console.log(i); // 输出 6(循环结束后的值)
  }, i * 1000);
}
  • 解决方法: 使用立即执行函数表达式(IIFE)来捕获正确的循环变量值。
for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j);
    }, j * 1000);
  })(i);
}

4. 内存管理与解除引用 (内存泄漏问题)

闭包可能导致内存泄漏,因为它们可以长时间保留对外部函数作用域中变量的引用,阻止这些变量被垃圾回收。

示例:

function createClosure() {
  let data = new Array(1000000).fill('Some data');

  return function() {
    console.log(data.length);
  };
}

const leakyClosure = createClosure();
leakyClosure(); // 在这里使用闭包

// 在不需要时解除对闭包的引用
// leakyClosure = null;

如果不解除对 leakyClosure 的引用,data 数组将一直存在于内存中,即使它不再被需要。解除引用后,垃圾回收器将能够回收这些不再使用的资源

5. 使用闭包实现模块模式

闭包可以用于创建私有变量和方法,从而实现模块化的代码结构。这种方式可以隐藏实现细节,防止全局命名空间的污染。

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

  function privateFunction() {
    console.log('This is private');
  }

  return {
    publicVariable: 'I am public',
    publicFunction: function() {
      console.log('This is public');
      privateFunction();
    }
  };
})();

console.log(myModule.publicVariable); // I am public
myModule.publicFunction(); 
// This is public  
// This is private

完~