JavaScript变量作用域

1,435 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

变量,作为Javascript语言中的基石,不同于其他语言,Javascript变量具有复杂的作用域场景。局部作用域,全局作用域以及变量提升,都是我们在学习Javascript变量必须了解的东西。

作用域分类

在Javascript中,变量的作用域在变量声明时已经确定下来,换个说法就是,变量的作用域由变量声明的位置控制。

目前,在Javascript中声明变量的方法由三种,var,let和const三个关键字。let和const是ES6新增的声明变量的关键字。

在Javascript中由两个作用域:全局作用域和局部作用域。在ES6之后,局部作用域又可以分为:函数作用域和ES6新引入的块级作用域。

全局作用域1️⃣-Global Scope

在一个Js脚本中,最外层的作用域就是全局作用域,在此范围内声明的任何变量都会是全局变量,可以在程序的任意位置访问到。

在下面的例子中,name是在全局作用域声明的,是全局变量,可以在任意位置访问。、

// Global Scope

const name = "公众号:搞前端的半夏";

function sayHi() {
  console.log(`你好,${name}`);
}

sayHi();
// 你好,公众号:搞前端的半夏

局部作用域2️⃣-Local Scope

在一个代码块中声明的变量都是属于该块的局部变量。

在函数中使用var, let和const创建的变量,都是属于该函数的局部变量,这个函数内部就是函数作用域。

在代码块(if, for,while等等)中使用let和const声明变量,let和const会为这个代码块创建一个新的块级作用域。

函数和块级作用域可以嵌套。在这种情况下,使用多个嵌套范围,可以在其他的作用域或者内部访问变量。

函数作用域

为了更加方便的理解,我们举一个例子:例如下面的学校,学校内部的一切都属于学校。学校里面有宿舍楼,宿舍楼里面有宿舍。每个都有自己的范围。

image-20220208165704951

var locales = {
 school: function() {          // 这里的school(学校)是局部作用域
    var student = "公众号:搞前端的半夏";

    var apartment = function() {   //这里的apartment(宿舍楼)也是局部作用域
      var room = function() {  // 这里的room依然是局部作用域
        console.log(student);  // 公众号:搞前端的半夏
      };

      room();
    };

    apartment();
  }
};

locales.school();

在上面的例子中,我们想在room中获取到student,因为student是在他的上上级作用域也就是school作用域定义的,所以我们可以拿到这个变量。如果我们将var student = "公众号:搞前端的半夏";console.log(student);对调位置,会直接报错:因为我们无法从外部作用域到达内部作用域

image-20220208173005151

回到上面的例子,我们想找到student,是怎么查找的呢,首先我们在他的宿舍查找,没有找到,然后会往上一层宿舍楼进行查找,宿舍楼没有找到,然后在往学校查找,最终找到了student。

这种类型的查找称为此法作用域。程序的静态结构决定了变量范围。变量的范围由其在源代码中的位置定义,嵌套函数可以访问在其外部范围中声明的变量。无论从哪里调用函数,甚至如何调用它,它的词法范围都只取决于函数的声明位置。

块级作用域

下面我们通过一个例子来看一下块级作用域

function f(n) {
  if (true) {
    const msg = '你好';
    let name = n;
    console.log(msg + " " + name); 
  }
  console.log(msg + " " + name); 
}

f('公众号-搞前端的半夏');   

image-20220208222119079

在这个例子中,我们可以看到通过let和const在块级作用域外是无法被访问的。

如果我们将let和const替换为var?

function f(n) {
  if (true) {
    var msg = '你好';
    var name = n;
    console.log(msg + " " + name); 
  }
  console.log(msg + " " + name); 
}

可以正常运行!这也印证了:当我们使用var关键字时,变量在整个函数作用域都是可访问的!

image-20220208222321067

在 JavaScript 中,可以在多层嵌套范围内指定同名变量。在这种情况下,局部变量优先于全局变量。如果你声明了一个同名的局部变量和一个全局变量,当你在函数或块中使用它时,局部变量将优先。这种类型的行为称为遮蔽(shadowing。简单地说,内部变量遮蔽了外部变量。

JavaScript 解释器从当时正在执行的最内层范围开始,一直持续到找到第一个匹配项,无论外层级别中是否存在其他同名变量。让我们看一个例子

var test = "我是全局";

function testScope() {
  var test = "我是局部";
  console.log (test);     
}

testScope();           // output: 我是局部

console.log(test);     // output: 我是全局

即使名称相同,局部变量在函数执行后也不会覆盖全局变量testScope()。但情况并非总是如此。让我们考虑一下:

var test = "我是全局";

function testScope() {
  test = "我是局部";

  console.log(test);     
}

console.log(test);     // output: 我是全局

testScope();           // output: 我是局部

console.log(test);     // output: 我是局部 

这一次,局部变量test覆盖了同名的全局变量。当我们在testScope()函数内部运行代码时,全局变量被重新分配。如果一个局部变量在没有首先用关键字声明的情况下被赋值var,它就变成了一个全局变量。为避免此类情况,应始终在使用局部变量之前声明它们。在函数中使用关键字声明的任何变量var都是局部变量。

注意:在严格模式下,如果没有先声明变量就给变量赋值是错误的。

总结

  • var 变量是函数作用域
  • let and const 变量是块级作用域