作者:姚一豪
日期:2020年8月18日
一、变量的作用域
变量的作用域分为:全局变量和局部变量.
Js作为弱类型语言,可以体现在变量的作用域上,函数内部可以访问到全局变量
[code]var y = 3;function foo() {console.log(y);}foo(); // 3
函数外部无法访问到内部变量
[code]function foo(){var y = 3;}console.log(y); // y is not defined
二、外部访问函数内部变量方法
函数外部若想访问获取函数内部变量,可以在函数内部在定义一个函数.
[code]function foo() {var y = 3;function foo2() {console.log(y); //3}}
可见函数foo2就被包含于foo1中,这时候foo中的局部变量对foo2中是可见的,但是foo2中的变量对foo1中是不可见的,子对象会一级一级向上寻找所有父对象的变量,父对象所有的变量对子对象是可见的,这是Javascript语言的\”链式作用域\”结构.
foo2中可以访问foo1中的变量,所以我们外部想要获取foo中的变量,可以让foo2作为返回值.
[code]function foo() {var n = 521;function foo2() {console.log(n);}return foo2;}var result = foo();result(); // 521
三、闭包的定义
\”闭包\”在我通俗的理解为定义在一个函数内部的函数,可以读取父函数的局部变量.在JavaScript语言中,只有函数内部的子函数才能读取局部变量,在上一段代码中,foo2就是闭包closure.
四.闭包的用处
闭包在我目前认为,最大的作用体现在两个方面,一是前面提到的访问局部变量,另外一个是它可以使变量的值始终保存在内存中.
[code]function foo() {var y = 520;add = function () {y++;};function foo2() {console.log(y);}return foo2;}var result = foo();result(); // 520add();result(); // 521
在这段代码中,result实际上就是闭包foo2函数。它一共运行了两次,第一次的值是520,第二次的值是521。这证明了,函数foo中的局部变量y一直保存在内存中,并没有在foo调用后被自动清除。
为什么会这样呢?原因就在于foo是foo2的父函数,而foo2被赋给了一个全局变量,这导致foo2始终在内存中,而foo2的存在依赖于foo,因此foo也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是\”add=function(){y++}\”这一行,首先在add前面没有使用var关键字,因此add是一个全局变量,而不是局部变量。其次,add的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以add相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
五、使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
六、例题分析
[code]function test() {var n = 4399;function add() {n++;console.log(n);}return { n: n, add: add };}var result = test();var result2 = test();result.add();result.add();console.log(result.n);result2.add();//4400//4401//4399//4400
js在执行之前,会将所有带var和function的进行提前定义和声明。(带var的提前声明,function声明和定义都完成了)
首先,在全局作用域下,进行预解释:
test=xxxfff000(开辟一个堆内存,里面存的是代码字符串)
var result(声明一个变量result)
var result2(声明一个变量result2)
——————————-
代码执行:
result=test() –>将test执行的返回结果赋值给result,是一个对象,再开辟一个堆内存,test执行,形成一个私有作用域A
再进行预解释和代码执行等一系列操作
result2=test() 同理
result.add() –>方法执行形成一个私有作用域
n++ 顺着作用域链向上寻找到test作用域A(A这个作用域不销毁,因为被全局变量result占用了)中的n为4399,n++ 》4400
(这时test这个作用域A下的n变成4400)
(1) console.log(n) //4400
==============================
result.add() –>方法执行形成一个新的私有作用域
n++ 顺着作用域链向上寻找到test作用域(A)中的n为4400,n++ 》4401
(2) console.log(n) //4401
===============================
(3) console.log(result.n) //4399
此时找的只是result对应的那个堆内存中的n
===============================
result2.add() –>方法执行形成一个私有作用域
n++ 顺着作用域链向上寻找到test作用域(B)中的n为4399,n++ 》4400
(3) console.log(n) //4400