JavaScript入门教程

JavaScript简介
JavaScript语法基础
JavaScript流程控制
JavaScript函数
面向对象编程
JavaScript事件
JavaScript DOM
正则表达式
JavaScript BOM
AJAX

专题分析

浏览器兼容性
JS优化
Web前端开发规范
编辑器推荐
总结和笔记

学习助手

对象参考手册
ECMAScript分析
数据中心
QQ交流群

JS函数作用域

词法作用域

作用域的概念在几乎所有的主流语言中都有体现,在 JavaScript 中,则有其特殊性:
JavaScript 中的变量作用域为函数体内有效,而无块作用域,我们在 Java 语言中,可以这样定义 for 循环块中的下标变量:
public void method(){
    for(int i = 0; i < obj1.length; i++){
        //do something here;
    }
    //此时的i为未定义
    for(int i = 0; i < obj2.length; i++){
        //do something else;
    }
}

而在 JavaScript 中:
function func(){
    for(var i = 0; i < array.length; i++){
        //do something here.
    }
    //此时i仍然有值,及I == array.length
    print(i);//i == array.length;
}

JavaScript 的函数是在局部作用域内运行的,在局部作用域内运行的函数体可以访问其外层的(可能是全局作用域)的变量和函数。JavaScript 的作用域为词法作用域,所谓词法作用域是说,其作用域为在定义时(词法分析时)就确定下来的,而并非在执行时确定,如下例:
var str = "global";
function scopeTest(){
    print(str);
    var str = "local";
    print(str);
}
scopeTest();

运行结果是什么呢?初学者很可能得出这样的答案:
global
local

而正确的结果应该是:
undefined
local

因为在函数 scopeTest 的定义中,预先访问了未声明的变量 str,然后才对 str 变量进行初始化,所以第一个 print(str)会返回 undifined 错误。那为什么函数这个时候不去访问外部的 str 变量呢?这是因为,在词法分析结束后,构造作用域链的时候,会将函数内定义的var 变量放入该链,因此 str 在整个函数 scopeTest 内都是可见的(从函数体的第一行到最后一行),由于 str 变量本身是未定义的,程序顺序执行,到第一行就会返回未定义,第二行为 str 赋值,所以第三行的 print(str)将返回”local”。

调用对象

我们再来深入的分析一下作用域,在 JavaScript 中,在所有函数之外声明的变量为全局变量,而在函数内部声明的变量(通过 var 关键字)为局部变量。事实上,全局变量是全局对象的属性而已,比如在客户端的 JavaScript 中,我们声明的变量其实是 window 对象的属性,如此而已。

那么,局部变量又隶属于什么对象呢?就是我们要讨论的调用对象。在执行一个函数时,函数的参数和其局部变量会作为调用对象的属性进行存储。同时,解释器会为函数创建一个执行器上下文(context),与上下文对应起来的是一个作用域链。顾名思义,作用域链是关于作用域的链,通常实现为一个链表,链表的每个项都是一个对象,在全局作用域中,该链中有且只有一个对象,即全局对象。对应的,在一个函数中,作用域链上会有两个对象,第一个(首先被访问到的)为调用对象,第二个为全局对象。

如果函数需要用到某个变量,则解释器会遍历作用域链,比如在上一小节的例子中:
var str = "global";
function scopeTest(){
    print(str);
    var str = "local";
    print(str);
}

当解释器进入 scopeTest 函数的时候,一个调用对象就被创建了,其中包含了 str 变量作为其中的一个属性并被初始化为 undefined,当执行到第一个 print(str)时,解释器会在作用域链中查找 str,找到之后,打印其值为 undefined,然后执行赋值语句,此时调用对象的属性 str 会被赋值为 local,因此第二个 print(str)语句会打印 local。

应该注意的是,作用域链随着嵌套函数的层次会变的很长,但是查找变量的过程依旧是遍历作用域链(链表),一直向上查找,直到找出该值,如果遍历完作用域链仍然没有找到对应的属性,则返回 undefined。