有点蒙蔽,总结一哈,也不知道对不对。。
闭包:有权访问另一个函数作用域中的变量的函数。
一、基础闭包。
由一下俩特性:
1. 内部函数可以用外层的变量(作用链current > out > outout > outoutout ...)
2. 函数名是一个指针,所以可以很多名字指向一个函数体
组合改编(把内部函数作为返回值)一下成为最简单的闭包:
function outFunc(){
var woshiwaimiandebianliang = "a";
return inFunc; //千万~!不能写括号,写括号代表调用,只一个名字代表指向的函数体,返回的是函数体
function inFunc(){
woshiwaimiandebianliang = "我在里面,我可以用外面的变量";
}
/* 可以省略内部函数的函数名
return function(){
woshiwaimiandebianliang = "我在里面,我可以用外面的变量";
}
*/
}
var wokeyizhixiangshangmiannagehanshu = outFunc();
二、闭包与变量
副作用:闭包只能取得函数中任何变量的最后一个值。
function outFunc(){
var result = new Array();
for(var i=0; i<10; i++){
result[i] = function(){
return i;
};
}
return result;
}
高程:因为每个函数的作用域链中都保存着outFunc()函数的活动对象,所以每次引用的都是同一个i(最后一个:10),即每次都返回10;
详解:首先要知道一点:
仅仅声明某一个函数,引擎并不会对函数内部的任何变量进行查找或赋值操作。只会对函数内部的语法错误进行检查(如果往内部函数加上非法语句,那么不用调用也会报错)(摘自网络),只有在运行该函数的时候才会对内部的变量进行查找操作(感觉是编译原理?)。所以是酱滴:result[3] = function(){ return i ;//i不是常量而是变量}; 只有在运行是才会在作用域链中查找i,找到在外层此时已经为10的i。
解决:
用一个函数强行使i成为常量(传参的方式):
function outFunc(){
var result = new Array();
for(var i=0; i<10; i++){
result[i] = fn(i);
}
function fn(num){
return function(){
return num;
};
};
return result;
}
简写成匿名函数:
function outFunc(){
var result = new Array();
for(var i=0; i<10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
可见:闭包与变量的经典问题 http://www.cnblogs.com/kindofblue/p/4907847.html
三、this对象
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()); //function(){return this.name;}; 调用该函数返回一个函数
alert(object.getNameFunc()()); //The Window 调用该函数返回的函数
问题在于为什么第二个输出为"The Window"而不是"My Object"。
解析:首先要明确this返回的是 调用本函数的对象。所以当运行时会在作用域链中查找this首先找函数本身的this(肯定会找到所以不会再向上层查找),调用本函数的对象是windows,所以返回windows下的name。
解决:若想返回object的name只需利用闭包的性质 在object内存下此时的对象(var that = this;)然后再返回的函数中返回that的name。
四、内存泄漏
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
垃圾回收:当一个对象的引用为0的时候,该对象所占内存会被回收。
因为闭包的特性(外层函数结束,也可以通过调用内层函数访问外层函数的变量),因此总会保留一个闭包函数对该对象的引用,即element的引用永远不会为0;
解决:
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
用一个变量代替对外层对象的使用。element=null是因为:闭包会引用包含函数的整个活动对象(即使闭包函数没直接用element,也会保存),所以要设置为null.
五、模仿块级作用域
形式:
(function(){
//这里是块级作用域;<— 函数表达式后可跟(),运行;function被当成函数声明的开始,函数声明不可以跟()运行;所以用括号包裹成为函数表达式。
})();
作用:限制向全局作用域中添加过多的变量和函数;也可以用来减少闭包占用的内存问题
六、私有变量
可以通过闭包创建用于访问私有变量/函数的共有方法——特权方法
function Person(name){
//私有变量和私有函数
var privateVariable = 10;
function think(){
return "Person can think";
}
//特权方法
this.setName = function(value){
name = value;
};
this.getName = function(){
return name;
};
}
var person = new Person("hhh");
alert(person.getName()); //hhh
person.setName("lll");
alert(person.getName()); //lll
缺点:必须使用构造函数模式
七、静态私有变量
(function(){
var name = '';
Person = function(value){
name = value;
}
Person.prototype.getName = function(){
return name;
}
Person.prototype.setName = function(value){
name = value;
}
})();
var person1 = new Person("Nicholas");
alert(person1.getName()); //Nicholas
person1.setName("Greg");
alert(person1.getName()); //Greg
var person2 = new Person('Michael');
alert(person1.getName()); //Michael
alert(person2.getName()); //Michael
懒得写了: http://www.cnblogs.com/tianxintian22/archive/2015/12/14/5045017.html
八、模块模式
为单例创建私有变量和特权方法
function BaseComponent(){
//code
}
function OtherComponent(){
//code
}
var application = function(){
//私有成员变量
var components = new Array();
//初始化
components.push(new BaseComponent());
//公有
return{
getComponentCount : function(){
return components.length;
},
registerComponent : function(component){ //维护私有变量
if(typeof component == "object"){
components.push(component);
}
}
};
}();
对单例进行初始化又需要维护私有变量。
九、增强的模块模式
适用于单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强。
var application = function(){
//私有变量和函数
var components = new Array();
//初始化
components.push(new BaseComponent());
//创建application的一个局部副本
var app = new BaseComponent();
//公共接口
app.getComponentCount = function(){
return components.length;
};
app.registerComponent = function(component){
if(typeof component == "object"){
components.push(component);
}
};
//返回这个副本
return app;
}();