JS闭包(Closures)了解一下

简介: 闭包对于前端开发者来说,既十分重要,又非常难理解。如果能很好的理解它,那你将能写出很多高逼格的代码,并且成为人生赢家,赢取白富美。这篇文章,我们来一起简单的认识一下,你走向人生巅峰的敲门砖(闭包)。Note: 闭包不是JS所特有的。它是一个计算机概念,应用的场景也很多。


该文章是直接翻译国外一篇文章,关于闭包(Closures)。

都是基于原文处理的,其他的都是直接进行翻译可能有些生硬,所以为了行文方便,就做了一些简单的本地化处理。

同时也新增了自己的理解,如有不对,请在评论区指出

如果想直接根据原文学习,可以忽略此文。

如果你觉得可以,请多点赞,鼓励我写出更精彩的文章🙏。

前言

闭包对于前端开发者来说,既十分重要,又非常难理解。如果能很好的理解它,那你将能写出很多高逼格的代码,并且成为人生赢家,赢取白富美。

这篇文章,我们来一起简单的认识一下,你走向人生巅峰的敲门砖(闭包)。

Note: 闭包不是JS所特有的。它是一个计算机概念,应用的场景也很多。

函数也是变量

首先,我们需要明确一个JS针对函数的定义或者使用方式::first-class function

JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。

由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。

这里引用了阮一峰老师对函数的定义

在JS中我们能对值干点啥呢?

值可以是变量

const name = 'Yazeed';
const age = 25;
const fullPerson = {
  name: name,
  age: age
};
复制代码

值也可以是数组

const items = ['Yazeed', 25, { name: 'Yazeed', age: 25 }];
复制代码

值可以被函数返回

function getPerson() {
  return ['Yazeed', 25, { name: 'Yazeed', age: 25 }];
}
复制代码

通过刚才讲述,通过联想是不是可以想到,一般的值能干的事,其实函数也是可以的。

函数可以是变量

const sayHi = function(name) {
  return `Hi, ${name}!`;
};
复制代码

函数可以是数组

const myFunctions = [
  function sayHi(name) {
    return `Hi, ${name}!`;
  },
  function add(x, y) {
    return x + y;
  }
];
复制代码

函数也可以被其他函数返回

一个函数返回另外一个函数,有一个很大气的名字:高阶函数(higher-order function)(如果是React开发者,是不是感觉到很熟悉,React中有高阶组件)

高阶函数也是实现闭包的基础。

function getGreeter() {
  return function() {
    return 'Hi, 北宸!';
  };
}
复制代码

getGreeter返回了一个函数,为了实现最终的方法调用,需要调用两次。

getGreeter(); // 返回被包裹的函数
getGreeter()(); // Hi, 北宸!
复制代码

我们也可以将返回的函数存入到一个变量中。

const greetBei = getGreeter();
greetBei(); // Hi, 北宸!
greetBei(); // Hi, 北宸!
greetBei(); // Hi, 北宸!
复制代码

初识闭包

我们将北宸这个直接写到被包裹函数的变量剔除,通过给getGreeter利用参数来动态的传入。

// 这样我们就可以通过变量来传入想要显示的字符串!
function getGreeter(name) {
  return function() {
    return `Hi, ${name}!`;
  };
}
复制代码

例如

const greetBei = getGreeter('北宸');
const greetNan = getGreeter('南蓁');
greetBei(); // Hi, 北宸!
greetNan(); // Hi, 南蓁!
复制代码

客官且慢,让我们再看看代码

function getGreeter(name) {
  return function() {
    return `Hi, ${name}!`;
  };
}
复制代码

在不知不觉中使用了闭包

外层函数接收name,但是是在嵌套函数中使用了这个变量。

当一个函数通过return返回了一些变量的时候,该函数的生命周期已经完结了,也就意味着函数中局部变量会被GC等内存清理机制给回收。

但是,凡事都有例外,如果return函数的话,被返回的函数还是继续拥有访问外层函数变量的权限。即便是外层函数已经被执行过了。

