一个变量的使用范围叫做作用域
为了避免函数内外的变量间互相干扰
1、全局作用域:保存着所有全局变量/函数
2、函数作用域:保存函数内的局部变量
先使用局部变量,当找不到局部变量时,再去全局寻找
创建一个数组(执行环境栈ESC):用于记录正在执行的函数
浏览器本身也是一个程序,会创建全局变量作用域对象(window),保存所有浏览器内置的对象和方法
在全局创建函数名变量
在window之外创建函数对象保存函数定义
函数名变量通过地址引用函数对象
函数对象使用scope属性指回自己来自的作用域
现在ECS中添加本次函数调用的记录
为本次函数调用创建专门的函数作用域的对象(AO)
在函数作用域对象中保存本次函数调用所需的所有局部变量
函数作用域对象的parent属性指向函数来自的父级作用域对象
变量的使用顺序:就近原则->先使用局部,如果局部没有再去全局window中找
将本次函数调用的记录从ECS中出栈
导致函数作用域对象释放
导致局部变量一同释放
故:局部变量不可重用!
由多级作用域主机引用而形成的链式结构
作用:
-1、保存了所有变量
-2、控制了变量的使用顺序:先局部,后全局
即重用变量,又保护变量不被篡改的一种机制
全局变量-优点:可以重用。 缺点:随处可用,容易被污染
局部变量-优点:仅函数内可用,不会被污染。缺点:不可以重用
当我们既想重用变量又想保护变量不被污染的情况下,就可以使用闭包。
1、用外层函数包裹受保护的变量和内层函数
2、外层函数将内层函数对象返回到外部(共3种途径)
-(1)使用return
-(2)直接给全局变量赋值
-(3)将内层函数包裹在数组/对象中返回
3、使用者调用外层函数,获得内层函数的对象
外层函数的函数作用域对象(AO)无法释放
可以用两步来判断是否是闭包:***
1、找受保护的变量:外层函数的局部变量
2、找外层函数共返回哪些内层函数:一次外层函数的调用,返回的多个内层函数,公用同一个闭包中的受保护的变量。
内层函数比普通函数占用更多的内存空间(外层函数的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
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
未完待续。。。