必威-必威-欢迎您

必威,必威官网企业自成立以来,以策略先行,经营致胜,管理为本的商,业推广理念,一步一个脚印发展成为同类企业中经营范围最广,在行业内颇具影响力的企业。

你不知道的javascript必威,比如闭包的定义

2019-10-05 05:36 来源:未知

闭包的艺术性

我相信这个世界上最美的事物往往就存在我们身边,通常它并不是那么神秘,那么不可见,只是我们缺少了一双发现美的眼睛。

生活中,我们抽出一段时间放慢脚步,细细品味我们所过的每一分每一秒,会收获到生活给我们的另一层乐趣。

闭包也一样,它不是很神秘,反而是在我们的程序中随处可见,当我们静下心来,品味闭包的味道,发现它散发出一种艺术的美,朴实、精巧又不失优雅。

必威 1

细想,在我们作用域气泡模型中,作用域链让我们的内部bar气泡能够”看到”外面的世界,而闭包则让我们的外部作用域能够”关注到”内部的情况成为可能。可见,只要我们愿意,内心世界和外面世界是可以相通的

2. 变量作用域

先来说几个概念:

全局变量:在所有作用域都可访问的变量,在函数外定义的变量就是全局变量

局部变量:在函数中使用关键字声明的变量,它的作用域只在声明该变量的函数内,在函数外面是访问不到该变量的。

词法作用域:词法作用域也叫静态作用域,也就是说函数的作用域在函数定义的时候就决定了,而不是调用的时候决定。JavaScript采用静态作用域,变量的作用域完全由写代码期间函数声明的位置来定义的。

话不多说,上代码:

代码1:

var func = function(){

     var a = 'closure'

       console.log(a);         // closure

}

func();

console.log(a); // Uncaught ReferenceError: a is not defined

局部变量a只能在函数内部使用,函数调用结束时,该变量就会被垃圾回收机制回收而销毁

代码2:

var value = 1;

function foo() {

    console.log(value);

}

​function bar() {

    var value = 2;

    foo();

}

bar(); 

foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。

 

  • 作用域
  • 词法作用域
  • 函数作用域
  • 块作用域
  • 闭包

闭包的应用

  1. 模块一个模块应该具有私有属性、私有方法和公有属性、公有方法。而闭包能很好的将模块的公有属性、方法暴露出来。
var myModule = (function (window, undefined) { let name = "echo";
function getName() { return name; } return { name, getName }
})(window); console.log( myModule.name ); // echo console.log(
myModule.getName() ); // echo

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6bea87da9603634463-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da9603634463-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da9603634463-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da9603634463-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da9603634463-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da9603634463-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da9603634463-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da9603634463-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da9603634463-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da9603634463-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da9603634463-11">
11
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da9603634463-12">
12
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da9603634463-13">
13
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da9603634463-14">
14
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da9603634463-15">
15
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6bea87da9603634463-1" class="crayon-line">
var myModule = (function (window, undefined) {
</div>
<div id="crayon-5b8f6bea87da9603634463-2" class="crayon-line crayon-striped-line">
 let name = &quot;echo&quot;;
</div>
<div id="crayon-5b8f6bea87da9603634463-3" class="crayon-line">

</div>
<div id="crayon-5b8f6bea87da9603634463-4" class="crayon-line crayon-striped-line">
 function getName() {
</div>
<div id="crayon-5b8f6bea87da9603634463-5" class="crayon-line">
 return name;
</div>
<div id="crayon-5b8f6bea87da9603634463-6" class="crayon-line crayon-striped-line">
 }
</div>
<div id="crayon-5b8f6bea87da9603634463-7" class="crayon-line">

</div>
<div id="crayon-5b8f6bea87da9603634463-8" class="crayon-line crayon-striped-line">
 return {
</div>
<div id="crayon-5b8f6bea87da9603634463-9" class="crayon-line">
 name,
</div>
<div id="crayon-5b8f6bea87da9603634463-10" class="crayon-line crayon-striped-line">
 getName
</div>
<div id="crayon-5b8f6bea87da9603634463-11" class="crayon-line">
 }
</div>
<div id="crayon-5b8f6bea87da9603634463-12" class="crayon-line crayon-striped-line">
})(window);
</div>
<div id="crayon-5b8f6bea87da9603634463-13" class="crayon-line">
 