闭包的优点

正如刚开始说过,利用闭包我们可以写出很多高逼格的代码,所以我们来看看闭包都有啥优点。

数据私有化

对于一些共用的代码模块来说,数据私有化是非常重要的

如果没有数据私有化,任何使用你定义的函数/库/框架都能够随意修改内部变量,导致不可逆转的bug.

示例讲解(银行不存在数据私有)

我们假设,下面的代码能够控制一个银行账户。而accountBalance将会作为全局变量暴露给外面。

let accountBalance = 0;
const manageBankAccount = function() {
  return {
    deposit: function(amount) {
      accountBalance += amount;
    },
    withdraw: function(amount) {
      // ... safety logic
      accountBalance -= amount;
    }
  };
};
复制代码

既然作为一个银行账户accountBalance会是一个比较私密的数据/属性,但是在外部某些操作人员,可以不计后果的随意修改。

accountBalance = '后生,你的余额,有点少哇!';
复制代码

如果遇到这个处理,是不是会气炸。有木有

JavaC++这些基于class的OOP语言都有private的保留字。通过private定义的类变量,是不能够在外界访问和赋值的。

但是很不幸的是,JS现在还没有支持private(现在TC39关于class中是可以的),但是在该语法没有出现的时候,我们可以利用闭包实现私有属性。

银行存在私有变量

此时我们将accountBalance放到了函数内部。

const manageBankAccount = function(initialBalance) {
  let accountBalance = initialBalance;
  return {
    getBalance: function() {
      return accountBalance;
    },
    deposit: function(amount) {
      accountBalance += amount;
    },
    withdraw: function(amount) {
      if (amount > accountBalance) {
        return 'You cannot draw that much!';
      }
      accountBalance -= amount;
    }
  };
};
复制代码

那接下来,我们对这个账户的操作就会很安全

const accountManager = manageBankAccount(0);
accountManager.deposit(1000);
accountManager.withdraw(500);
accountManager.getBalance();
复制代码

Note:其实在上面的实例中,我们没有直接访问accountBalance,而是通过各种函数来控制它。

即使accountBalance是由manageBankAccount创建的,但是由manageBankAccount返回的三个函数通过闭包都拥有对accountBalance的访问权限。

Currying(柯里化)

如果想对柯里化有更深的了解,可以移步到此处。但是不要忘记回来哈。

当一个函数一次接收很多参数,可以通过柯里化处理为多个单元函数。

const add = function(x, y) {
  return x + y;
};
add(2, 4); // 6
复制代码

我们可以通过闭包对函数进行柯里化处理。

const add = function(x) {
  return function(y) {
    return x + y;
  };
};
复制代码

函数经过柯里化处理之后,返回了一个具有访问x/y变量的函数。

const add10 = add(10);
add10(10); // 20
add10(20); // 30
add10(30); // 40
复制代码

如果你不想将函数所有参数都预载,可以对该函数进行柯里化处理。同样的,实现柯里化也只能通过闭包。

React -Hooks-useEffect

如果你实时关注React的技术动向,在16版本发布了一个技术实现hooks。其中useEffect这个API,就是依赖闭包来实现的。

我们这篇文章只是介绍闭包相关的文章,具体关于React的相关最新技术,现在已经有一个大致的规划,所以这里只是简单的一带而过。

Talk is cheap,show you the code

