该文章是直接翻译国外一篇文章,关于闭包(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 = '后生,你的余额,有点少哇!'; 复制代码
如果遇到这个处理,是不是会气炸。有木有
像Java
、C++
这些基于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
- 函数在JS中作为变量使用(first-class function)
- 函数能作为返回值,被其他函数返回
- 内部函数拥有访问外部函数的变量的权限,即使外部函数已经被执行过了。
- 外部函数的变量也被称为state
- 因此,闭包也被称为有状态的函数