理解 JS Closure

0. 前言

JS Closure闭包,平时我们可能感觉用到的不多,但是在面试中是非常常见的问题之一,一般用来检验面试者对于JS基础的掌握程度,很多同学可能看过但是印象不深,也有可能感觉自己弄懂了,但是问的一些问题或者题目又答不上来。这篇文章,我们就好好讲一讲JS Closure,把闭包讲通。

1. 定义

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

解读一下官方定义,可以看出闭包是一种代码结构或者说是能力(也可以说是特定的代码结构产生的能力),这种结构或者能力可以让开发者访问一些常理来说不能访问的东西。

首先先有一个概念,下面我们再来展开的讲一讲。

2. 词法环境和作用域

官方定义中涉及到一个名词:Lexical Envrionmnet 词法环境,展开说的话要从JS编译解析的原理开始讲起,内容太多了(开个坑以后有机会可以展开讲讲)。

简单来讲,JS编辑过程中有一个过程会一行一行的去处理你的代码,处理的时候会将标识符(变量和函数的名字)和引用的映射记录在一个小本上,下次用到的时候就从这个小本子的记录里面去找。有一点特殊的地方这个记录的结构是一层一层的嵌套关系,内层记录有外层的引用。

每个本子上能访问到的记录,这个范围就是作用域

3. 深入探索

来看下面的例子:

function outter() {

  let name = "test";
  function inner() {

    console.log(name);

  }

  inner();
}

outter();
// 输出:test

为什么能够正常输出test?就是因为闭包闭包是使用者可以通过内部方法来访问外部变量。内部inner方法没有name这个变量,但是因为JS的词法环境是会有外层的引用,所以会读取到outter中的name

image.png

打印出内部方法可以看到,内部有属性保存着外部的作用域的引用。

我们先暂停一下上一个例子的思考。先大概的讲一下运行的原理。JS在运行过程中执行函数的时候,会有一个调用栈。当方法执行时,就会放到栈中执行,执行完毕就出栈。(为什么是栈?栈这种数据结构先进后出,保证嵌套的执行顺序,以后我们机会可以讲一下DFS和BFS,会对栈和队列有更清晰的理解)

于是我们再看上面的例子,自然有这个画面:outter推到执行栈中执行,附带了他的一些变量(作用域中的引用,执行的时候要用),执行内部,有inner调用,于是将inner推到执行栈中,此时outter还没释放,inner执行的时候,读name,能够找到是应为outter在执行栈中,所以变量跟着outter一起的?也在执行栈中?

image.png

真的是这样吗?我们来看下下面的例子:

function outter() {

  let name = 'test';
  function inner() {

    console.log(name);

  }

  return inner;
}


let f = outter();
f();
// 输入:test

按照我们刚才的理解,推演一遍:outter执行,进栈,执行完成后返回inner方法赋值给foutter出栈释放变量。f执行进栈执行,打印name,但是这个时候outter已经不在执行栈中了,还是能找到name,哦吼?因为数据是存在堆(Heap)中,执行完成也不会释放。

image.png

我们再回到最开始说的,闭包是一种能力,访问常理不能访问的东西。按常理name跟随outter释放确实不应该能访问到。但是因为闭包,使得我们可以打印出name

4. 应用场景

由于这种即使外层方法结束了,但是通过调用内部方法仍然可以访问到外层作用域的变量。我们可以实现一些操作。下面的示例出资官方文档:

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

为什么会不一样?因为add5add10保存的是不同的词法环境,所以会返回不同的结果。于是我们很方便的就可以实现一个工厂模式

当然还有很多其他应用,以后可以开个专题来展开讲讲。(又挖一坑)

5. 隐含问题

由于使用闭包之后,在外层调用结束后,仍会保存属性,不会释放,所以可能会导致内存占用过高,内存泄漏的问题。我们在使用时要考虑是否真的需要用到闭包,谨防滥用。

6. 总结

以上我们介绍了JS Closure,一般来说,闭包出现总是有函数的嵌套和内层函数引用外层变量。不知道我有没有讲清楚,也欢迎大家在评论区讨论。

参考资料

developer.mozilla.org/zh-CN/docs/…

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYF7VZjv' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片