深入JavaScript之变量作用域 原创
春节不停更,此文正在参加「星光计划-春节更帖活动」https://harmonyos.51cto.com/posts/9923
变量,作为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会为这个代码块创建一个新的块级作用域。
函数和块级作用域可以嵌套。在这种情况下,使用多个嵌套范围,可以在其他的作用域或者内部访问变量。
函数作用域
为了更加方便的理解,我们举一个例子:例如下面的学校,学校内部的一切都属于学校。学校里面有宿舍楼,宿舍楼里面有宿舍。每个都有自己的范围。
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);
对调位置,会直接报错:因为我们无法从外部作用域到达内部作用域
回到上面的例子,我们想找到student,是怎么查找的呢,首先我们在他的宿舍查找,没有找到,然后会往上一层宿舍楼进行查找,宿舍楼没有找到,然后在往学校查找,最终找到了student。
这种类型的查找称为此法作用域。程序的静态结构决定了变量范围。变量的范围由其在源代码中的位置定义,嵌套函数可以访问在其外部范围中声明的变量。无论从哪里调用函数,甚至如何调用它,它的词法范围都只取决于函数的声明位置。
块级作用域
下面我们通过一个例子来看一下块级作用域
function f(n) {
if (true) {
const msg = '你好';
let name = n;
console.log(msg + " " + name);
}
console.log(msg + " " + name);
}
f('公众号-搞前端的半夏');
在这个例子中,我们可以看到通过let和const在块级作用域外是无法被访问的。
如果我们将let和const替换为var?
function f(n) {
if (true) {
var msg = '你好';
var name = n;
console.log(msg + " " + name);
}
console.log(msg + " " + name);
}
可以正常运行!这也印证了:当我们使用var
关键字时,变量在整个函数作用域都是可访问的!
在 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
andconst
变量是块级作用域