JS函数表达式 -- 闭包

闭包是指有权访问另一个函数作用域中变量的函数。 创建闭包的常见方式,就是在一个函数内部创建另一个函数。本质上讲,闭包就是讲函数内部和函数外部连接起来的一座桥梁。

function a(){
    var i = 0;
    function b(){
        alert(++i);
    }
    return b;
}
var c = a();
c(); //1

在函数a 中嵌套了函数b,并将函数b返回。

在执行完 var c = a() 后,变量c实际上指向了函数b,再执行c()后就会弹出一个窗口显示i的值。

当函数a的内部函数b被函数a外的一个变量引用时,就创建了一个闭包。

闭包主要涉及到js的几个其他的特性: 作用域链,垃圾(内存)回收机制,函数嵌套

function compare(value1, value2){
    if(value1 > value2){
        return 1;
    }else if(value1 < value2){
        return -1;
    }else{
        return 0;
    }
}
var result = compare(5,10);

1. 作用域链:

  作用域链是在函数定义时创建的,当函数需要查询一个变量的值的时候,js解析器会去作用域链中查找。在上述例子中,查找i时,会先在函数b中找,找到则返回,没找到继续在上一级的链即函数a中查找,如果还没找到,再到全局链中找,如果全局链中也没有,则返回undefined。

  在创建函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。  当调用函数时,会为函数创建一个执行环境,然后通过复制函数[[Scope]]属性中的对象构建起执行环境的作用域链。

  在上面的例子中,对于compare()函数的执行环境而言,其作用域中包含两个变量对象: 本地活动对象和全局变量对象。作用域链本质上是一个指向变量对象的指针表,它只引用但不实际包含变量对象。

  在函数中访问一个变量时,会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域。但闭包的情况又有所不同。

2.垃圾回收机制:

一个函数在执行开始的时候,会给其中定义的变量划分内存空间,用来保存这些变量。等函数执行完毕后,这些变量的内存就会被回收。下次再执行时,又会重新分配。

如果一个函数的内部嵌套了另外的一个函数,内部函数有可能在外部被调用到,并且这个内部函数使用了外部函数的某些变量。

这种情况下,js解释器在遇到函数定义的时候,会自动把函数和它可能用到的变量(包括本地变量、父级和祖父级函数的变量)一起保存下来,也就是构建一个闭包。

这些变量将不会被内存回收器回收。只有当内部的函数不可能被调用以后,才会销毁这个闭包。

3.闭包与变量:

  闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量。

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

createFunctions();
alert(result[0]());    //10
alert(result[9]());    //10

这个函数会返回一个函数数组,表面上看,似乎每个函数都应返回自己的索引值,但实际中,每个函数都返回10。

因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以他们引用的都是同一个变量 i。当createFunctions()函数返回后,变量i的值时10,

此时,每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部 i 的值都是10。

我们可以创建另一个匿名函数强制让闭包的行为符合预期。

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

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

4.关于this对象:

  在闭包中使用this对象也会导致一些问题。this对象是在运行时基于函数的执行环境绑定的: 在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于当前对象。 不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window。但有时候,由于编写闭包的方式不同,这一点可能不会那么明显:

var name = "The Window";
var object = {
    name: "My Object",

    getNameFunc: function(){
        return function(){
            this.name;
        };
    }
};
alert(object.getNameFunc()());    //The Window

以上代码先创建了一个全局变量name,又创建了一个包含name属性的对象。这个对象还包含一个方法 -- getNameFunc(), 它返回一个匿名函数,而匿名函数又返回this.name。由于getNameFunc()返回一个函数,因此调用object.getNameFunc()()就会立即调用它返回的函数,结果就是返回一个字符串。然而这个结果是全局name变量的值"The Window"。

  每个函数在被调用时,其活动对象都会自动取得两个特殊变量: this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。 不过,把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。

var name = "The Window";
var object = {
    name: "My Object",
    getNameFunc: function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
};
alert(object.getNameFunc()());    //My Object

在定义匿名函数之前,把this对象赋值给了一个名叫that的变量。而在定义了闭包之后,闭包也可以访问这个变量。即使在函数返回后,that也仍然引用着object,所以调用object.getNameFunc()()就返回了"My Object"。

时间: 2024-08-29 05:03:07

JS函数表达式 -- 闭包的相关文章

js函数表达式

