搞懂Javascript闭包概念(二)
这是闭包系列的第二章,第一章只是囫囵吞枣般的记笔记,根本没多深入理解,最近在看了MDN上关于闭包的文章以及B站几个闭包视频后有了些许理解,于是用小本本记下来。
首先我们先来理解几个概念
作用域
作用域就是变量与函数的可访问范围。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。
1 |
|
作用域链
上面的代码形象的解释了全局作用域和局部作用域这两个概念。接下来看看下面这个例子。
1 |
|
那么,有没有什么办法可以实现每次调用fn,输出值就自增1呢,我们把代码修改一下
1 |
|
为什么每次调用都会自增而不是一直输出1呢,我们来分析一下上面的代码:
- 执行fn函数而fn函数的返回值还是一个函数就相当于将sum函数赋值给一个全局变量test,全局变量我们上文提到过,只有当网页关闭时才会卸载,这就导致sum函数始终在内存中。
- 那么此时的test是什么呢,我们来打印一下看看,输出如下
1 |
|
- 每次执行test()即相当于执行sum函数,++a则相当于a=a+1,代码可简化下
1 |
|
- 因为sum一直在内存中,而fn是sum的父函数,sum的存在依赖于fn,因此fn也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收,所以整个fn函数内部数据都会被保留,a自增的结果也就一直存在
仔细回想一下刚刚我们查找变量a的过程发生了什么?
- 先从当前上下文的变量对象中查找
- 如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找
- 一直找到全局上下文的变量对象,也就是全局对象(ps:作用域链的顶端就是全局对象)
这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
课外小拓展:fn函数中不return函数,直接return变量a行不行
答案是:不行,上代码,每次调用完fn,内部的变量a就会被释放,所以每次调用都是返回1
1 |
|
主角登场-闭包
面试题问什么是闭包
现在我们来尝试回答一下:
- 内部函数可以访问外部函数的变量称之为闭包
- 闭包就是能够读取其他函数内部变量的函数,在本质上是函数内部和函数外部链接的桥梁
- 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行
- 函数和对其周围状态(词法环境)的引用捆绑在一起构成闭包(closure)(ps:回答最官方最靠谱的一个)
利用闭包搞点事
给定一个数组,里面有若干数字,要求返回数字大于3且小于9的
1 |
|
一顿操作轻轻松松实现了需求,如果此时万恶的pm改了一下需求,要求返回4-8的呢?既然我们目前在学闭包,那就利用闭包的特性来实现一下
1 |
|
代码是不是更加健壮了呢,我们来分析一下上面的代码
- 首先运行between函数而between函数返回一个函数,这个函数作为filter的回调函数来使用
- filter循环下,不断执行between中的子函数,因为闭包的特性,子函数可以访问到父函数between函数的参数,然后就不断开辟空间执行return value >= a && value <= b这段代码
闭包中的历史遗留问题
1 |
|
根据闭包的特性return this.user;
此处的this应该是get函数中的this即hd对象,可为什么输出是undefined
this永远指向调用他的的对象,我们将hd.get()
赋值给全局的对象a,所以此处this指向全局变量Window,而Window下是没有user的对象的,所以输出undefined
解决方法
1 |
|
闭包的优点
延长外部函数局部变量生命周期
闭包的一些缺陷
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量手动删除。
1 |
|