JavaScript中的闭包及应用场景介绍
对于工程师来讲,闭包是一个很难弄懂而且十分难征服的一个概念!因为闭包的生成不仅仅与变量的作用域相关而且与变量的生命周期也有着密切的关系。最后我可以肯定的告诉你,闭包在实际开发的过程中应用十分广泛,所以你必须要掌握它。
先来看一下关于闭包的定义:闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
//通过闭包可以获得函数fn的局部变量user
function fn(){
var user='zhang';
return function(){
return user;
}//通过匿名函数返回局部变量user
}
console.log(fn()());//zhang 通过box()()来直接调用匿名函数返回值
var b=fn();
console.log(b());//zhang 另一种调用匿名函数返回值
通过闭包可以实现函数内的局部变量的累加:
function fn(){
var num=100;
return function(){
num++;
return num;
}
}
var b=fn();//获得函数
//你会发现局部变量num并没有在内存当中消失
console.log(b());//调用匿名函数
console.log(b());//第二次调用匿名函数,实现累加
由于在闭包所在的作用域返回的局部变量不会被销毁,所以会占用内存。过度的使用闭包会迫使性能下降,因此建议大家在有必要的情况下再使用闭包。
作用域链的机制会导致一个问题,在循环中里的匿名函数取得的任何变量都是最后一个值
function fn(){
var arr=[];
//i为fn函数中的局部变量。
for(var i=0;i<3;i++){
arr.push(function(){
return i;
});
}
return arr;
}
var b=fn();
for(var i=0;i<b.length;i++){
console.log(b[i]());//3
}
以上示例输出的结果均是3,也就是循环结束后i的值。这是因为在for循环的过程当中,数组中的匿名函数并没有自我执行。当在调用匿名函数的时候,通过闭包获得的i已经是3了,所以每次输出的都是3。
如果想要输出的结果为0,1,2可以做如下的调整:
function fn(){
var arr=[];
for(var i=0;i<3;i++){
arr.push((function(num){
//将立即执行函数返回的匿名函数放到数组中。
//因为num为函数的参数,因此有自己独立的作用域
return function(){
return num;
};
})(i));
}
return arr;
}
var b=fn();
for(var i=0;i<b.length;i++){
console.log(b[i]());//0,1,2
}
通过匿名函数的立即执行,将立即执行后返回的函数直接赋值给数组arr。每次循环即将i的值传递给num,又因为num在函数中,所以有自己的独立作用域,因此num得到的值为每次循环传递进来的i值,即0,1,2
接下来看一下关于闭包当中的this对象:
this对象指的是什么,这个要看函数所运行的环境。如果函数在全局范围内调用 ,函数内的this指向的是window对象。对象中的方法,通过闭包如果运行的环境为window时,则this为window。因为闭包并不是该对象的方法。
var color="red";
function fn(){
return this.color;
}
var obj={
color:"yellow",
fn:function(){
return function(){//返回匿名函数
return this.color;
}
}
}
console.log(fn());//red 在外部直接调用this为window
var b=obj.fn();//b为window下的变量,获得的值为obj对象下的fn方法返回的匿名函数
console.log(b());//red 因为是在window环境下运行,所以this指缶的是window
//可以通过call或apply改变函数内的this指向
console.log(b.call(obj));//yellow
console.log(b.apply(obj));//yellow
console.log(fn.call(obj));//yellow
通过变量可以获得上一个作用域中的this指向
var color="red";
function fn(){
return this.color;
}
var obj={
color:"yellow",
fn:function(){
var _this=this;//将this赋值给变量_this
return function(){
return _this.color;//通过_this获得上一个作用域中的this指向
}
}
}
console.log(fn());//red
var b=obj.fn();
console.log(b());//yellow
可以通过构造方法传参来访问私有变量
function desk(){
var str="";//局部变量str,默认值为""
this.getstr=function(){
return str;
}
this.setstr=function(value){
str=value;
};
}
var desk=new desk();
//为构造函数的局部变量写入值。
desk.setstr("zhangpeiyue");
//获取构造函数的局部变量
console.log(desk.getstr());//zhangpeiyue
闭包常见的作用
1、模拟块级作用域(匿名自执行函数)
if(){}for(){}等没有作用域,所以在其块内声明的变量,在外部是可以使用的。
//javascript没有块级作用域的概念
function fn(num){
for(var i=0;i<num;i++){}
console.log(i);//在for外部i不会失败
}
fn(2);
if(true){
var a=13;
}
console.log(a);//在if定义的变量在外部可以访问
通过匿名自执行函数可以模拟块级作用域
(function(){
//i在外部就不认识啦
for(var i=0;i<count;i++){}
})();
console.log(i);//报错,无法访问
由于外部无法访问自执行函数内的变量,因此在函数执行完后会立刻被销毁,在外部无法访问。从而可有效避免在多人开发时由于全局变量过多而造成的命名冲突问题。另外由于作用域链的机制,局部变量要比全局变量的访问速度更快,可以提升程序的运行速度!
2、对结果进行缓存
写一个用于实现所有参数和的函数:
var fn=function(){
var sum=0;
for(var i=0;i<arguments.length;i++){
sum+=arguments[i];
}
return sum;
}
console.log(fn(1,2));//3
以上函数接收一些number类型的参数,并返回这些参数之和。由于每次传递的参数相同,所以返回的结果是一样的。这样势必会造成了一种浪费,在此时咱们可以通过缓存机制来提高这个函数的性能。
var fn=(function(){
var cache={}//将结果缓存到该对象中
return function(){
var str=json.stringify(arguments);
if(cache[str]){//判断缓存中是否存在传递过来的参数,存在直接返回结果,无需计算
return cache[str];
}else{//进行计算并返回结果
var sum=0;
for(var i=0;i<arguments.length;i++){
sum+=arguments[i];
}
return cache[str]=sum;
}
}
})()
上面的示例将计算后的结果缓存到局部变量cache当中,在调用这个函数时,先在缓存中查找,如果找不到,则进行计算,然后将结果放到缓存中并返回,如果找到了,直接返回查找到的值。