闭包的理解

首先需要了解什么是作用域和作用域链?

作用域(scope)

1、什么是作用域?

一个变量的使用范围叫做作用域

2、为什么存在作用域?

为了避免函数内外的变量间互相干扰

3、作用域分为两种

    1、全局作用域:保存着所有全局变量/函数

    2、函数作用域:保存函数内的局部变量

4、变量的使用顺序

先使用局部变量,当找不到局部变量时,再去全局寻找

5、函数的生命周期**

1、程序执行前

    创建一个数组(执行环境栈ESC):用于记录正在执行的函数

    浏览器本身也是一个程序,会创建全局变量作用域对象(window),保存所有浏览器内置的对象和方法

    闭包的理解_第1张图片

2、函数定义时

    在全局创建函数名变量

    在window之外创建函数对象保存函数定义

    函数名变量通过地址引用函数对象

    函数对象使用scope属性指回自己来自的作用域

闭包的理解_第2张图片

3、函数调用时

    现在ECS中添加本次函数调用的记录

    为本次函数调用创建专门的函数作用域的对象(AO)

    在函数作用域对象中保存本次函数调用所需的所有局部变量

    函数作用域对象的parent属性指向函数来自的父级作用域对象

    变量的使用顺序:就近原则->先使用局部,如果局部没有再去全局window中找

闭包的理解_第3张图片

4、函数调用后

    将本次函数调用的记录从ECS中出栈

    导致函数作用域对象释放

    导致局部变量一同释放

    故:局部变量不可重用!

闭包的理解_第4张图片

作用域链

由多级作用域主机引用而形成的链式结构

作用:

    -1、保存了所有变量

    -2、控制了变量的使用顺序:先局部,后全局


1、什么是闭包?

即重用变量,又保护变量不被篡改的一种机制

2、为什么要使用闭包?

全局变量-优点:可以重用。                         缺点:随处可用,容易被污染

局部变量-优点:仅函数内可用,不会被污染。缺点:不可以重用

当我们既想重用变量又想保护变量不被污染的情况下,就可以使用闭包。

3、判断闭包的三个特点***

1、用外层函数包裹受保护的变量和内层函数

2、外层函数将内层函数对象返回到外部(共3种途径)

    -(1)使用return

    -(2)直接给全局变量赋值

    -(3)将内层函数包裹在数组/对象中返回

3、使用者调用外层函数,获得内层函数的对象

4、闭包是怎么形成的?

外层函数的函数作用域对象(AO)无法释放

5、闭包的笔试题答题技巧

可以用两步来判断是否是闭包:***

    1、找受保护的变量:外层函数的局部变量

    2、找外层函数共返回哪些内层函数:一次外层函数的调用,返回的多个内层函数,公用同一个闭包中的受保护的变量。

6、闭包会导致的问题

内层函数比普通函数占用更多的内存空间(外层函数的AO)

如何解决?

一旦闭包不再使用时,应立即释放闭包结构

例:函数 = null

闭包的一些例子

1、使用闭包实现点赞功能



 
  
  Document
 
 
  
	
 

原理:如果单在函数内定义i,例如:

因为这里的i是局部变量,不可重用,所以i只会被递增一次,故i一直为1

而如果将i定义为全局变量,例如:

这样的话确实可以实现i的点击累加,但是由于i在这里是全局变量,故容易被污染,如果在函数任意位置重新定义了i,则函数无法

执行,所以这个时候需要使用闭包来实现既能重用又不会被污染

2、经典笔试题



 
  
  Document
 
 
  
 

这个例子参照判断闭包的三个特点中:外层函数将内层函数对象返回到外部的三种途径中的第二个途径-直接给全局变量赋值

因为nAdd没有被定义过,强行赋值会使JS将nAdd定义为全局变量,所以他也会被返回到外部,故也将执行

所以当先调用一次get()的时候函数作用域对象中的n为999

再调用nAdd()时,由于返回到外部,所以也会对函数作用域对象造成影响,所以这个时候n变成了1000

当再次调用get()时,函数作用域对象中的n变成了1000,所以会输出1000

闭包的理解_第5张图片

3、经典笔试题2:



 
  
  Document
 
 
  
 

这个例子中,数组容易迷惑人

这个例子参照判断闭包的三个特点中:外层函数将内层函数对象返回到外部的三种途径中的第二个途径-将内层函数包裹在数组/对象中返回。

所以即便是数组也返回了外部

第二个迷惑点是

arr[i]=function(){ console.log(i); }

这里虽然看起来i都受到for循环的影响,其实不然,因为function()的意思是构造一个函数

在后面function(){console.log(i);}中,只是构造了函数,并没有调用函数,所以function中的i并不受for的影响

所以只有arr[i]中的i会跟着for进行改变,并将function(){console.log(i);}添加到数组中,所以最后数组arr中

会包含三个function(){console.log(i);}

arr[

function(){console.log(i);}, -对应下标0    因为for循环从0开始 

function(){console.log(i);}, -对应下标1    到这里i变成1所以下标为1

function(){console.log(i);}  -对应下标2    到这里i变成2所以下标为2

]

另外:

for(var i=0,arr[];i<3;i++)

i在循环结束时并不等于2,因为等于二的情况下还是小于3的,只有再进入一次循环,执行一次i++时才算循环结束,

因为当i真正等于3的时候,才真正的不满足循环条件,循环才算真正结束,所以这个时候函数作用域对象中的i=3

闭包的理解_第6张图片

未完待续。。。

你可能感兴趣的:(web前端)