Javascript的作用域、作用域链以及闭包

一、javascript中的作用域

①全局变量-函数体外部进行声明

②局部变量-函数体内部进行声明

1)函数级作用域

javascript语言中局部变量不同于C#、Java等高级语言,在这些高级语言内部,采用的块级作用域中会声明新的变量,这些变量不会影响到外部作用域。

而javascript则采用的是函数级作用域,也就是说js创建作用域的单位是函数。

例如:

在C#当中我们写如下代码:

static void Main(string[] args)
{
     for (var x = 1; x < 10; x++)
     {
           Console.WriteLine(x.ToString());
     }
     Console.WriteLine(x.ToString());
}

上面代码会出现如下的编译错误:

The name ‘x‘ does not exist in the current context

同样在javascript当中写如下代码:

<script>
    function main() {
        for (var x = 1; x < 10; x++) {
             console.log(x.toString());
         }
         console.log(x.toString());
     }
     main();
</script>

输出结果如下:

[Web浏览器] "1"

[Web浏览器] "2"

[Web浏览器] "3"

[Web浏览器] "4"

[Web浏览器] "5"

[Web浏览器] "6"

[Web浏览器] "7"

[Web浏览器] "8"

[Web浏览器] "9"

[Web浏览器] "10"

这就说明了,“块级作用域”和“函数级作用域”的区别,块级作用域当离开作用域后,外部就不能用了,就像上面的C#例子,"x"离开for循环后就不能用了,但是javascript中不一样,它还可以进行访问。

 2)变量提升

再看javascript中作用域的一个特性,例子如下:

function func(){
    console.log(x);
    var x = 1;
    console.log(x);
}
func();

输出结果:

[Web浏览器] "undefined"

[Web浏览器] "1"

如上面的结果:有意思的是,为什么第一个输出是“undefined”呢?这就涉及到javascript中的“变量提升”,其实我感觉叫“声明提升”更好,因为这个机制就是把变量的声明提前到函数的前面。并不会把值也同样提升,也就是为什么第一次没有输出“1”的原因。

上面的代码就相当于:

function func(){
    var x;
    console.log(x);
    var x = 1;
    console.log(x);
}
func();

3)函数内部用不用“var”对程序的影响

这是个值得注意的地方:

var x = 1;
function addVar(){
    var x = 2;
    console.log(x);
}
addVar();
console.log(x);

输出:

[Web浏览器] "2"

[Web浏览器] "1"

当在函数内部去掉var之后,再执行:

 var x = 1;
 function delVar(){
       x = 2;
       console.log(x);
  }
 delVar();
 console.log(x);

[Web浏览器] "2"

[Web浏览器] "2"

上面的例子说明了,在函数内部如果在声明变量没有使用var,那么声明的变量就是全局变量。

二、javascript的作用域链

先看如下的代码:

var name="Global";
function t(){
        var name="t_Local";
        
        function s1(){
            var name="s1_Local";
                console.log(name);
        }
        function s2(){
                console.log(name);
        }
        s1();
        s2();
}
t(); 

输出结果:

[Web浏览器] "s1_Local"

[Web浏览器] "t_Local"

那么就有几个问题:

1.为什么第二次输出的不是s1_Local?

2.为什么不是Global?

解决这个两个问题就在于作用域链…

下面就解析一下这个过程,

例如当上面程序创建完成的时候,会形成上图中的链状结构(假想的),所以每次调用s1()函数的时候,console.log(name);先会在他自己的作用域中寻找name这个变量,这里它找到name=“s1_Local”,所以就输出了s1_Local,而每次调用s2()的时候,它和s1()函数过程一样,只不过在自身的作用域没有找到,所以向上层查找,在t()函数中找到了,于是就输出了"t_Local"。

同样如果我们可以验证一下,如果把t中的name删除,可以看看输出是不是“Global”

结果如下:

[Web浏览器] "s1_Local"

[Web浏览器] "Global"

当然具体每一个作用域直接是如何连接的,请参考

http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html

其中也说明了为什么JS当中应该尽量减少with关键字的使用。

三、闭包

了解了作用域和作用域链的概念之后,再去理解闭包就相对容易了。

1.闭包第一个作用

还是先看例子:

function s1() {
    var x = 123;
    return s2();
}

function s2() {
    return x;
}

alert(s1());

