理解 Javascript 中变量的作用域

孙据
• 阅读 8249

Javascript 这门语言与其他的大部分语言相比,有很多特殊性,这是很多人喜欢它或者讨厌它的原因。其中变量的作用域问题,对很多初学者来说就是一个又一个「坑」。

变量的作用域在编程技能中算是一个基本概念,而在 Javascript 中,这一基本概念往往挑战者初学者的常识。

基本的变量作用域

先上例子:

javascriptvar scope = 'global';
function checkScope(){
    var scope = 'local';
    console.log(scope); // local
}
checkScope();
console.log(scope); // global

上面的例子中,声明了全局变量 scope 和函数体内的局部变量 scope。在函数体内部,局部变量的优先级比通明的全局变量要高,如果一个局部变量的名字与一个全局变量相同,那么,在声明局部变量的函数体范围内,局部变量将覆盖同名的全局变量。

下面再看一个例子:

javascriptscope = 'global';
function checkScope(){
    scope = 'local'; 
    console.log(scope); // local
    myScope = 'local';
    console.log(myScope); // local
}
checkScope();
console.log(scope); // local
console.log(myScope); // local

对于初学者来说,可能会有两个疑问:为什么在函数体外,scope 的值也变成了 local ?为什么在函数体外可以访问 myScope 变量?

这两个问题都源于一个特性。在全局作用域中声明变量可以省略 var 关键字,但是如果在函数体内声明变量时不使用 var 关键字,就会发生上面的现象。首先,函数体内的第一行语句,把全局变量中的 scope 变量的值改变了。而在声明 myScope 变量时,由于没有使用 var 关键字,Javascript 就会在全局范围内声明这个变量。因此,在声明局部变量时使用 var 关键字是个很好的习惯。

在 Javascript 中,没有「块级作用域」一说

在 C 或者 Java 等语言中,iffor 等语句块内可以包含自己的局部变量,这些变量的作用域是这些语句的语句块,而在 Javascript 中,不存在「块级作用域」的说法。

看下面的例子:

javascriptfunction checkScope(obj){
    var i = 0;
    if (typeof obj == 'object') {
        var j = 0;
        for (var k = 0; k < 10; k++) {
            console.log(k);
        }
        console.log(k);
    }
    console.log(j);
}
checkScope(new Object());

在上面的例子中,每一条控制台输出语句都能输出正确的值,这是因为,由于 Javascript 中不存在块级作用域,因此,函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的

如果要更加强调上文中 函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的 这句话,那么还可以在后面跟一句话:函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的,即使是在声明之前。对于这句话,有个经典的困扰初学者的「坑」。

javascriptvar a = 2;
function test(){
    console.log(a);
    var a = 10;
}
test();

上面的例子中,控制台输出变量 a 的值为 undefined,既不是全局变量 a 的值 2,也不是局部变量 a 的值 10。首先,局部变量在整个函数体内都是有定义的,因此,局部变量 a 会在函数体内覆盖全局变量 a,而在函数体内,var 语句之前,它是不会被初始化的。如果要读取一个未被初始化的变量,将会得到一个默认值 undefined

所以,上面示例中的代码与下面的代码时等价的:

javascriptvar a = 2;
function test(){
    var a;
    console.log(a);
    a = 10;
}
test();

可见,把所有的函数声明集合起来放在函数的开头是个良好的习惯

变量的真相

可能很多人已经注意到,在 Javascript 当中,一个变量与一个对象的一个属性,有很多相似的地方,实际上,它们并没有什么本质区别。在 Javascript 中,任何变量都是某个特定对象的属性

全局变量都是全局对象的属性。在 Javascript 解释器开始运行且没有执行 Javascript 代码之前,会有一个「全局对象」被创建,然后 Javascript 解释器会给它与定义一些属性,这些属性就是我们在 Javascript 代码中可以直接使用的内置的变量和方法。之后,每当我们定义一个全局变量,实际上是给全局对象定义了一个属性。

在客户端的 Javascript 当中,这个全局变量就是 Window 对象,它有一个指向自己的属性 window ,这就是我们常用的全局变量。

对于函数的局部变量,则是在函数开始执行时,会有一个对应的「调用对象」被创建,函数的局部变量都作为它的属性而存储。这样可以防止局部变量覆盖全局变量。

作用域链

如果要深入理解 Javascript 中变量的作用域,那就必须拿出「作用域链」这个终极武器。

首先要理解的一个名词就是「执行环境」,每当 Javascript 执行时,都会有一个对应的执行环境被创建,这个执行环境中很重要的一部分就是函数的调用对象(前面说过,调用对象是用来存储相应函数的局部变量的对象),每一个 Javascript 方法都是在自己独有的执行环境中运行的。简而言之,函数的执行环境包含了调用对象,调用对象的属性就是函数的局部变量,每个函数就是在这样的执行环境中执行,而在函数之外的代码,也在一个执行环境中,这个执行环境包含了全局变量。

在 Javascript 的执行环境中,还有一个与之对应的「作用域链」,它是一个由对象组成的列表或链。

