在一些类似C语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的(也就是我们不能在代码段外直接访问代码段内声明的变量),我们称之为块级作用域,然而,不同于这类型的编程语言,javascript是没有块级作用域。取而代之的,javascript使用的是块级作用域:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。

  在如下的所示的代码中,在不同位置定义了变量 i 、 j 和 k ,它们都在同一个作用域内——这三个变量在函数体内均是有定义的。  

function text(o){
    var i = 0;                                        // i 在整个函数体内均是由定义的
    if(typeof o == "object"){
        var j = 0;                                    // j 在函数体内是有定义的,不仅仅是这个代码段内
        for (var k = 0; k < 10; k++) {                // k 在函数体内是有定义的,不仅仅是在循环体内
            console.log(k);                           //输出数字 0 到 9
        };
        console.log(k);                               // k 已经定义了,输出 10
    }
    console.log(j);                                   // j 已经定义了,但可能没有初始化
}

  javascript的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。有意思的是,这意味着变量在声明之前甚至已经可用。javascript的这个特征被非正式地称为声明提前,即javascript函数里声明的所有变量(但不涉及赋值)都被“提前”至函数体的顶部,看一下如下代码:

 function f(){
     console.log(scope);            //输出"undefined",而不是"global"
     var scope = "local";           //变量在这里赋初始值,但变量本身在函数体任何地方均是有定义的
     console.log(scope);            //输出"local"
 }

  你可能会误以为函数中的第一行会输出"global",因为代码还没有执行到var语句声明局部变量的地方。其实不然,由于函数作用域的特性,局部函数在整个函数体始终是有定义的,也就是说,在函数体局部变量遮盖了全名全局变量。尽管如此,只有在程序执行到var语句的时候,局部变量才会被真正赋值。因此,上述过程等价于:将函数内的变量声明"提前"至函数体顶部,同时变量初始化留在原来的位置:

 function f(){
     var scope;                     //在函数顶部声明了局部变量
     console.log(scope);            //变量存在,但其值是"undefined"
     scope = "local";               //这里将其初始化并赋值
     console.log(scope);            //这里它具有了我们所期望的值
 }    

  接下来,谈谈javascript的作用域链的概念。在javascript犀牛这本书中,有一小段对作用域链的定义和介绍。

  javascript是基于词法作用域的语言:通过阅读包含变量定义在内的数行源码就能知道变量的作用域。全局变量在程序中始终都是有定义的。局部变量在声明它的函数体内以及其所在嵌套的函数内始终是有定义的。

  如果将一个局部变量看做是自定义实现的对象的属性的话,那么可以换个角度来解读变量作用域。每一段javascripe代码(全局代码或函数)都有一个与之关联的作用域链。这个作用域链是一个对象列表或者链表,这组对象定义了这段代码"作用域中"的变量。当javascript需要查找变量x的值的时候(这个过程称作"变量解析"),它会从链中的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性的值,如果第一个对象中不存在名为x的属性,javascript会继续查找链上的下一个对象。如果第二个对象依然没有名为x的属性,则会继续查找下一个对象,以此类推,如果作用域链上没有任何一个对象含有属性x,那么就认为这段代码的作用域链上不存在x,并最终抛出一个引用错误异常。

  在javascript的最顶层代码中(也就是不包含任何函数定义内的代码),作用域链由一个全局对象组成。在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。在一个嵌套的函数体内,作用域链上至少有三个对象。理解对象链的创建规则是非常重要的。当定义一个函数时,它实际上保存了一个作用域链。当调用这个函数时,它创建一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的"链"。对于嵌套函数来讲,事情变得更加有趣,每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的。外部函数在每次定义的时候都有微妙的差别——在每次调用外部函数时,内部函数的代码都是相同的,而且关联这段代码的作用域链也不相同。

  这段定义和介绍可能比较难理解,那么,我们先来看一段代码:

 name="lwy";                      //全局作用域中定义一个全局变量name,值为lwy
 function t(){
     var name="tlwy";             //函数 t 的作用域中定义一个局部变量name,值为tlwy
     function s(){
         var name="slwy";         //函数 t 的内嵌函数 s 的作用域中定义一个局部变量name,值为slwy
         console.log(name);       //输出name
     }
     function ss(){
         console.log(name);       //输出name
     }
     s();                         //调用执行函数s
     ss();                        //调用执行函数ss
 }
 t();                             //调用执行函数t

  当执行s时,将创建函数s的执行环境(调用对象),并将该对象置于链表开头,然后将函数t的调用对象链接在之后,最后是全局对象。

  作用域链为:s()->t()->window (函数 s 和函数 t 以及对象window中都能查找到变量name)

  然后从链表开头寻找变量name,很明显name是"slwy"。

  但执行ss()时,作用域链是: ss()->t()->window (除了函数 ss ,函数 t 和对象window都能找到变量name) ,所以name是”tlwy"

  那么,接下来我们来看看一个有意思的例子:

 <html>
 <head>
 <script type="text/javascript">
 function buttonInit(){
     for(var i=1;i<4;i++){
         var b=document.getElementById("button"+i);
         b.addEventListener("click",function(){ alert("Button"+i);},false);
     }
 }
 window.onload=buttonInit;
 </script>
 </head>
 <body>
 <button id="button1">Button1</button>
 <button id="button2">Button2</button>
 <button id="button3">Button3</button>
 </body>
 </html>

  文档加载完毕,给几个按钮注册点击事件。当我们点击按钮时,你会觉得每个按钮点击后都会弹出按钮内相对应的内容。

  然而不正确,三个按钮最终都会弹出:"Button4"。

  原因:当注册事件结束后,i的值为4,当点击按钮时,事件函数即function(){ alert("Button"+i);}这个匿名函数中没有i,根据作用域链(匿名函数->函数buttonInit->window),所以到buttonInit函数中找,此时i在循环结束过后的值为4,所以,不管你点击任何哪个按钮都会弹出”button4“。

