匿名函数就是没有名字的函数,闭包是可访问一个函数做用域里变量的函数。
设计模式
一、匿名函数数组
//普通函数浏览器
functionbox(){ //函数名是box闭包
return 'Lee';函数
}性能
//匿名函数学习
function(){ //匿名函数,会报错this
return 'Lee';spa
}prototype
//经过表达式自我执行
(functionbox(){ //封装成表达式
alert('Lee');
})(); //()表示执行函数,而且传参
//把匿名函数赋值给变量
varbox=function(){ //将匿名函数赋给变量
return 'Lee';
};
alert(box()); //调用方式和函数调用类似
//函数里的匿名函数
functionbox(){
return function(){ //函数里的匿名函数,产生闭包
return 'Lee';
}
}
alert(box()()); //调用匿名函数
二、闭包
闭包是指有权访问另外一个函数做用域中的变量的函数,建立闭包的常见的方式,就是在一个函数内部建立另外一个函数,经过另外一个函数访问这个函数的局部变量。
//经过闭包能够返回局部变量
functionbox(){
var user = 'Lee';
return function(){ //经过匿名函数返回box()局部变量
return user;
};
}
alert(box()()); //经过box()()来直接调用匿名函数返回值
varb=box();
alert(b()); //另外一种调用匿名函数返回值
使用闭包有一个优势,也是它的缺点:就是能够把局部变量驻留在内存中,能够避免使用全局变量。
(全局变量污染致使应用程序不可预测性,每一个模块均可调用必将引来灾难,因此推荐使用私有的,封装的局部变量)。
//经过全局变量来累加
varage=100; //全局变量
functionbox(){
age++; //模块级能够调用全局变量,进行累加
}
box(); //执行函数,累加了
alert(age); //输出全局变量
//经过局部变量没法实现累加
functionbox(){
varage = 100;
age++; //累加
return age;
}
alert(box()); //101
alert(box()); //101,没法实现,由于又被初始化了
//经过闭包能够实现局部变量的累加
functionbox(){
var age = 100;
return function(){
age++;
return age;
}
}
var b = box(); //得到函数
alert(b()); //调用匿名函数
alert(b()); //第二次调用匿名函数,实现累加
PS:因为闭包里做用域返回的局部变量资源不会被马上销毁回收,因此可能会占用更多的内存。过分使用闭包会致使性能降低,
建议在很是有必要的时候才使用闭包。做用域链的机制致使一个问题,在循环中里的匿名函数取得的任何变量都是最后一个值。
//循环里包含匿名函数
functionbox(){
var arr = [];
for(var i = 0; i < 5; i++){
arr[i] = function(){
return i;
};
}
return arr;
}
var b = box(); //获得函数数组
alert(b.length); //获得函数集合长度
for(var i = 0;i < b.length; i++){
alert(b[i]()); //输出每一个函数的值,都是最后一个值
}
上面的例子输出的结果都是5,也就是循环后获得的最大的i值。由于b[i]调用的是匿名函数,匿名函数并无自我执行,
等到调用的时候,box()已执行完毕,i早已变成5,因此最终的结果就是5个5。
//循环里包含匿名函数-改1,自我执行匿名函数
function box(){
vararr = [];
for(var i = 0;i < 5; i++){
arr[i] = (function(num){ //自我执行
return num;
})(i); //而且传参
}
return arr;
}
var b = box();
for(var i = 0;i < b.length; i++){
alert(b[i]); //这里返回的是数组,直接打印便可
}
改1中,咱们让匿名函数进行自我执行,致使最终返回给a[i]的是数组而不是函数了。最终致使b[0]-b[4]中保留了0,1,2,3,4的值。
//循环里包含匿名函数-改2,匿名函数下再作个匿名函数
function box(){
var arr = [];
for(var i = 0;i < 5; i++){
arr[i] = (function(num){
return function(){ //直接返回值,改2变成返回函数
return num; //原理和改1同样
}
})(i);
}
return arr;
}
var b = box();
for(var i = 0;i < b.length; i++){
alert(b[i]()); //这里经过b[i]()函数调用便可
}
改1和改2中,咱们经过匿名函数自我执行,当即把结果赋值给a[i]。每个i,是调用方经过按值传递的,因此最终返回的都是指定的递增的i。
而不是box()函数里的i。关于this对象在闭包中使用this对象也可能会致使一些问题,this对象是在运行时基于函数的执行环境绑定的,
若是this在全局范围就是window,若是在对象内部就指向这个对象。而闭包却在运行时指向window的,由于闭包并不属于这个对象的属性或方法。
var user = 'The Window';
var obj = {
user:'The Object',
getUserFunction:function(){
return function(){ //闭包不属于obj,里面的this指向window
return this.user;
};
}
};
alert(obj.getUserFunction()()); //Thewindow
//能够强制指向某个对象
alert(obj.getUserFunction().call(obj)); //TheObject
//也能够从上一个做用域中获得对象
getUserFunction:function(){
var that = this; //从对象的方法里得对象
return function(){
return that.user;
};
}
三、内存泄漏
因为IE的JScript对象和DOM对象使用不一样的垃圾收集方式,所以闭包在IE中会致使一些问题。就是内存泄漏的问题,
也就是没法销毁驻留在内存中的元素。如下代码有两个知识点尚未学习到,一个是DOM,一个是事件。
function box(){
var oDiv = document.getElementById('oDiv'); //oDiv用完以后一直驻留在内存
oDiv.onclick = function(){
alert(oDiv.innerHTML); //这里用oDiv致使内存泄漏
};
}
box();
那么在最后应该将oDiv解除引用来避免内存泄漏。
function box(){
var oDiv = document.getElementById('oDiv');
var text = oDiv.innerHTML;
oDiv.onclick = function(){
alert(text);
};
oDiv = null; //解除引用
}
PS:若是并无使用解除引用,那么须要等到浏览器关闭才得以释放。
四、模仿块级做用域
JavaScript没有块级做用域的概念。
function box(count){
for(var i = 0;i < count; i++){}
alert(i); //i不会由于离开了for块就失效
}
box(2);
function box(count){
for(var i = 0; i < count; i++){}
var i; //就算从新声明,也不会前面的值
alert(i);
}
box(2);
以上两个例子,说明JavaScript没有块级语句的做用域,if(){}for(){}等没有做用域,若是有,出了这个范围i就应该被销毁了。
就算从新声明同一个变量也不会改变它的值。JavaScript不会提醒你是否屡次声明了同一个变量;遇到这种状况,它只会对后续的声
明视而不见(若是初始化了,固然还会执行的)。使用模仿块级做用域可避免这个问题。
//模仿块级做用域(私有做用域)
(function(){
//这里是块级做用域
})();
//使用块级做用域(私有做用域)改写
functionbox(count){
(function(){
for(var i = 0; i < count; i++){}
})();
alert(i); //报错,没法访问
}
box(2);
使用了块级做用域(私有做用域)后,匿名函数中定义的任何变量,都会在执行结束时被销毁。这种技术常常在全局做用域中被用在函数外部,
从而限制向全局做用域中添加过多的变量和函数。通常来讲,咱们都应该尽量少向全局做用域中添加变量和函数。在大型项目中,
多人开发的时候,过多的全局变量和函数很容易致使命名冲突,引发灾难性的后果。若是采用块级做用域(私有做用域),
每一个开发者既可使用本身的变量,又没必要担忧搞乱全局做用域。
(function(){
var box = [1,2,3,4];
alert(box); //box出来就不认识了
})();
在全局做用域中使用块级做用域能够减小闭包占用的内存问题,由于没有指向匿名函数的引用。只要函数执行完毕,就能够当即销毁其做用域链了。
五、私有变量
JavaScript没有私有属性的概念;全部的对象属性都是公有的。不过,却有一个私有变量的概念。任何在函数中定义的变量,
均可以认为是私有变量,由于不能在函数的外部访问这些变量。
function box(){
var age = 100; //私有变量,外部没法访问
}
而经过函数内部建立一个闭包,那么闭包经过本身的做用域链也能够访问这些变量。而利用这一点,能够建立用于访问私有变量的公有方法。
functionBox(){
varage = 100; //私有变量
function run(){ //私有函数
return '运行中...';
}
this.get=function(){ //对外公共的特权方法
return age + run();
};
}
var box = new Box();
alert(box.get());
能够经过构造方法传参来访问私有变量。
function Person(value){
var user = value; //这句其实能够省略
this.getUser = function(){
return user;
};
this.setUser = function(value){
user = value;
};
}
可是对象的方法,在屡次调用的时候,会屡次建立。可使用静态私有变量来避免这个问题。
六、静态私有变量
经过块级做用域(私有做用域)中定义私有变量或函数,一样能够建立对外公共的特权方法。
(function(){
varage = 100;
function run(){
return '运行中...';
}
Box = function(){}; //构造方法
Box.prototype.go = function(){ //原型方法
return age + run();
};
})();
var box = new Box();
alert(box.go());
上面的对象声明,采用的是Box=function(){}而不是functionBox(){}由于若是用后面这种,就变成私有函数了,没法在全局访问到了,因此使用了前面这种。
(function(){
var user = '';
Person = function(value){
user = value;
};
Person.prototype.getUser = function(){
return user;
};
Person.prototype.setUser = function(value){
user = value;
}
})();
使用了prototype致使方法共享了,而user也就变成静态属性了。(所谓静态属性,即共享于不一样对象中的属性)。
七、模块模式
以前采用的都是构造函数的方式来建立私有变量和特权方法。那么对象字面量方式就采用模块模式来建立。
var box = { //字面量对象,也是单例对象
age:100, //这是公有属性,将要改为私有
run:function(){ //这时公有函数,将要改为私有
return '运行中...';
};
};
私有化变量和函数:
var box = function(){
var age = 100;
function run(){
return '运行中...';
}
return{ //直接返回对象
go:function(){
return age + run();
}
};
}();
上面的直接返回对象的例子,也能够这么写:
var box = function(){
var age = 100;
function run(){
return '运行中...';
}
var obj = { //建立字面量对象
go:function(){
return age + run();
}
};
return obj; //返回这个对象
}();
字面量的对象声明,其实在设计模式中能够看做是一种单例模式,所谓单例模式,就是永远保持对象的一个实例。
加强的模块模式,这种模式适合返回自定义对象,也就是构造函数。
function Desk(){};
var box = function(){
var age = 100;
function run(){
return '运行中...';
}
var desk = new Desk(); //能够实例化特定的对象
desk.go = function(){
return age + run();
};
return desk;
}();
alert(box.go());