</div>
<div id="crayon-5b8f6bea87da9603634463-14" class="crayon-line crayon-striped-line">
console.log( myModule.name ); // echo
</div>
<div id="crayon-5b8f6bea87da9603634463-15" class="crayon-line">
console.log( myModule.getName() ); // echo
</div>
</div></td>
</tr>
</tbody>
</table>

“return”关键字将对象引用导出赋值给myModule,从而应用到闭包。
  1. 延时器(setTimeout)、计数器(setInterval)这里简单写一个常见的关于闭包的面试题。
for( var i = 0; i &lt; 5; i++ ) { setTimeout(() =&gt; { console.log(
i ); }, 1000 * i) }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6bea87dad912221241-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87dad912221241-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87dad912221241-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87dad912221241-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87dad912221241-5">
5
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6bea87dad912221241-1" class="crayon-line">
for( var i = 0; i &lt; 5; i++ ) {
</div>
<div id="crayon-5b8f6bea87dad912221241-2" class="crayon-line crayon-striped-line">
 setTimeout(() =&gt; {
</div>
<div id="crayon-5b8f6bea87dad912221241-3" class="crayon-line">
 console.log( i );
</div>
<div id="crayon-5b8f6bea87dad912221241-4" class="crayon-line crayon-striped-line">
 }, 1000 * i)
</div>
<div id="crayon-5b8f6bea87dad912221241-5" class="crayon-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

答案大家都知道:**每秒钟输出一个5,一共输出5次**。

那么如何做到**每秒钟输出一个数,以此为0,1,2,3,4**呢?

我们这里只介绍闭包的解决方法,其他类似块作用域等等的解决方法,我们这里不讨论。



for( var i = 0; i &lt; 5; i++ ) { ((j) =&gt; { setTimeout(() =&gt; {
console.log( j ); }, 1000 * j) })(i) }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6bea87db1013292990-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87db1013292990-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87db1013292990-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87db1013292990-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87db1013292990-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87db1013292990-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87db1013292990-7">
7
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6bea87db1013292990-1" class="crayon-line">
for( var i = 0; i &lt; 5; i++ ) {
</div>
<div id="crayon-5b8f6bea87db1013292990-2" class="crayon-line crayon-striped-line">
 ((j) =&gt; {
</div>
<div id="crayon-5b8f6bea87db1013292990-3" class="crayon-line">
 setTimeout(() =&gt; {
</div>
<div id="crayon-5b8f6bea87db1013292990-4" class="crayon-line crayon-striped-line">
 console.log( j );
</div>
<div id="crayon-5b8f6bea87db1013292990-5" class="crayon-line">
 }, 1000 * j)
</div>
<div id="crayon-5b8f6bea87db1013292990-6" class="crayon-line crayon-striped-line">
 })(i) 
</div>
<div id="crayon-5b8f6bea87db1013292990-7" class="crayon-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

“setTimeout”方法里应用了闭包,使其内部能够记住每次循环所在的词法作用域和作用域链。

由于setTimeout中的回调函数会在当前任务队列的尾部进行执行,因此上面第一个例子中每次循环中的setTimeout回调函数记住的i的值是for循环作用域中的值,此时都是5,而第二个例子记住的i的数为setTimeout的父级作用域自执行函数中的j的值,依次为0,1,2,3,4。
  1. 监听器
var oDiv = document.querySeletor("#div");
oDiv.addEventListener('click', function() { console.log( oDiv.id );
})

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6bea87db4035872148-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87db4035872148-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87db4035872148-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87db4035872148-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87db4035872148-5">
5
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6bea87db4035872148-1" class="crayon-line">
var oDiv = document.querySeletor(&quot;#div&quot;);
</div>
<div id="crayon-5b8f6bea87db4035872148-2" class="crayon-line crayon-striped-line">
 
</div>
<div id="crayon-5b8f6bea87db4035872148-3" class="crayon-line">
oDiv.addEventListener('click', function() {
</div>
<div id="crayon-5b8f6bea87db4035872148-4" class="crayon-line crayon-striped-line">
 console.log( oDiv.id );
</div>
<div id="crayon-5b8f6bea87db4035872148-5" class="crayon-line">
})
</div>
</div></td>
</tr>
</tbody>
</table>

=- 关于闭包,我觉得我说清楚了,你看清楚了吗?留言告诉我吧 -=

如果你觉得写的还不是很烂,请关注我的 github 吧,让我们一起成长。。。

1 赞 3 收藏 评论

必威 2

3. 变量的生命周期

我们知道的事情有:

1. 一个局部变量当定义该变量的函数调用结束时,该变量就会被垃圾回收机制回收而销毁。再次调用该函数时又会重新定义了一个新变量。

  1. 按照代码书写时的样子,内部函数可以访问函数外面的变量。

那么,如果在函数内部声明一个内部函数,并将内部函数作为值返回,调用外部函数之后,内部函数保持对外部函数词法作用域的引用,这样会发生什么呢?由于内部函数作为值返回了出去,所以外层函数执行完毕,其词法作用域中的变量也不会被销毁。

**  [块作用域与函数作用域](

什么是块作用域?

  • 块作用域

    • 除 JavaScript 外的很多编程语言都支持块作用域,因此其他语言的开发者对于相关的思维 方式会很熟悉,但是对于主要使用 JavaScript 的开发者来说,这个概念会很陌生。

      - 看下面的代码

      for (var i=0; i<10; i++) { console.log}
      
      • 我们在for的头部定义了变量 i ,而且我们只想在for循环中使用 i ,而忽略了在 javascript 中 i 会绑定在全局变量中。
      • for(let i = 0;i < 10; i++ ){ console.log;}

拨开闭包神秘的面纱

我们先看一个闭包的例子:

function foo() { let a = 2; function bar() { console.log( a ); } return bar; } let baz = foo(); baz();

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
    let a = 2;
 
    function bar() {
        console.log( a );
    }
 
    return bar;
}
 
let baz = foo();
 
baz();

大家肯定都写过类似的代码,相信很多小伙伴也知道这段代码应用了闭包,but, Why does the closure be generated and Where is closure?

来,我们慢慢分析:

首先必须先知道闭包是什么,才能分析出闭包为什么产生和闭包到底在哪?

当一个函数能够记住并访问到其所在的词法作用域及作用域链,特别强调是在其定义的作用域外进行的访问,此时该函数和其上层执行上下文共同构成闭包。

需要明确的几点:

  1. 闭包一定是函数对象(wintercn大大的闭包考证)
  2. 闭包和词法作用域,作用域链,垃圾回收机制息息相关
  3. 当函数一定是在其定义的作用域外进行的访问时,才产生闭包
  4. 闭包是由该函数和其上层执行上下文共同构成(这点稍后我会说明)

闭包是什么,我们说清楚了,下面我们看下闭包是如何产生的。

接下来,我默认你已经读过我之前的两篇文章 原来JavaScript内部是这样运行的 和 彻底搞懂JavaScript作用域 , 建议先进行阅读理解JS执行机制和作用域等相关知识,再理解闭包,否则可能会理解的不透彻。

现在我假设JS引擎执行到这行代码

let baz = foo();

此时,JS的作用域气泡是这样的:

必威 3

这个时候foo函数已经执行完,JS的垃圾回收机制应该会自动将其标记为”离开环境”,等待回收机制下次执行,将其内存进行释放(标记清除)。

但是,我们仔细看图中粉色的箭头,我们将bar的引用指向baz,正是这种引用赋值,阻止了垃圾回收机制将foo进行回收,从而导致bar的整条作用域链都被保存下来

接下来,baz()执行,bar进入执行栈,闭包(foo)形成,此时bar中依旧可以访问到其父作用域气泡中的变量a。

这样说可能不是很清晰,接下来我们借助chrome的调试工具看下闭包产生的过程。

当JS引擎执行到这行代码let baz = foo();时:

必威 4

图中所示,let baz = foo();已经执行完,即将执行baz();,此时Call Stack中只有全局上下文。

接下来baz();执行:

必威 5

我们可以看到,此时bar进入Call Stack中,并且Closure(foo)形成。

针对上面我提到的几点进行下说明:

  1. 上述第二点(闭包和词法作用域,作用域链,垃圾回收机制息息相关)大家应该都清楚了
  2. 上述第三点,当函数baz执行时,闭包才生成
  3. 上述第四点,闭包是foo,并不是bar,很多书(《you dont know JavaScript》《JavaScript高级程序设计》)中,都强调保存下来的引用,即上例中的bar是闭包,而chrome认为被保存下来的封闭空间foo是闭包,针对这点我赞同chrome的判断(仅为自己的理解,如有不同意见,欢迎来讨论)

闭包


经典案例

下面是一个经典的事件绑定例子:

<div id = "test">
    <p>栏目1</p>
    <p>栏目2</p>
    <p>栏目3</p>
    <p>栏目4</p>
</div>
 </body>
<script type="text/javascript">    
function bindClick(){
    var allP = document.getElementById("test").getElementsByTagName("p"),
    i=0,
    len = allP.length;        
    for( ;i<len;i++){
    allP[i].onclick = function(){
        alert("you click the "+i+" P tag!");//you click the 4 P tag!    
    }
    }
}
bindClick();//运行函数,绑定点击事件
</script>

上面的代码给P标签添加点击事件,但是不管我们点击哪一个p标签,我们获取到的结果都是“you click the 4 P tag!”。

我们可以把上述的JS代码给分解一下,让我们看起来更容易理解,如下所示。前面使用一个匿名函数作为click事件的回调函数,这里使用的一个非匿名函数,作为回调,完全相同的效果。

function bindClick(){
    var allP = document.getElementById("test").getElementsByTagName("p"),
    i=0,
    len = allP.length;
    for( ;i<len;i++){
    allP[i].onclick = AlertP;
    }
    function AlertP(){
    alert("you click the "+i+" P tag!");
    }
}
bindClick();//运行函数,绑定点击事件

这里应该没有什么问题吧,前面使用一个匿名函数作为click事件的回调函数,这里使用的一个非匿名函数,作为回调,完全相同的效果。也可以做下测试哦。

理解上面的说法了,那么就可以很简单的理解,为什么我们之前的代码,会得到一个相同的结果了。首先看一下for循环中,这里我们只是对每一个匹配的元素添加了一个click的回调函数,并且回调函数都是AlertP函数。这里当为每一个元素添加成功click之后,i的值,就变成了匹配元素的个数,也就是i=len,而当我们触发这个事件时,也就是当我们点击相应的元素时,我们期待的是,提示出我们点击的元素是排列在第几行。当click事件触发时,执行回调函数AlertP,但是当执行到这里的时候,发现alert方法中,有一个变量是未知的,并且在AlertP的局部作用域中,也没有查找到相应的变量,那么按照作用域链的查找方式,就会向父级作用域去查找,这里的父级作用域中,确实是有变量i的,而i的值,却是经过for循环之后的值,i=len。所以也就出现了我们最初看到的效果。

解决办法如下所示:

function bindClick(){
    var allP = document.getElementById("test").getElementsByTagName("p"),
  i=0,
  len = allP.length;
    for( ;i<len;i++){
    AlertP(allP[i],i);
    }
    function AlertP(obj,i){
    obj.onclick = function(){
        alert("you click the "+i+" P tag!");
    }
    }
}
bindClick();

这里,objiAlertP函数内部,就是局部变量了。click事件的回调函数,虽然依旧没有变量i的值,但是其父作用域AlertP的内部,却是有的,所以能正常的显示了,这里AlertP我放在了bindClick的内部,只是因为这样可以减少必要的全局函数,放到全局也不影响的。

这里是添加了一个函数进行绑定,如果我不想添加函数呢,当然也可以实现了,这里就要说到自执行函数了。可以跳到本文的自执行函数,也可以看参考引文的深度讲解:浅析作用域链–JS基础核心之一

参考:

你不知道的javascript

学习Javascript闭包

闭包的应用的注意事项

闭包,在JS中绝对是一个高贵的存在,它让很多不可能实现的代码成为可能,但是物虽好,也要合理使用,不然不但不能达到我们想要的效果,有的时候可能还会适得其反。

  • 内存泄漏(Memory Leak)JavaScript分配给Web浏览器的可用内存数量通常比分配给桌面应用程序的少,这样做主要是防止JavaScript的网页耗尽全部系统内存而导致系统崩溃。因此,要想使页面具有更好的性能,就必须确保页面占用最少的内存资源,也就是说,我们应该保证执行代码只保存有用的数据,一旦数据不再有用,我们就应该让垃圾回收机制对其进行回收,释放内存。

    我们现在都知道了闭包阻止了垃圾回收机制对变量进行回收,因此变量会永远存在内存中,即使当变量不再被使用时,这样会造成内存泄漏,会严重影响页面的性能。因此当变量对象不再适用时,我们要将其释放。

    我们拿上面代码举例:

function foo() { let a = 2; function bar() { console.log( a ); }
return bar; } let baz = foo(); baz();
//baz指向的对象会永远存在堆内存中 baz = null;
//如果baz不再使用,将其指向的对象释放

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6bea87da5441991997-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da5441991997-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da5441991997-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da5441991997-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da5441991997-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da5441991997-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da5441991997-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da5441991997-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da5441991997-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da5441991997-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da5441991997-11">
11
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da5441991997-12">
12
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da5441991997-13">
13
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6bea87da5441991997-14">
14
</div>
<div class="crayon-num" data-line="crayon-5b8f6bea87da5441991997-15">
15
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6bea87da5441991997-1" class="crayon-line">
 function foo() {
</div>
<div id="crayon-5b8f6bea87da5441991997-2" class="crayon-line crayon-striped-line">
     let a = 2;
</div>
<div id="crayon-5b8f6bea87da5441991997-3" class="crayon-line">

</div>
<div id="crayon-5b8f6bea87da5441991997-4" class="crayon-line crayon-striped-line">
     function bar() {
</div>
<div id="crayon-5b8f6bea87da5441991997-5" class="crayon-line">
         console.log( a );
</div>
<div id="crayon-5b8f6bea87da5441991997-6" class="crayon-line crayon-striped-line">
     }
</div>
<div id="crayon-5b8f6bea87da5441991997-7" class="crayon-line">

</div>
<div id="crayon-5b8f6bea87da5441991997-8" class="crayon-line crayon-striped-line">
     return bar;
</div>
<div id="crayon-5b8f6bea87da5441991997-9" class="crayon-line">
 }
</div>
<div id="crayon-5b8f6bea87da5441991997-10" class="crayon-line crayon-striped-line">

</div>
<div id="crayon-5b8f6bea87da5441991997-11" class="crayon-line">
 let baz = foo();
</div>
<div id="crayon-5b8f6bea87da5441991997-12" class="crayon-line crayon-striped-line">

</div>
<div id="crayon-5b8f6bea87da5441991997-13" class="crayon-line">
 baz(); //baz指向的对象会永远存在堆内存中
</div>
<div id="crayon-5b8f6bea87da5441991997-14" class="crayon-line crayon-striped-line">

</div>
<div id="crayon-5b8f6bea87da5441991997-15" class="crayon-line">
 baz = null; //如果baz不再使用,将其指向的对象释放
</div>
</div></td>
</tr>
</tbody>
</table>

关于内存泄漏,推荐
[阮一峰老师博客](http://www.ruanyifeng.com/blog/2017/04/memory-leak.html)。

1. 什么是闭包?

作为一名前端无知小白,我猜MDN的中文版一定是机器翻译的,因为有时翻到上面的中文怎么看都不像是人话,比如闭包的定义:

Closures (闭包)是使用被作用域封闭的变量,函数,闭包等执行的一个函数的作用域。通常我们用和其相应的函数来指代这些作用域。(可以访问独立数据的函数)

能看得懂这个定义才真有鬼了。

好的,我们还是用蹩脚的英文来看看吧:

“A closure is the combination of a function and the lexical environment within which that function was declared.“

闭包是函数以及在函数声明下的词法环境的结合。

函数?变量?词法环境?

似乎感觉懂了一点点?

翻了下JS权威指南,里面说”Javascript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为闭包“。

所以我们从变量的作用域开始谈起。

代码块

JavaScript中的代码块是指由<script>标签分割的代码段。JS是按照代码块来进行编译和执行的,代码块间相互独立,但变量和方法共享。如下:

<script type="text/javascript">//代码块一
var test1 = "我是代码块一test1";
alert(str);//因为没有定义str,所以浏览器会出错,下面的不能运行
alert("我是代码块一");//没有运行到这里
var test2 = "我是代码块一test2";//没有运行到这里但是预编译环节声明提前了,所以有变量但是没赋值
</script>
<script type="text/javascript">//代码块二
alert("我是代码块二"); //这里有运行到
alert(test1); //弹出"我是代码块一test1"
alert(test2); //弹出"undefined"
</script>

上面的代码中代码块一中运行报错,但不影响代码块二的执行,这就是代码块间的独立性,而代码块二中能调用到代码一中的变量,则是块间共享性。

但是当第一个代码块报错停止后,并不影响下一个代码块运行。当然在下面的例子中,虽然代码块二中的函数声明预编译了,但是在代码块1中的函数出现Fn函数为定义错误(浏览器报错,并不是声明未赋值的undefined),说明代码块1完全执行后才执行代码块2。

<script type="text/javascript">//代码块1
Fn(); //浏览器报错:"undefined",停止代码块1运行
alert("执行了代码块1");//未运行
</script>
<script type="text/javascript">//代码块2
alert("执行了代码块2");//执行弹框效果
function Fn(){ //函数1
alert("执行了函数1");
}
</script>

所以js函数解析顺序如下:
  step 1. 读入第一个代码块。
  step 2. 做语法分析,有错则报语法错误(比如括号不匹配等),并跳转到step5。
  step 3. 对var变量和function定义做“预编译处理”(永远不会报错的,因为只解析正确的声明)。
  step 4. 执行代码段,有错则报错(比如变量未定义)。
  step 5. 如果还有下一个代码段,则读入下一个代码段,重复step2。
  step6. 结束。

:需要在页面元素渲染前执行的js代码应该放在<body>前面的<script>代 码块中,而需要在页面元素加载完后的js放在</body>元素后面,body标签的onload事件是在最后执行的。

<script type="text/javascript">
alert("first");
function Fn(){
alert("third");
}
</script>
<body onload="Fn()">
</body>
<script type="text/javascript">
alert("second");
</script>

什么是垃圾回收机制

  • 垃圾回收机制
    • JavaScript 垃圾回收的机制很简单:找出==不再使用的变量==,然后释放掉其占用的内存,但是这个过程不是时时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。
    • 那什么是==不再使用的变量==啦?
      • 我们知道js中的全局变量,和局部变量。全局变量在浏览器页面卸载的时候才会回收。而局部变量在函数生命周期结束的时候浏览器为了节约内存空间,就需要回收这一变量。
    • 一种回收方法-标记清除(mark and sweep)
      • 这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候将其标记为“离开环境”。
    • 还有其他的回收的方法就不多多探究了。
  • 闭包的理解

    • 作用域闭包

      • 参考阮一峰大神的文章,代码中的f2函数,就是闭包。
      function f1(){ var n = 999; function f2(){ alert; } return f2;}var result = f1();result();//999
      
      • 闭包就是能够读取其他函数内部变量的函数。

      • 由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

      • 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

    • 闭包的用途

      • 一个是前面提到的可以读取函数内部的变量
      • 另一个就是让这些变量的值始终保持在内存中。
      function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ alert; } return f2; }var result=f1();result(); // 999nAdd();result(); // 1000
      
      • result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
      • 原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

闭包,是真的美

2018/04/11 · JavaScript · 闭包

原文出处: 张建成   

欢迎评论和star

写这篇文章时的心情是十分忐忑的,因为对于我们今天的主角:闭包,很多小伙伴都写过关于它的文章,相信大家也读过不少,那些文章到底有没有把JS中这个近似神话的东西讲清楚,说实心里,真的有,但为数不多。

写这篇文章的初衷:让所有看到这篇文章的小伙伴都彻彻底底的理解闭包 => 提高JS水平 => 能够写出更高质量的JS代码。

开文之所以说心情是忐忑的,就是怕达不到我写该文的初衷,但是我有信心同时我也会努力的完成我的目标。如行文中有丝毫误人子弟的陈述,欢迎大家指正,在这感激不尽。

我们开始吧:

相信众多JS的lovers都听说过这句话:闭包很重要但是很难理解

我起初也是这么觉得,但是当我努力学习了JS的一些深层的原理以后我倒觉得闭包并不是那么不好理解,反倒是让我感觉出一种很美的感觉。当我彻底理解闭包的那一刹那,心中油然产生一种十分愉悦感觉,就像**”酒酣尚醉,花未全开”**那种美景一样。

这就是闭包!

老规矩,上代码:

function foo() {

    var a = 2;

    function bar() {

    console.log( a );

}

   return bar;

}

   var baz = foo();

 baz(); // 2 —— 朋友,这就是闭包的效果。

这段代码非常清晰地展示了闭包,函数 bar() 的词法作用域能够访问 foo() 的内部作用域。然后我们将 bar() 函数本身当作一个值类型进行传递。在 foo() 执行后,其返回值赋值给变量 baz 并调用 baz(),也就是调用了内部函数bar()。bar()在自己定义的词法作用域以外的地方执行。由于bar()保持着队foo()词法作用域的引用,所以foo()在执行完后内部作用域也不会被程序销毁。

循环和闭包

for循环是常见的说明闭包的例子,也是我这样的小白非常容易错的地方。

for (var i = 0; i < 5; i++) {

setTimeout(function () {

console.log(i)

}, 0)

}

我们可能会简单的以为控制台会打印出‘ 0 1 2 3 4 ’,可事实却打印出了5个‘ 5 ’,这又是为什么呢?我们发现,setTimeout 函数时异步的,等到函数执行时,for循环已经结束了,此时的 i 的值为 5,所以 function() { console.log(i) } 去找变量 i,只能拿到 5。

所以,改进一下:

for (var i = 0; i < 5; i++) {

!function (i) {

setTimeout(function () {

console.log(i)

}, 0)}(i)

我们套用了一个立即执行函数,当i=0, 此时 function(i){} 此匿名函数中的 i 的值为 0,等到 setTimeout 执行时顺着外层去找 i,这时就能拿到 0。如此循环,就能拿到想要的 0 1 2 3 4。

作为小白,可能还是太明白什么是立即执行函数,下篇我们会详细介绍。这里我们先换个例子。

function constfunc(v){return function(){  return v;  };} // 这个函数返回一个总是返回v的函数

var funcs = []; // 创建一个空的数组

for(var i=0; i<10;i++){funcs[i] = constfunc(i)}

for(i=0;i<funcs.length;i++) {console.log(funcs[i]())}

这样就可以打印出0~9了。

不知道你明白了没有,反正我是明白啦~

今天给自己加个鸡蛋!

作用域

什么是作用域?

  • 作用域

    • 众所周知 在javascript 作用域就是限制我们执行代码的一个范围,或者说是框架。
    • 首先来谈谈js的编译原理,其中不可避免的就要提到 引擎、编译器、作用域
      • 引擎:负责整个 JavaScript 程序的编译及执行过程
      • 编译器:负责语法分析及代码生成
      • 作用域: 负责收集并维护由所有声明的标识符组成的一系列查 询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限
    • 这次我们的主角是==作用域==
    • var a = 2;

      • 然后他们三个都干了什么啦?
        • 变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如 果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对 它赋值。
    • 当然我们不必深究他们到底干了什么,我们要深入探究 作用域到底是什么?

**  声明式函数、赋值式函数与匿名函数**

什么是函数作用域?

  • 函数作用域

    • 如同上面的词法作用域那样,在 javascript 中当我们创建一个函数的时候都会创建一个新的作用域。
    function foo{ var b = 2; //一些代码 function bar(){ // ... } // 更多的代码 var a = 3;}
    
    • 在这个代码片段中,==foo 的作用域气泡中包含了标识符 a、b、c 和 bar==。无论标识符 声明出现在作用域中的何处,这个标识符所代表的变量或函数都将附属于所处作用域的气泡。

  [代码块]()  **

什么是词法作用域?

  • 词法作用域

    • function foo { var b = a * 2; function bar { console.log( a, b, c ); } bar; }foo; // 2, 4, 12

      • 1 包含着整个全局作用域,其中只有一个标识符:foo。

      • 2 包含着 foo 所创建的作用域,其中有三个标识符:a、bar 和 b。

      • 3 包含着 bar 所创建的作用域,其中只有一个标识符:c。

    • 上面描述的就是作用域的作用,每个标识符都对应着相应的。我们==把作用域看做一个气泡==

作用域(scope)

TAG标签:
版权声明:本文由必威发布于必威-前端,转载请注明出处:你不知道的javascript必威,比如闭包的定义