javascript闭包

本文讲述Javascript的闭包。

引言(简单了解js闭包)

在讲述js闭包之前,我们先来说一个简单的例子:

var a = 1;
function fn(){
    var b = 2;
    console.log("a: " + a + ", b: " + b);
}
fn();    //a: 1, b: 2
console.log("a: " + a + ", b: " + b);    //a: 1, b: undefined

这个相信大家都可以理解,就是变量的作用域,a为定义的全局变量,b为函数fn内部定义的变量,在函数内部,可以获取到全局变量a的值和函数局部变量b的值。在函数外部,不能获取到局部变量b的值。但是,如果我确实想得到函数fn内部的b元素的值呢?

想一想,这个fn其实是一个方法,既然是个方法,那应该可以给这个方法设置一个返回值吧,那如果将这个函数的返回值设置为b元素,会发生什么情况呢?

function fn(){
    var b = 5;
    function fn2(){
        console.log(n);
    }
    return fn2;
}
var result = fn();
result();    //5

实践证明,这样是可以获取到b元素值的。没错,这就是最简单的js闭包。

正题(js闭包讲解)

js闭包概念:

官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

是不是有点看不懂?

其实,js闭包就是在外部可以访问函数内部的局部变量、参数或声明的其他内部函数。

可能你会说,那这个算不算闭包呢:

function f1(){
    var a = 1;

    f2(a);
}

function f2(m){
    console.log(m);
}

f1();    //1

那么,你觉得下边这个函数和上边的有什么区别呢:

function f1(){
    var a = 1;
    console.log(a);    
}
f1();    //1

这个函数和上边的函数其实并没有什么区别,完全可以合并为同一个函数。只不过有时候为了实现代码模块化或组件化,于是将通用的方法封装了起来,以便于方法的重用,所以它并不是闭包。

既然我们已经看到了比较简单的js闭包,那么我们再来加一点难度,看一下下面这段代码:

var name = '张三';
var Comp = {
    name : '小张',
    getName : function(){
        return this.name;
    }
}
console.log(Comp.getName());    //小张

这个应该比较容易理解,Comp是一个对象,包含一个name属性和一个名为getName的方法,因为getName方法是属于Comp对象的,所以这个this指向Comp对象。因此调用Comp对象中的getName方法,这时候this.name的值应该是”小张”。

再加深一点难度:

var name = '张三';
var Comp = {
    name : '小张',
    getName : function(){
        var here = this;
        return function(){
            return here.name;
        }
    }
}
console.log(Comp.getName()());    //小张

在这段代码中,定义了here作为getName()方法的局部变量,所以here指的对象并不是window对象,而是Comp对象。但是,由于返回值中有here,所以这个局部变量就一直存在内存中,当调用Comp.getName()()这个方法时,将here从内存中取出来,由于here指的就是Comp对象,所以返回的值就是Comp.name的值。

提升(this指向问题)

var name = '张三';
var Comp = {
    name : '小张',
    getName : function(){
        return function(){
            return this.name;
        }
    }
}
console.log(Comp.getName()());    //张三

为什么这个函数中的this.name返回的是”张三”呢?我们可以先试着运行一下这个

console.log(Comp.getName());    //function (){    return this.name;    }

这段代码输出的是一段字符串,而这段字符串是一个匿名函数,而且此时并没有执行这个方法,仅仅是返回了构成这个方法的字符串而已。然后调用这段字符串构成的方法,也就相当于调用window下的一个匿名函数。那这样说的话,是不是我可以将上边这个函数改成这个样子呢?

var name = '张三';
var Comp = {
    name : '小张',
    getName : function(){
        return noNameFun;
    }
}
var noNameFun = function(){
    return this.name;
}
console.log(Comp.getName()());    //张三

可以看出,这个函数确实输出的是”张三”,也就是说和上边这个函数是一样的。那我们就明白了,noNameFun()是属于window对象的,那么这个方法的this也属于window对象,所以this.name就代表的是”张三”。

闭包的优点

闭包的优点有两个:

  1. 可以读取函数内部的变量;
  2. 让这些变量的值始终保持在内存中。

补充(js闭包内存问题)

在我们的使用中,很多时候都会用到js闭包的概念,尤其是模块化开发。然而,js闭包虽然好用的,但是任何事物都有它的两面性,变量保持在内存中是优点,但也存在这样一个问题:

由于闭包会是函数中的变量一直存在于内存中,导致内存被占用,而如果这样的变量多了的话,会使内存消耗很大。所以,不能滥用闭包,否则会是网页性能变得很差,而且在IE中,有可能导致内存泄露。

什么是内存被占用,我举个例子来说一下:

function fn(){
    var n = 1;
    function add(){
        n += 1;
        console.log(n);
    }
    return add;
}
var result = fn();
result();    //2
result();    //3
result();    //4
result();    //5
result();    //6

也就是说,这个变量n是一直存在于内存中的,所以每次调用,n就会加1。

题外(全局变量和局部变量)

对于全局变量和局部变量,有些新手可能不太明白。

外部声明的变量属于全局变量,方法内部声明的变量属于局部变量。但是如果在方法内部没有用var来声明一个变量,那这个变量应该也是一个全局变量。

var n = 1;
function fn(){
    var m = 5;
    t = 10;
    console.log(m);
}

console.log(n);    //5
console.log(m);    //undefined
console.log(t);    //undefined
fn();            //5
console.log(m);    //undefined
console.log(t);    //10

为什么在执行fn()函数之前打印”t”这个全局变量会输出”undefined”呢?这个”t”到底是不是全局变量?

我们可以想一下,我现在声明了一个函数,但是我只是定义了这个函数,并没有调用它。那么这个函数内部的方法是不会执行的,也就是说,在我调用方法fn()之前,这个”t”是并没有被声明的,既然没有被声明,那就肯定会输出”undefined”。但是在调用了fn()函数之后,也就执行了函数内部的方法,即:声明了”t”变量。之后,就肯定可以获取到”t”这个全局变量啦。

对于函数里的方法也是一样的。

function fn(){
    var n = 1;
    nAdd = function(){
        n += 1;
    }
    function f(){
        console.log(n);
    }
    return f;
}
//nAdd();    //undefined
var result = fn();
result();    //1
nAdd();
result();    //2
nAdd();
result();    //3

本章节完!


转载请注明: MrLi javascript闭包

上一篇
常见前端知识点 常见前端知识点
本文讲述了部分前端面试经常遇到的问题。 HTML和CSS对WEB标准以及W3C的理解1.写代码的时候注意 标签闭合 标签小写 不能随意嵌套 2.提高搜索引擎搜到几率 mate中的name变量(其中keywords和description
2017-08-08
下一篇
kettle_errot_karafLifecycleListenter kettle_errot_karafLifecycleListenter
使用kettle 6.1 通过命令行批量执行作业的过程中,发现偶尔有作业执行时间会变慢几分钟,查看日志发现改作业开始就报了一个错 报错之后才会继续下面的作业,虽然不影响最终作业执行结果,但也延误了一些跑批时间。去pentaho论坛查了一
2017-07-12
目录