JavaScript学习笔记: 局部变量和全局变量

JavaScript有两种变量:局部变量全局变量。而这两个对于初学JavaScript的同学来说是一个较为麻烦的。非常容易搞错。我也看了好几天有关于这方面的教程,也还没完全整明白。今天把自己理解的记录下来,有不对之处,还请高手斧正。

在深入了解JavaScript中的局部变量和全局变量,我们必须要了解以下几个方面:

  • 如何声明局部变量和全局变量
  • JavaScript的变量作用域是基于特有的作用域链的
  • JavaScript中没有块级作用域
  • 函数中声明的变量在整个函数中都有定义

如何声明局部变量和全局变量

在JavaScript中,常常把定义在函数外的变量称为全局变量;而在函数内声明的变量称为局部变量

来看一个例子:

var variable = 'global';
function checkVariable () {
    var variable = 'local';
    console.log(variable); // local
}

checkVariable();
console.log(variable); // global

上面的示例,在函数checkVariable()外声明了一个全局变量variable,同时在函数内声明了一个局部变量variable。打个比方来说,把函数checkVariable()当作一个容器,那么放在容器里的变量就是局部变量,而放在容器外的变量则是全局变量。当然,这也不是一尘不变的。为什么这么说呢?我们还是继续用代码来说话吧,因为代码比我更能阐述要说的一切:

variable = 'global';
function checkVariable() {
    variable = 'local';
    console.log(variable); // local
    myVariable = 'local';
    console.log(myVariable); // local
}

checkVariable();

console.log(variable); // local
console.log(myVariable); // local

对于我这样的初学者来说,我很多时候搞不清楚,为什么在函数外,variable的值也变成了local?为什么在函数体外可以访问myVariable变量?

其实这两个问题都源于一个特性。在全局作用域中声明变量可以省略var关键字,但如果在函数体内声明变量时不使用var关键字,就会发生上面这样的现象。

另外,第一个示例告诉我们,在函数体内部,局部变量的优先级比同名的全局变量要高。那么在声明局部变量的函数体范围内,局部变量将覆盖同名的全局变量。

感觉有点啰嗦,不过还是做个小小总结:

声明局部变量一定要使用var关键字,使用var关键字声明变量时,变量会自动添加到距离最近的可用环境中。如果没有写var, 变量就会暴露在全局上下文中, 这样很可能会和现有变量冲突. 另外, 如果没有加上, 很难明确该变量的作用域是什么, 变量也很可能像在局部作用域中, 很轻易地泄漏到 Document 或者 Window 中, 所以务必用var去声明变量。如果变量在未声明的情况下被初始化,该变量会自动添加到全局环境。

变量作用域链

如果要理解JavaScript中变量是局部变量还是全局变量,那就必须要先理解变量作用域链。要理解这个就得先整明白执行环境

每当JavaScript执行时,都会有一个对应的执行环境被创建,执行环境中很重要的一部分就是函数的调用对象,而调用对象就是用来存储相应函数的局部变量的对象。每一个JavaScript方法都是在自己独有的执行环境中运行。

简而言之,函数的执行环境包含了调用对象,调用对象的属性就是函数的局部变量,每个函数就是在这样的执行环境中执行,而在函数之外的代码,也在一个环境中执行,这个执行环境包含了全局变量。

在JavaScript的执行环境中,还有一个词要理解,那就是与执行环境对应的作用域链,它是一个由对象组成的列表或链。

在理解作用域链之前先来看一个示例:

var rain = 1;
function rainMan () {
    var man = 2;

    function inner () {
        var innerVar = 4;

        console.log(rain); // 1
    }

    inner(); // 调用inner函数
}

rainMan(); // 调用rainMan函数

执行上面的代码,浏览器控制器会输出rain的值,其值为1。先来看一张执行的示意图:

全局变量和局部变量

执行rainMan()函数后,会执行其内部的inner()函数,而这个函数执行console.log(rain)会输出一个值。这个过程在JavaScript是这样执行的,首先在inner()函数中查找是否声明了变量rain,如果声明了则会使用inner()函数中的rain变量;如果inner()函数中未声明变量rain,JavaScript则会继续在rainMan()函数中查找是否定义了rain变量。和inner()函数查找方式一样,如果在rainMan()函数中声明了变量rain,则会使用这个声明的变量;如果函数内没有定义rain变量,则JavaScript则会继续往向(全局对象)查找是否定义了rain变量。在这个示例中,inner()rainMan()函数中并未声明rain变量,而在全局对象中声明了rain=1,因此函数执行完最终结果会输出1。如果在全局对象中都未声明rain变量的话,则会输出报错信息:Uncaught ReferenceError: raine is not defined

