javascript篇-----函数作用域,函数作用域链和声明提前

  在一些类似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函数里声明的所有变量(但不涉及赋值)都被“提前”至函数体的顶部,看一下如下代码:

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

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

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

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

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

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

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

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

 1 name="lwy";                      //全局作用域中定义一个全局变量name,值为lwy
 2 function t(){
 3     var name="tlwy";             //函数 t 的作用域中定义一个局部变量name,值为tlwy
 4     function s(){
 5         var name="slwy";         //函数 t 的内嵌函数 s 的作用域中定义一个局部变量name,值为slwy
 6         console.log(name);       //输出name
 7     }
 8     function ss(){
 9         console.log(name);       //输出name
10     }
11     s();                         //调用执行函数s
12     ss();                        //调用执行函数ss
13 }
14 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"

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

 1 <html>
 2 <head>
 3 <script type="text/javascript">
 4 function buttonInit(){
 5     for(var i=1;i<4;i++){
 6         var b=document.getElementById("button"+i);
 7         b.addEventListener("click",function(){ alert("Button"+i);},false);
 8     }
 9 }
10 window.onload=buttonInit;
11 </script>
12 </head>
13 <body>
14 <button id="button1">Button1</button>
15 <button id="button2">Button2</button>
16 <button id="button3">Button3</button>
17 </body>
18 </html>

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

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

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

时间: 2024-12-31 03:31:43

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

javascript的函数作用域及声明提前

废话不说:先来段代码: var scope='global';function test(){    alert(scope);        // 输出undefine:而不是global    var scope='local';      alert(scope);        // 输出local}test(); 脑算下:你可能会认为第一alert会输出global: 但是事实上第一个alert输出的是undefined: 这是怎么回事呢? 要探讨这个问题首先要解释下两个概念: 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权威指南>中指出:JavaScript变量在声明之前已经可用,JavaScript的这个特性被非正式的称为声明提前(hoisting),即JavaScript函数中声明的所有变量(但不涉及赋值)都被“提前”至函数的顶部.下面我们从实例中看看: 实例1: var aa = "test"; function myFunc(){ console.log('aa值为:'+aa); var aa = "TEST"; } myFunc(); 调

javascript数据类型(六)--- 函数对象之作用域和作用域链

一.作用域 1.1 理解 * 就是一块"地盘", 一个代码段所在的区域 * 它是静态的(相对于上下文对象), 在编写代码时就确定了 1.2  分类 * 全局作用域 * 函数作用域 * 没有块作用域(ES6有了) 1.3 作用 * 隔离变量,不同作用域下同名变量不会有冲突 1.4 作用域与执行上下文的区别 区别1:创建的时间不同 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了.而不是在函数调用时 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之

深入理解javascript的作用域--函数声明为什么会前置

标签: javascript函数对象 这篇博文解决了以下迷惑 函数声明为什么前置 函数声明前置和变量前置优先级问题 为什么js文件开头就可以使用Math,String等库,而不需要导入头文件 1.变量对象VO 变量对象(Variable Object, 缩写为VO)是一个抽象 概念中的"对象",它用于存储执行上下文中的: 1. 变量 2. 函数声明 3. 函数参数 js解释器就是通过变量对象(VO)来找到我们定义的变量和函数的. 举个例子: var a = 10; function t

JavaScript中的匿名函数及函数的闭包以及作用域

1. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

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

作用域和作用域链 js的语法用法非常的灵活,且稍不注意就踩坑.这集来分析下作用域和作用域链.我们且从几道题目入手,您可以试着在心里猜想着答案. 问题一. if (true) { var str = "李四"; } alert(str);//弹出值是? 问题二. function add(num1, num2) { var sum = num1 + num2; } add(1,2); alert(sum) //弹出值是? 问题三. var str1 = "张三"; v

javascript回调函数,闭包作用域,call,apply函数解决this的作用域问题

在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实际上是一种对象,它可以“存储在变量中,通过参数传递给(别一个)函数(function),在函数内部创建,从函数中返回结果值”. 因为function是内置对象,我们可以将它作为参数传递给另一个函数,延迟到函数中执行,甚至执行后将它返回.这是在JavaScript中使用回调函数的精髓.本篇文章的剩余部

函数作用域及作用域链

变量作用域 全局作用域 在JavaScript中全局变量的作用域比较简单,它的作用域是全局的,在代码的任何地方都是有定义的.然而函数的参数和局部变量只在函数体内有定义 1.函数外面定义的变量拥有全局作用域 var n =2; function fn() { var a= 1; return a; } console.log(fn())//1 console.log(n)//2 console.log(a)//报错 error 2未定义直接赋值的变量自动声明为拥有全局作用域 var n =2; f