这里我想弹出x的值,但是却发生了错误 "Uncaught ReferenceError: x is not defined",根据作用域链的知识,了解到因为s2中没有x的定义,并且向上找全局也没有x定义,所以就报错了。也就是说s1和s2不能够共享x的值。

那么问题来了,我想要访问s1当中的x,怎么弄?

修改代码如下:

function s1() {
    var x = 123;
    return function(){
        return x;
    };
}
    
var test = s1();
console.log(test());

结果为:

[Web浏览器] "123"

解释:因为function本质也是数据,所以它和x的作用域相同,可以访问x。这样就相当于对外部开放了一个可以访问内部变量的接口。

还可以怎么玩,稍微修改一下代码

var func;
function f(){
var x=‘123‘;
func=function(){
    return x;
    };
}
f();
alert(func());

定义一个全局的变量,然后在函数内部让其等于一个函数,这样就可以通过这个全局变量来进行访问x了。

综上:闭包是啥?闭包就相当于函数外部和内部的桥梁,通过闭包可以在外部访问函数内部的变量。这也是闭包的第一个作用。

2.闭包的第二个作用

先看代码:

function f1(){
    var n=1;
    add=function(){
        n+=1;
    };
    function f2(){
        console.log(n);
        return ‘输出完成‘;
    }
    return f2;
}
var res=f1();
console.log(res());
add();
console.log(res());

输出结果:

[Web浏览器] "1"

[Web浏览器] "输出完成"

[Web浏览器] "2"

[Web浏览器] "输出完成"

问题为什么第二次输出的结果n变成了2,没有被清除?

我的理解:res是一个全局变量,一直驻留在内存当中,它就相当于f2函数,f2函数又要依赖于f1函数,所以f1也驻留在内存当中,保证不被GC所回收,每一次调用add函数的时候,就相当于把f1中的n重新赋值了,这样n的值就变化了。这也是闭包的第二个作用,可以让变量的值始终保存在内存当中

3.闭包的应用

①可以做访问控制(相当于C#当中的属性)

//定义两个变量,用于存储取值和存值
var get,set;
//定义一个自调用函数,设定set和get方法
(function(){
    //设定x的默认值
    var x = 0;
    set = function(n){
        x = n;
    }
    get = function(){
        return x;
    }
})();

console.log(get());
set(5);
console.log(get());

输出结果:

[Web浏览器] "0"

[Web浏览器] "5"

②可以用做迭代器

//定义一个函数,里面使用了闭包
function foo(myArray){
    var i=0;
    //闭包迭代器
    next=function(){
        //每次返回数组的当前值,下标+1
        return myArray[i++];
    }
}
//调用foo,参数为一个数组
foo([‘a‘,‘b‘,‘c‘,‘d‘]);
//每次调用next都会打印数组的一个值
console.log(next());
console.log(next());
console.log(next());
console.log(next());

输出结果:

[Web浏览器] "a"

[Web浏览器] "b"

[Web浏览器] "c"

[Web浏览器] "d"

③闭包在循环中的使用

例1
function f(){
    var a=[];
    var i;
    for(i=0;i<3;i++){
        a[i]=function(){
            return i;
        };
    }
    return a;
}
var test=f();
console.log(test[0]());
console.log(test[1]());
console.log(test[2]());

输出结果:

[Web浏览器] "3"

[Web浏览器] "3"

[Web浏览器] "3"

为什么结果不是0、1、2?

这里我们使用了闭包,每次循环都指向了同一个局部变量i,但是闭包不会记录每一次循环的值,只保存了变量的引用地址,所以当我们在调用test[0]()、test[1]()、test[2]()的时候都返回的是for循环最后的值,也就是3的时候跳出了循环。

例2:我想输出0,1,2怎么搞?

function f(){
    var a=[];
    var i;
    for(i=0;i<3;i++){
        a[i]=(function(x){
            return function(){
                return x;
            }
        })(i);
    }
    return a;
}
var test=f();
console.log(test[0]());
console.log(test[1]());
console.log(test[2]());

结果:

[Web浏览器] "0"

[Web浏览器] "1"

[Web浏览器] "2"

关于这个为什么和上面不一样,我在知乎上找到了一篇文章,总结的非常好,

★★★★★引用自:http://www.zhihu.com/question/33468703

时间: 2024-10-15 01:37:46

Javascript的作用域、作用域链以及闭包的相关文章

JavaScript中的作用域链和闭包

JavaScript中的作用域链和闭包 2012-06-29 11:41 1878人阅读 评论(46) 收藏 举报 JavaScript中出现了一个以前没学过的概念——闭包.何为闭包?从表面理解即封闭的包,与作用域有关.所以,说闭包以前先说说作用域. 作用域(scope) 通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突. 全局作用域(Global Scope) 在代码中任何地方都能访问到的对象拥

JavaScript this 局部变量全局变量 作用域 作用域链 闭包

从阮老师博客的一道测试题说起: 代码段一: var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); 代码段二: var name = "The Window"; var object