javascript篇-----函数作用域,函数作用域链和声明提前的更多相关文章

  1. 初探JavaScript(四)——作用域链和声明提前

    前言:最近恰逢毕业季,千千万万的学生党开始步入社会,告别象牙塔似的学校生活.往往在人生的各个拐点的时候,情感丰富,感触颇深,各种对过去的美好的总结,对未来的展望.与此同时,也让诸多的老“园”工看完这些 ...

  2. 深入理解 JavaScript 变量的作用域和作用域链

    一个变量的作用域(scope)是程序源代码中定义这个变量的区域.简单的说,作用域就是变量与函数的可访问范围.全局变量拥有全局作用域,在JavaScript代码中的任何地方都有定义.局部变量是在函数体内 ...

  3. JavaScript 变量声明提前

    <JavaScript权威指南>中指出:JavaScript变量在声明之前已经可用,JavaScript的这个特性被非正式的称为声明提前(hoisting),即JavaScript函数中声 ...

  4. 我所理解的javascript中函数的作用域和作用域链

    本文为原创,转载请注明出处: cnzt       文章:cnzt-p 写在前面 一周木有更新了,今天终于攻克了自行车难关,非常开心,特意来一更~ (那些捂嘴偷笑的人我看到你们了快把嘴闭上我会假装没看 ...

  5. 一步步学习javascript基础篇(2):作用域和作用域链

    作用域和作用域链 js的语法用法非常的灵活,且稍不注意就踩坑.这集来分析下作用域和作用域链.我们且从几道题目入手,您可以试着在心里猜想着答案. 问题一. if (true) { var str = & ...

  6. JS函数作用域及作用域链理解

    从事web开发工作,尤其主要是做服务器端开发的,难免会对客户端语言JavaScript一些概念有些似懂非懂的,甚至仅停留在实现功能的层面上,接下来的文章,是记录我对JavaScript的一些概念的理解 ...

  7. js对象系列【二】深入理解js函数,详解作用域与作用域链。

    这次说一下对象具体的一个实例:函数,以及其对应的作用域与作用域链.简单的东西大家查下API就行了,这里我更多的是分享自己的理解与技巧.对于作用域和作用域链,相信绝大多数朋友看了我的分享都能基本理解,少 ...

  8. javaScript函数提升及作用域

    代码片段: var a = 1; function foo() { console.log(a); //输出为undefined if (!a) { var a = 2; } alert(a); }; ...

  9. 《你不知道的javascript》一、函数作用域和块作用域

    函数中的作用域 所谓函数作用域,就是属于这个函数的全部变量都可以在整个函数的范围内使用及复用. function foo(a) { var b=a; function bar(c){ var c=b* ...

随机推荐

  1. [问题2014S13] 复旦高等代数II(13级)每周一题(第十三教学周)

    [问题2014S13]  (1)  设 \(A\) 是数域 \(\mathbb{K}\) 上的 \(n\) 阶非异阵, 若存在主对角元全为 \(1\) 的下三角阵 \(L\in M_n(\mathbb ...

  2. git的一些常用方法

    1.撤销add但未commit的文件: git rm -r --cached path/file 2.git 撤销commit: 2.1).git log-显示提交的历史 commit ee50348 ...

  3. 可怜的js居然没有块级作用域

    js中在一个函数中定义一个for循环:for(var i=0;i<5;i++) 其中的i并不会随着for循环的结束就销毁,i会一直存在该函数中,这就是js和其他语言的区别,也就是js没有块级作用 ...

  4. PL/SQL之--触发器

    一.简介 触发器在数据库里以独立的对象进行存储,它与存储过程和函数不同的是,存储过程与函数需要用户显示调用才执行,而触发器是由一个事件来触发运行.oracle事件指的是对数据库的表或视图进行的inse ...

  5. eclipse luna maven失效的原因

    昨天发现单位里的eclipse中的maven直接不显示了,不能在 Windows-Preference 中显示maven 也不能新建maven工程,也不能maven-update,连STS(Sprin ...

  6. hdu 1728 bfs **

    简单bfs,记录好状态即可 #include<cstdio> #include<iostream> #include<algorithm> #include< ...

  7. Shortcut 常用快捷键

    多行注释: VS2010:   / NotePad++: Ctrl Q Xcode:    CMMND / 回到光标所在之前位置 VS2010: Ctrl +/- 关闭当前页: VS2010: 鼠标中 ...

  8. 添加favicon.ico网站文件

    <link rel="shortcut icon" type="image/x-icon" href="favicon.ico" me ...

  9. Mybatis学习——传递Map型参数

    Spring整合Mybatis调用 public boolean editItemSales(int i_id, int i_sales) { Map<String, Object> ma ...

  10. sencha touch 2 tabpanel中List的不显示问题,解决方案

    笔者在做sencha项目的时候碰到一个需求,就是"好友列表"中分为"未确认好友"和"已确认好友",两个都是一个list,自然想到的就是使用t ...