闭包与变量:
作用域链的一个副作用,闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。
function fn1(){ //创建一个数组 var arr = new Array(); //为数组赋值,此时i是保存在fn1 这个作用域中 for (var i = 0; i <10; i++) { arr[i] = function(){ return i } } return arr; } var fs = fn1(); for (var i = 0; i < fs.length; i++) { fs[i](); //此时通过闭包来调用函数,会去上一级作用域中找,这是i的值已经是10,所以会连续输出10个 10; };
解决方法:通过创建另一个匿名函数强制让闭包的行为符合预期,
function fn1(){ var arr = new Array(); for (var i = 0; i <10; i++) { arr[i] = (function(num){ return function(){ return num; } })(i); //此时 有大量的作用域 } return arr; } var fs = fn1(); for (var i = 0; i < fs.length; i++) { fs[i](); //此时就是 0,1,2,3,4,5,6,7,8,9 , };
消耗大量的内存,
闭包的this问题:
在闭包中使用 this 对象也可能会导致一些问题,this 对象是在运行时基于函数的执行环境绑定的:
在全局函数中, this 等于 window,当函数作为某个对象的方法调用时, this 等于那个对象。不过,匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。
var name ="A"; var object = { name: "obj", say: function(){ return function(){ return this.name; } } } object.say()(); //此时,输出的是A,当调用object.say()后,object的函数 内存空间已经释放,但是里面的闭包 依然存在,此时这个this就是指向window
解决方法:
var name ="A"; var object = { name: "obj", say: function(){ //此时 that 就指向了 object var that = this; return function(){ return that.name; } } }
object.say()(); //此时,输出的是obj,
内存的泄露:
如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无法被销毁。
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
创建了一个作为 element 元素事件处理程序的闭包,匿名函数保存了一个对 assignHandler()的活动对象的引用,只要匿名函数存在, element 的引用数至少也是 1,因此它所
占用的内存就永远不会被回收。可以通过稍微改写一下代码来解决,
function assignHandler(){ var element = document.getElementById("someElement"); var id = element.id; element.onclick = function(){ alert(id); }; element = null; }
把 element.id 的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。仅仅做到这一步,还是不能解决内存泄漏。闭包会引用包含函数
的整个活动对象,而其中包含着 element。即使闭包不直接引用 element,包含函数的活动对象中也仍然会保存一个引用。因此,有必要把 element 变量设置为 null。
块级作用域:
JavaScript 没有块级作用域的概念,不管是使用循环还是判断之后,这个变量会一直存在,所以当全局中使用了 循环 或者判断后,这个变量可能会影响到函数的变量,所以除非特殊情况,不要使用全局变量,且 全局变量在作用域链最顶部,访问最慢,
for (var i = 0; i < 10; i++) { }; alert(i); //此时i就是 10;
解决的方式是匿名函数,用作块级作用域(通常称为私有作用域)的匿名函数的语法如下所示。
(function(){ //这里是块级作用域 })();
定义并立即调用了一个匿名函数。在一个由很多开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突。而通过创建私有作用域,每个开发人员既可
以使用自己的变量,又不必担心搞乱全局作用域。
这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。
私有变量:
私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
function add(num1, num2){ var sum = num1 + num2; return sum; }这个函数内部,有 3 个私有变量: num1、 num2 和 sum。在函数内部可以访问这几个变量,但在函数外部则不能访问它们。如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访 问这些变量。
有权访问私有变量和私有函数的公有方法称为特权方法(privileged method)。
function Person(name){ //特权方法 this.setName = function(value){ name = value; } //特权方法 this.getName = function(){ return name; } } var p = new Person("Linda"); p.setName("Joke"); p.getName(); //Joke
两个特权方法: getName()和 setName()。这两个方法都可以在构造函数外部使用,而且都有权访问私有变量 name。但在 Person 构造函数外部,没有任何办法访问 name。
它们作为闭包能够通过作用域链访问 name。私有变量 name在 Person 的每一个实例中都不相同,因为每次调用构造函数都会重新创建这两个方法。
构造函数模式的缺点是针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方法就可以避免这个问题。
静态私有变量:
多查找作用域链中的一个层次,就会在一定程度上影响查找速度。而这正是使用闭包和私有变量的一个显明的不足之处
模块模式
模块模式通过为单例添加私有变量和特权方法能够使其得到增强,
var singleton = (function(){ //私有变量和私有函数 var privateVariable = 10; function privateFunction(){ return false; } //特权/公有 方法和属性 return { publicProperty: true, publicMethod : function(){ privateVariable++; return privateFunction(); } } })();
这个模块模式使用了一个返回对象的匿名函数。匿名函数内部,定义了私有变量和函数。返回的对象 字面量中只包含可以公开的属性和方法。由于这个对象是在匿名函数内部定义的,因此它的公有方法 有权 访问私有变量和函数。这个对象字面量定义的是单例的公共接口。这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的:
var application = (function(){ //私有变量和函数 var components = new Array(); components.push(new BaseComponent()); //公有 return{ getComponent:function(){ return components.length; }, registerComponent:function(component){ if(typeof component == "object") { components.push(component); } } } })();
如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。以这种模式创建的每个单例都是 Object 的实例,
增强的模块模式:
这种增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。
var singleton = (function(){ //私有变量和私有函数 var privateVariable = 10; function privateFunction(){ return false; } //创建对象 var object = new CustomType(); //添加特权/公有属性和方法 object.publicProperty = true; object.publicMethod = function(){ privateVariable++; return privateFunction(); }; //返回这个对象 return object; })();
总结:
函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫做匿名函数。
递归函数应该始终使用 arguments.callee 来递归地调用自身,不要使用函数名——函数名可能会发生变化。
当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理:
闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。
函数的作用域及其所有变量都会在函数执行结束后被销毁。
当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。使用匿名函数可以在 JavaScript 中模仿块级作用域(JavaScript 本身没有块级作用域的概念),要点如下。
? 创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。
? 结果就是函数内部的所有变量都会被立即销毁—
闭包还可以用于在对象中创建私有变量,相关概念和要点如下。
? 即使 JavaScript 中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量。
? 有权访问私有变量的公有方法叫做特权方法。
? 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强的模块模式来实现单例的特权方法。
JavaScript 中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。因为创建闭包必须维护额外的作用域,所以过度使用它们可能会占用大量内存。
,