对js闭包的理解

在了解闭包之前,需先了解三个概念
1.变量对象(variable object)
2.执行环境(execution contexts)

3.作用域链(scope chain)


1.变量对象(variable object)

ECMAScript变量可能包含两种不同数据类型的值:
1.基本类型值:指的是简单的数据段
2.引用类型值:指那些可能由多个值构成的对象

可以给引用类型值添加属性,但是不可以给基本类型值添加属性。
例如:
var person = new Object(); var name = “Tom”;
person.name = “Tom”; name.age = 27;

alert(person.name);//”Tom” alert(name.age)//undefined

2.执行环境(execution contexts)

(1)执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。
(2)每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
(3)全局环境是最外围的一个执行环境,在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。

(4)每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。


3.作用域链(scope chain)

(1)当代码在一个环境中执行时,会创建变量对象的一个作用域链
(2)作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。

(3)作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境,一直延续到全局执行环境。全局执行环境的变量对象始终都是作用域链中的最后一个对象。

下面来看一段代码理解作用域链和执行环境

//全局环境
var color = “blue”;
function changeColor(){//局部环境1
    var anotherColor = “red”;
    function swapColors(){//局部环境2	
        var tempColor = anotherColor;
	anotherColor = color;
	color = tempColor;
	//这里可以访问color、anotherColor和tempColor
    } 
	//这里可以访问color、anotherColor,不能访问tempColor
    swapColor();
}
//这里只能访问color
changeColor();

附加:闭包与变量

作用域链的这种配置机制引起了一个值得注意的副作用,即闭包只能取得含函数中任何变量的最后一个值。因为闭包所保存的是整个变量对象,而不是某个特殊变量。下面来看一个例子:

function createFunctions (){
    var result = new Array();
    for (var i=0;i<10;i++){
        result[i] = function (){
            return i;
        };
    }
    return result;
}

这段代码输出的是一个函数数组。为什么呢?单从表面上来看,我们会觉得每个函数(return[i])都会返回自己的索引值,即return[0]这个函数返回0,return[1]返回1,以此类推。但是实际上,每个函数都返回10。因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。当createFunctions()函数返回后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是10(意思是return[i]里的i和console.log(i)里的i是不一样的)。但是可以通过创建另一个匿名函数强制让闭包的行为符合预期,代码如下:

function createFunctions (){
    var result = new Array();
    for (var i=0;i<10;i++){
        result[i] = function (num){
            return function(){
                return num;
            };
        }(i);
    }
    return result;
}

修改之后,每个函数就会返回各自不同的值了,在这个函数中,没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋值给数组。这里的匿名函数有一个参数num,也就是最终的函数要返回的值。在调用每个匿名函数时,传入了变量i。由于匿名函数参数是按值传递的,所以将变量i的当前值复制给了参数num。而在这个匿名函数的内部,又创建并返回了一个访问num的闭包。故result数组里的每个函数都有自己的num变量副本,因此就可以返回各自不同的值了。


下面再来看对闭包的理解
闭包是指有权访问另一个函数作用域中的变量的函数。(js高级程序设计第三版)

官网(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures)给出的是:闭包是函数和声明该函数的词法环境的组合。

词法环境是一个用于定义特定变量和函数标识符在ECMAScript代码的词法嵌套结构上关联关系的规范类型。一个词法环境由一个环境记录项和可能为空的外部词法环境引用构成。

创建闭包的常见方式,就是在一个函数内部创建另一个函数。
虽然在字面容易理解闭包的概念,但很多时候却搞不明白匿名函数和闭包的概念

下面附上一个链接,是本人目前看过博客之中理解它们最好的一个,网址为:点击打开链接

那就先看看匿名函数:

函数是js中最灵活的一种对象,而匿名函数就是没有函数名的函数。
匿名函数最大的用途是创建闭包(这是JavaScript语言的特性之一),并且还可以构建命名空间,以减少全局变量的使用,同时还可以模仿块级作用域。
函数的定义想必大家都知道了,大致分为三种:
最常见的一种,也是项目中用的最多的一种:
function  double(x){
return 2*x;

}

第二种:
var double = new Function(x){ return 2*x ; }
‘=’右边的函数就是一个匿名函数,创建完毕函数后,有将该函数赋给了变量double。
由于第三种不建议使用,这里不再赘述,请查阅链接。


匿名函数的创建:
第一种也就是上面的一种,也是最常用的方式之一;
第二种方式:
(function(x,y){
alert(x + y);
})(1,2);

这里创建了一个匿名函数(第一个括号内),第二个括号用于调用该匿名函数,并传入参数。


再回头理解闭包

闭包的英文单词是closure,这是JavaScript中非常重要的一部分知识,因为使用闭包可以大大减少我们的代码量,使我们的代码看上去更加清晰等等,总之功能十分强大。

闭包的含义:闭包说白了就是函数的嵌套,内层的函数可以使用外层函数的所有变量,即使外层函数已经执行完毕(这点涉及JavaScript作用域链)。

function checkClosure(){
    var str = 'rain-man';
    setTimeout(
        function(){ alert(str); } //这是一个匿名函数
    , 2000);
}
checkClosure();

这个例子看上去十分的简单,仔细分析下它的执行过程还是有许多知识点的:checkClosure函数的执行是瞬间的(也许用时只是0.00001毫秒),在checkClosure的函数体内创建了一个变量str,在checkClosure执行完毕之后str并没有被释放,这是因为setTimeout内的匿名函数存在这对str的引用。待到2秒后函数体内的匿名函数被执行完毕,str才被释放。

学习完闭包之后,可以尝试着做一些题,因为在笔试之中考察闭包和原型链的题目基本都会出。

第一题:
var outer = null;
(function(){
var one = 1;
function inner (){
one += 1;
alert(one);
}
outer = inner;
})();
outer();
outer();
outer();

分别输出什么?

这段代码中的变量one是一个局部变量(因为它被定义在一个函数之内),因此外部是不可以访问的。但是这里我们创建了inner函数,inner函数是可以访问变量one的;又将全局变量outer引用了inner,所以三次调用outer会弹出递增的结果,即分别弹出2,3,4。

注意闭包允许内层函数引用父函数中的变量,但是该变量是最终值
/**
 * 
 * 
    *
  • one
  • *
  • two
  • *
  • three
  • *
  • one
  • *
*/ var lists = document.getElementsByTagName('li'); for(var i = 0 , len = lists.length ; i < len ; i++){ lists[ i ].onmouseover = function(){ alert(i); }; }

你会发现当鼠标移过每一个


解决方法一:

var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
    (function(index){
        lists[ index ].onmouseover = function(){
            alert(index);    
        };                    
    })(i);
}

解决方法二:

var lists = document.getElementsByTagName('li');
for(var i = 0, len = lists.length; i < len; i++){
    lists[ i ].$$index = i;    //通过在Dom元素上绑定$$index属性记录下标
    lists[ i ].onmouseover = function(){
        alert(this.$$index);    
    };
}

解决方法三:

function eventListener(list, index){
    list.onmouseover = function(){
        alert(index);
    };
}
var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
    eventListener(lists[ i ] , i);
}





你可能感兴趣的:(js)