个人理解的javascript作用域链与闭包

闭包引入的前提个人理解是为从外部读取局部变量,正常情况下,这是办不到的.简单的闭包举例如下: 1 function f1(){ 2 3 n=100; 4 5 function f2(){ 6 alert(n); 7 } 8 9 return f2; 10 11 } 12 13 var result=f1(); 14 15 result(); // 100 代码中的f2函数,就是闭包. 1 function f1(){ 2 3 var n=100; 4 5 nAdd=function(){n+=1

JavaScript中的作用域 、作用域链和闭包

JavaScript中作用,作用域链和闭包详解 一.作用域在js中有全局变量和局部变量之分:比如var a = 1;function sum(){var b=1console.log(b) //1console.log(a) //2 }sum()console.log(a) //3console.log(b) //4 例子中 a 是全局变量,b是局部变量(定义在函数内部,只能在函数内部访问)所以第1行正确 函数内部也能访问全局变量 a所以第2行也能正确 第三行也正确.第4行有外部不能访问内部变量

(第四天)作用域链、闭包

前言 JavaScript是基于词法作用域的语言:通过阅读包含变量定义在内的数行源码就能知道变量的作用域.全局变量在程序中始终都是有定义的.局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的. 如果将一个局部变量看做是自定义实现的对象的属性的话,那么可以换个角度来解读变量作用域.每一段JavaScript代码(全局代码或函数)都有一个与之关联的作用域链(scope chain).这个作用域链是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量.当JavaScript需要查找

作用域、原型链、闭包等概念理解

1.执行环境及作用域 (1).执行环境: 定义了变量或函数有权访问的其它数据,决定了它们的各自行为.每个执行环境都有一个与之关联的变量对象(variable object, VO),执行环境中定义的所有变量和函数都会保存在这个对象中,解析器在处理数据的时候就会访问这个内部对象. 全局执行环境是最外层的一个执行环境,在web浏览器中全局执行环境是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的.每个函数都有自己的执行环境,当执行流进入一个函数的时候,函数的环境会被

JavaScript变量作用域(Variable Scope)和闭包(closure)的基础知识

在这篇文章中,我会试图讲解JavaScript变量的作用域和声明提升,以及许多隐隐藏的陷阱.为了确保我们不会碰到不可预见的问题,我们必须真正理解这些概念. 基本定义 作用范围是个“木桶”,里面装着变量.变量可以是局部或者全局性的,但在子范围中定义的变量是可以访问父范围的,这一点可能会造成一些困扰. 在JavaScript中使用"var"关键字声明变量.一旦在父范围宣声明,就会作为各自子范围的一部分.即在本地范围内有效,但本地定义的变量不可在全局范围内访问. 让我们来看一个例子.执行下面

Javascript 进阶 作用域 作用域链

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/25076713 一直觉得Js很强大,由于长期不写js代码,最近刚好温故温故. 1.Javascript没有代码块作用域的概念,局部作用域是针对函数来说的. function fun() { for( var i = 0 ; i < 10 ; i++) {} //如果在Java中i此时应当属于未声明的变量,但是Js中i的作用域依然存在 console.log(i);//10 if(

从作用域链谈闭包

闭包(closure)是Javascript语言的一个难点,也是它的特色.非常多高级应用都要依靠闭包实现. 神马是闭包 关于闭包的概念,是婆说婆有理. 因而,我就翻阅了红皮书(p178)上对于闭包的陈述: 闭包是指有权訪问另外一个函数作用域中的变量的函数 这概念有点绕,拆分一下.从概念上说,闭包有两个特点: 1.函数 2.能訪问另外一个函数作用域中的变量 在ES 6之前,Javascript仅仅有函数作用域的概念.没有块级作用域(但catch捕获的异常 仅仅能在catch块中訪问)的概念(IIF