在一些类似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. 微信小程序开发—快速掌握组件及API的方法

    微信小程序框架为开发者提供了一系列的组件和API接口. 组件主要完成小程序的视图部分,例如文字.图片显示.API主要完成逻辑功能,例如网络请求.数据存储.音视频播放控制,以及微信开放的微信登录.微信支 ...

  2. centos 6.5 升级php

    1>追加CentOS 6.5的epel及remi源. # rpm -Uvh http://ftp.iij.ad.jp/pub/linux/fedora/epel/6/x86_64/epel-re ...

  3. webpack解惑:require的五种用法

    我之前在 <前端搭环境之从入门到放弃>这篇文章中吐槽过,webpack中可以写commonjs格式的require同步语法,可以写AMD格式的require回调语法,还有一个require ...

  4. HTML 字符图案

    Dog: <!-- :: :;J7, :, ::;7: ,ivYi, , ;LLLFS: :iv7Yi :7ri;j5PL ,:ivYLvr ,ivrrirrY2X, :;r@Wwz.7r: : ...

  5. Linux C++线程池

    .为什么需要线程池? 部分应用程序需要执行很多细小的任务,对于每个任务都创建一个线程来完成,任务完成后销毁线程,而这就会产生一个问题:当执行的任务所需要的时间T1小于等于创建线程时间T2和销毁线程时间 ...

  6. Python array,list,dataframe索引切片操作 2016年07月19日——智浪文档

    array,list,dataframe索引切片操作 2016年07月19日——智浪文档 list,一维,二维array,datafrme,loc.iloc.ix的简单探讨 Numpy数组的索引和切片 ...

  7. C#动态添加属性

    class DynamicInputParams: DynamicObject { Dictionary<string, object> property = new Dictionary ...

  8. 关于angularjS与jQuery框架的那些事

    这篇文章主要介绍了jQuery和angularJS的区别浅析,本文着重讲解一个熟悉jQuery的程序员如何应对angularJS中的一些编程思想的转变吗,需要的朋友可以参考下 最近一直研究angula ...

  9. Quantum Bogo sort浅谈

    1.普通的猴子排序(bogo sort) 猴子排序百科 en.wikipedia.org/wiki/Bogosort 不停的随机打乱序列,然后检查,直到排好序 复杂度O(n*n!) while not ...

  10. c++之map

    题目描述:     哈利波特在魔法学校的必修课之一就是学习魔咒.据说魔法世界有100000种不同的魔咒,哈利很难全部记住,但是为了对抗强敌,他必须在危急时刻能够调用任何一个需要的魔咒,所以他需要你的帮 ...