作用域链:JavaScript需要查询一个变量x时,首先会查找作用域链的第一个对象,如果在第一个对象中没有定义x变量,JavaScript会继续查找有没有定义x变量,如果第二个对象没有定义则会继续查找,以此类推。上面的代码涉及到了三个作用域链对象,依次是:innerrainManwindow

在全局的 Javascript 执行环境中,作用域链中只包含一个对象,就是全局对象。而在函数的执行环境中,则同时包含函数的调用对象。由于 Javascript 的函数是可以嵌套的,因此每个函数执行环境的作用域链可能包含不同数目个对象,一个非嵌套的函数的执行环境中,作用域链包含了这个函数的调用对象和全局对象,而在嵌套的函数的执行环境中,作用域链包含了嵌套的每一层函数的调用对象以及全局变量。

全局变量和局部变量

上图来自于@范斌写的《理解 Javascript 中变量的作用域》文章中。

JavaScript中没有块级作用域

在一些程序语言中,比如C或者Java等语言,iffor这样的语句块中可以包含自己的局部变量,这些变量的作用域是这些语句块的,而在JavaScript中却不存在块级作用域这样的说法。

仔细看看下面的示例:

function checkVariable (obj) {
    var i = 0;
    if (typeof obj == 'object') {
        var j = 0;
        for (var k = 0; k < 3; k++) {
            console.log(k); // 0,1,2
        }
        console.log(k); // 3
    }
    console.log(j); // 0
}

checkVariable(new Object());

你会发现变量i,j,k作用域是相同的,他们都在checkVariable()函数内都是全局的。也就是说,在函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的

函数中声明的变量在整个函数中都有定义

在JavaScript中,函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的,即使在声明之前。其实这里隐藏一个坑,来看看下面的示例代码:

function checkVariable() {
    var a = 1;
    function inner () {
        a = 100;
    }
    inner();
    console.log(a); // 100
}
checkVariable();

上面的代码说明了,变量a在整个checkVariable()函数体内都可以使用,并可以重新赋值。由于这条规则,给入门者留埋下一个坑:

var a = 1;function checkVariable () {
    console.log(a); // undefined
    var a = 2;
    console.log(a); // 2
}
checkVariable();

在上面的示例中,console.log(a)输出的是undefined,既不是全局变量a的值1,也不是全局变量a的值10。是由于在函数checkVariable()内局部变量a在整个函数内都有定义(var a = 2进行了声明),所以在整个checkVariable()函数内隐藏了同名的全局变量a。所以才会输出undefined

上面的代码其实就等同于:

var a = 1;
function checkVariable() {
    var a;
    console.log(a);
    a = 2;
    console.log(a)
}

checkVariable();

简单总结一下

JavaScript的变量的域是根据方法块来划分的(也就是说以function的一对大括号{}来划分)。切记:function块,而不是forwhileif。因为在JavaScript中没有块作用域。

function checkVariable() {
    console.log(i); //i=> undefined i未赋值

    for (var i = 0; i < 3; i++) {
        console.log(i); // i=>0,1,2;当i为3时跳出循环
    }

    console.log(i); // i=>3 i在for循环外,但i的值仍然保留为3

    while (true) {
        var j = 1;
        break;
    }

    console.log(j); // j=>1; j在while循环外,但j的值仍然保留为1

    if (true) {
        var k = 1;
    }

    console.log(k); // k=>1; k已经在if循环外,但k的值仍然保留为1
}

checkVariable();

console.log(i); // Uncaught ReferenceError: i is not defined; 程序报错,中断脚本执行
console.log(j); // 未执行
console.log(k); // 未执行

JavaScript在执行前会对整个脚本做完整的分析(包括局部变量),从而确定变量的是局部变量还是全局变量。如下面的示例代码:

var a = 1;
function checkVariable () {
    console.log(a); // a=> undefined
    a = 2;
    console.log(a); // a=> 2
    var a;
    console.log(a); // a=> 2
}
checkVariable();
console.log(a); // a=>1

参考资料

初学者学习笔记,如有不对,还希望高手指点。如有造成误解,还希望多多谅解。

原文链接:https://www.w3cplus.com/javascript/the-basics-of-variable-scope-in-javascript.html

发表评论

登录后才能评论