定义函数的方式 定义函数表达式的方法有两种,一种是函数声明,另一种是函数表达式. 函数声明的方式,关于函数声明的方式,它的一个重要的特性就是函数声明提升(function declaration hoisting),意思是在执行代码之前会先读取函数声明.这就意味着可以把函数声明放在调用它的语句后面,like this 1 sayHi();//声明函数(function declaration hoisting) 2 function sayHi(){ 3 alert("Hi!"); 4

js函数表达式和函数声明的区别

我们已经知道,在任意代码片段外部添加包装函数,可以将内部的变量和函数定义"隐 藏"起来,外部作用域无法访问包装函数内部的任何内容. 例如: var a = 2; function foo() { // <-- 添加这一行 var a = 3; console.log( a ); // 3 } // <-- 以及这一行 foo(); // <-- 以及这一行 console.log( a ); // 2 虽然这种技术可以解决一些问题,但是它并不理想,因为会导致一些额外的

JS函数表达式 -- 模仿块级作用域

JavaScript没有块级作用域的概念.这意味着在语句中定义的变量,实际上是在包含函数中而非语句中创建的. 1 function outputNumbers(count){ 2 for(var i=0; i<count; i++){ 3 alert("In: " + i); 4 } 5 var i; 6 alert("Out: " + i); 7 } 8 outputNumbers(2); //In: 0 In: 1 Out:2 在这个函数中定义了一个for

js 函数表达式

1.定义函数: (1)函数声明 function 函数名(){}//指定函数名的方式 它的一个重要特征是函数声明提升,在执行代码之前会先读取函数声明. (2) 函数表达式 var FunctionName = function(arg0, arg1, arg2){}//匿名函数 在使用之前必须先赋值.

js自执行函数表达式

// 下面2个括弧()都会立即执行 (function () { /* code */ } ()); // 推荐使用这个(function () { /* code */ })(); // 但是这个也是可以用的 和普通function执行的时候传参数一样,自执行的函数表达式也可以这么传参,因为闭包直接可以引用传入的这些参数,利用这些被lock住的传入参数,自执行函数表达式可以有效地保存状态. // 这个代码是错误的,因为变量i从来就没背locked住// 相反,当循环执行以后,我们在点击的时候i

js系列-3 js作用域与闭包

4,函数作用域中可用arguments来获取函数实参,arguments虽然可以通过下标访问,但它不是数组,原型不是Array.prototype. 它是实参的一个副本,通过arguments可模拟方法的重载.    function add(){        if(arguments.length == 1){           alert(1);        }        if(arguments.length == 2){           alert(2);        }

深入理解JavaScript系列(4):立即调用的函数表达式

前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行. 在详细了解这个之前,我们来谈了解一下“自执行”这个叫法,本文对这个功能的叫法也不一定完全对,主要是看个人如何理解,因为有的人说立即调用,有的人说自动执行,所以你完全可以按照你自己的理解来取一个名字,不过我听很多人都叫它为“自执行”,但作者后面说了很多,来说服大家称呼为“立即调用的函数表达式”. 本文英文原文地址:http://benalman.com/news/2010/11/immedia

&lt;深入理解JavaScript&gt;学习笔记(4)_立即调用的函数表达式

前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行.(小菜理解:的确看到好多,之前都不知道这是自执行匿名函数) 在详细了解这个之前,我们来谈了解一下“自执行”这个叫法,本文对这个功能的叫法也不一定完全对,主要是看个人如何理解,因为有的人说立即调用,有的人说自动执行,所以你完全可以按照你自己的理解来取一个名字,不过我听很多人都叫它为“自执行”,但作者后面说了很多,来说服大家称呼为“立即调用的函数表达式”.(小菜理解:不管叫什么,大概意思都是一样的

重操JS旧业第九弹:函数表达式

函数表达式,什么概念,表达式中的函数表达式. 1 函数申明 function 函数名([函数参数]){ //函数体 } js中无论像这样的显示函数什么放在调用之前还是调用之后,都不影响使用,因为js解释引擎会将函数声明提前化,这点很好理解: 2 函数表达式 var functionV=function 函数名([函数参数]){ //函数体 }: 这种方式的一个变量指向了一个函数,仅此而已.js引擎只会把他当作一个函数类型的变量来处理,仅此而已. 3 递归调用 我们知道函数名仅仅是指向函数的指针,