import React from 'react';
import ReactDOM from 'react-dom';
function App() {
  const username = 'yazeedb';
  React.useEffect(function() {
    fetch(`https://api.github.com/users/${username}`)
      .then(res => res.json())
      .then(user => console.log(user));
  });
  return (
    <div className="App">
      <h1>Open Codesandbox console for {username}'s info</h1>
    </div>
  );
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
复制代码

我们修改username,就会发现,在控制台就会显示最新的数据信息。其中username是定义在匿名函数(function() {//....})之外的,但是可以在匿名函数中进行访问。

Note:这里再继续重申一点,关于React的相关文章,译者会有相应的篇幅进行讲述,这里不做展开说明。

Summary

  1. 函数在JS中作为变量使用(first-class function)
  2. 函数能作为返回值,被其他函数返回
  3. 内部函数拥有访问外部函数的变量的权限,即使外部函数已经被执行过了。
  4. 外部函数的变量也被称为state
  5. 因此,闭包也被称为有状态的函数



相关文章
|
8天前
|
自然语言处理 JavaScript 前端开发
JavaScript中闭包:概念、用途与潜在问题
【4月更文挑战第22天】JavaScript中的闭包是函数及其相关词法环境的组合,允许访问外部作用域,常用于数据封装、回调函数和装饰器。然而,不恰当使用可能导致内存泄漏和性能下降。为避免问题,需及时解除引用,减少不必要的闭包,以及优化闭包使用。理解并慎用闭包是关键。
|
8天前
|
JavaScript
闭包(js的问题)
闭包(js的问题)
13 0
|
8天前
|
JavaScript 前端开发
解释JavaScript闭包的工作原理,并举例说明其在游戏开发中的应用。
JavaScript闭包允许内部函数访问并保持对外部函数变量的引用,即使外部函数执行结束。当函数返回内部函数时,形成闭包,继承父函数作用域链。在游戏开发中,闭包用于创建具有独立状态和行为的角色实例。例如,`createCharacter`函数创建角色并返回包含属性和方法的对象,内部函数如`getHealth`、`setHealth`和`attack`通过闭包访问并操作角色的变量。这种方式确保了每个角色的状态在不同实例间独立,是实现游戏逻辑的强大工具。
14 2
|
8天前
|
存储 缓存 JavaScript
|
7天前
|
JavaScript 前端开发
JavaScript 闭包:让你更深入了解函数和作用域
JavaScript 闭包:让你更深入了解函数和作用域
|
6天前
|
自然语言处理 JavaScript 前端开发
JavaScript闭包基础
JavaScript闭包基础
|
8天前
|
缓存 自然语言处理 JavaScript
JavaScript内存泄漏导致应用性能下降,常见于闭包使用不当
【5月更文挑战第14天】JavaScript内存泄漏导致应用性能下降,常见于闭包使用不当。闭包能记住并访问词法作用域,若函数返回后,其引用的对象未被释放,就会引发泄漏。例如,`createLeakyFunction`创建的闭包保留了对大型对象`someLargeObject`的引用,即使函数执行完毕,对象也无法被垃圾回收。避免泄漏的方法包括及时解除引用、清除事件监听器、使用WeakMap和WeakSet以及定期清理缓存。使用性能分析工具可检测和修复内存泄漏问题。
18 3
|
8天前
|
JavaScript 前端开发
JavaScript闭包允许内部函数访问并保留外部函数的变量,即使外部函数执行结束
【5月更文挑战第13天】JavaScript闭包允许内部函数访问并保留外部函数的变量,即使外部函数执行结束。在游戏开发中,闭包常用于创建独立状态的角色实例。例如,`createCharacter`函数生成角色,内部函数(如`getHealth`、`setHealth`)形成闭包,保存角色的属性(如生命值)。这样,每个角色实例都有自己的变量副本,不互相影响,从而实现角色系统的独立性。
21 0
|
8天前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包机制
闭包是JavaScript中一个重要且常被误解的概念。本文将深入探讨闭包的本质、工作原理以及在实际开发中的应用。通过详细解析闭包的定义、作用域链、内存管理等方面,读者将对闭包有更清晰的理解,并能够运用闭包解决实际开发中的问题。
|
8天前
|
前端开发 JavaScript
闭包在JavaScript中有许多应用场景
【5月更文挑战第7天】闭包在JavaScript中发挥关键作用,如封装私有变量和函数提升安全性,维护变量生命周期,实现高阶函数,模拟块级作用域,支持回调函数以处理异步操作,以及促进模块化编程,增强代码组织和管理。闭包是理解和掌握JavaScript高级特性的重要一环。
30 7