当 Javascript 代码需要查询一个变量 x 的时候,会有一个被称为「变量名解析」的过程。它会首先检查作用域链的第一个对象,如果这个对象包含名为 x 的属性,那么就采用这个属性的值,否则,会继续检查第二个对象,依此类推。当检查到最后一个对象的时候仍然没有相应的属性,则这个变量会被认定为是「未定义」的。

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

我们可以用一个图来直观地解释作用域链和变量名解析的过程:

理解 Javascript 中变量的作用域

点赞
收藏
评论区
推荐文章
Dax Dax
4年前
JS核心原理理解闭包
前置概念在正式看闭包之前,我们先来学习一下前置知识,那就是JS中的作用域,我们知道,在ES5之中,作用域分为两种:全局作用域和函数作用域,随着ES6的到来,新增了块级作用域,想更好的理解闭包,那么搞清楚作用域是首要条件全局作用域我们知道,对于变量而言,我们一般会分成两类:全局变量和局部变量,一般定义在最外围环境的为全局变量,定义在函数当中的为局部变量,在we
Souleigh ✨ Souleigh ✨
4年前
JS - 作用域
一、作用域作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合换句话说,作用域决定了代码区块中变量和其他资源的可见性举个例子function myFunction(){    let inVariable  "函数内部变量";}myFunction();//要先执行这个函数,否则根本不知
Symbol卢 Symbol卢
4年前
秒懂js作用域与作用域链
JavaScript中有一个被称为作用域(Scope)的特性。虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,本文我会尽我所能用最简单的方式来解释作用域和作用域链,希望大家有所收获!好了下面开始我们的正文作用域常见的解释(什么是作用域)1.一段程序代码中所用到的名字并不总是有效,而限定它的可用性的范围就是这个名字的作用域;2.作用域规定了
Karen110 Karen110
3年前
一篇文章带你了解JavaScript作用域
在JavaScript中,对象和函数也是变量。在JavaScript中,作用域是你可以访问的变量、对象和函数的集合。JavaScript有函数作用域:这个作用域在函数内变化。一、本地JavaScript变量一个变量声明在JavaScript函数内部,成为函数的局部变量。局部变量有局部作用域:它们只能在函数中访问。JS://codeherecann
菜园前端 菜园前端
2年前
为你解惑JS作用域和作用域链知识
原文链接:变量作用域一个变量的作用域(scope)是程序源代码中定义这个变量的区域。全局变量拥有全局作用域,在JavaScript代码中的任何地方都是可以访问的。然而在函数内声明的变量只能在函数体内访问,它们是局部变量,作用域是局部性的。函数参数也是局部变
御弟哥哥 御弟哥哥
4年前
彻底理解js的作用域链
在之前的文章(https://www.helloworld.net/p/G4dFV7tALU4J)中我已经介绍了执行上下文的变量对象。在这一篇文章我要介绍执行上下文的作用域链了。执行上下文.作用域链(scopechain)作用域链与变量对象有着密不可分的关系,因为作用域链就是变量对象的数组!其中第
Jacquelyn38 Jacquelyn38
4年前
你所知道的JS变量作用域
变量的作用域,指的是变量在脚本代码中的可读、可写的有效范围,也就是脚本代码中可以使用这个变量的区域。在ES6之前,变量的作用域主要分为全局作用域、局部作用域(也称函数作用域)两种;在ES6及其之后,变量的作用域主要分为全局作用域、局部作用域、块级作用域这3种。相应作用域变量分别称为全局变量、局部变量、块级变量。全局变量声明在所有函数之外;局部变量是在函数体内
Jacquelyn38 Jacquelyn38
4年前
重学JavaScript第1集|变量提升
变量提升就好比JavaScript引擎用一个很小的代码起重机将所有var声明和function函数声明都举起到所属作用域(所谓作用域,指的是可访问变量和函数的区域)的最高处。这句话的意思是:如果在函数体外定义函数或使用var声明变量。则变量和函数的作用域会提升到整个代码的最高处,此时任何地方访问这个变量和调用这个函数都不会报错;而在函数体内定义函数或使用va
御弟哥哥 御弟哥哥
4年前
彻底理解js的执行上下文,以及变量对象
在js中,执行上下文(ExecutionContext)是非常重要的一种对象,它保存着函数执行所需的重要信息,其中有三个属性:变量对象(variableobject),作用域链(scopechain),this指针(thisvalue),它们影响着变量的解析,变量作用域和函数this的指向。上下文栈(ExecutionContextS
Stella981 Stella981
3年前
JavaScript易错知识点整理
前言本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一些ES6的知识点。JavaScript知识点1.变量作用域vara1;functio
Wesley13 Wesley13
3年前
Java连载7
一、变量1.注意点:在同一个“作用域”中,变量名不能重名,但是变量可以重新赋值。2.什么是作用域?答:描述的是变量的有效范围,在范围之内是可以被访问的,只要出了作用域就无法访问(也就是在大括号里面才行)3.关于变量的分类(1)局部变量:在方法体中声明的变量;(2)成员变量:在方法体外声明的变量。4.在不同的作用域中,变量名是可