谈谈JavaScript中的函数与闭包 |
闭包这东西,说难也难,说不难也不难,下面我就以自己的理解来说一下闭包 一、闭包的解释说明 Javascript采用词法作用域,函数的执行依赖于变量作用域,这个作用域是在定义函数时确定的 。因此Javascript中函数对象不仅保存代码逻辑,还必须引用当前的作用域链 。Javascript中函数内部的局部变量可以被修改,而且当再次进入到函数内部的时候,上次被修改的状态仍然持续 。这是因为因为局部变量并不保存在栈上,而是通过一个对象来保存 。 决定使用哪个变量是由作用域链决定的,每次生成函数实例时,都会为之创建一个对象用来保存局部变量,并且把这个用于保存局部变量的对象加入作用域链中 。不同函数对象可以通过作用域链关联起来 。Javascript中所有函数都是闭包,我们不能避免“产生”闭包 。 引用一张《Javascript高级程序设计》中的图来说明,虽然这张图并不完全说明所有情况 。图中的activation object就是用于保存变量的对象 。
闭包:函数实例保存着在执行时所需要的变量的引用,而不会复制保存当时变量的值 。(在Object C的实现中,我们可以选择保存当时的值或者是引用) 作用域链:解析变量时查找变量所在的方式,以var作为终止符号,如果链上一直没有var,则一直追溯到全局对象为止 。 C#中的闭包特性是由编译器把局部变量转换成引用类型的对象成员实现的 。 二、闭包的使用 1.闭包是在定义的时候产生的 function Foo(){ function A(){} function B(){} function C(){}} var scope = "global scope"; function checkscope() { var scope = "local scope"; function f() { return scope; } return f;}checkscope()()
(function(){ function A(){} function B(){} function C(){}}()) 我们之所以这么写,目地有两个 1.避免污染全局对象 2.避免多次产生相同的函数实例
对比下面两个例子,闭包是如何保存作用域链的: function A(){} //比较省内存的写法,创建对象速度快,开销小 (function(prototype){ var name = "a"; function sayName () { alert(name); } function ChangeName() { name += "_changed" } prototype.sayName = sayName;//引用通过执行匿名函数产生的闭包,闭包只会产生一次 prototype.changeName = ChangeName; }(A.prototype)) var a1 = new A(); var a2 = new A();
function B(){ //原型链比较短的做法,找到方法的速度快,但是比较耗内存,每次new 调用构造器都有2个函数实例和1个变量产生 。 var name = "b"; function sayName () { alert(name); } function changeName() { name += "_changed"; } this.sayName = sayName;//引用闭包,每次调用函数B都会产生新的闭包 this.changeName = changeName; }//如果函数调用之前带有new关键字,则函数作为构造器使用 。//本质上来说作为构造器和作为普通函数调用没区别 。如果直接调用B(),那么this对象会绑定到全局对象,新生成的闭包会代替旧的闭包赋给全局对象的changeName和sayName属性上,因此旧的闭包会被当成垃圾回收 。//如果作为构造器使用,new 关键字会生成一个新的对象(this指向这个新对象)并初始化这个新对象的sayName和changeName属性,因此每次生成的闭包都会因为有引用而保留下来 。 var b1 = new B(); b1.sayName(); b1.changeName(); b1.sayName(); var b2 = new B(); b2.sayName(); b1.sayName();
1.存在循环引用 2.有些对象总不能销毁,如IE6在DOM中的内存泄漏,或者在销毁时不能通知到Javascript引擎,因此也就有些Javascript闭包总不能被销毁 。这些情况通常是发生在Javascript宿主对象和Javascript中原生对象沟通不畅导致 。 |