04-全局作用域
7.3 全局作用域
作用域具有层次结构,它有一个顶端:任何程序只要开始运行,就隐式地运行在某个作用域中,这就是全局作用域。当一个JavaScript程序开始运行时,在任何函数被调用之前,它都运行在全局作用域内。也就是说,在全局作用域中声明的一切,在当前程序的所有作用域内都是可用的。
在全局作用域中声明的变量叫全局变量,然而,全局变量的名声并不好。几乎所有关于编程的书都会这样警告读者:全局变量简直是洪水猛兽!为什么会这样呢?
全局变量本身并没有什么问题——事实上,它们还是必须的。问题出在全局作用域的滥用上。在前文提到,全局作用域中声明的一切在所有作用域中都可用。所以一定要谨慎地使用全局变量。
聪明的读者可能会想“那好,我在全局作用域中只创建一个函数,这样可以把全局变量减少到只剩一个!”聪明,不过这样只是将问题向下降了一级。因为在这个函数的作用域内声明的变量,对这个函数内部的任何调用来说,也是可用的……这其实不比全局变量好到哪去。
关于全局变量的底线是:大家可能会在全局作用域中声明某些变量,而这不一定就是糟糕的,而是应该努力避免依赖全局作用域。看一个简单的例子:跟踪一个用户信息。程序会跟踪用户的名字和年龄,有一些函数可以更改这些信息。一种方式是使用全局变量:
let name = "Irena"; // 全局变量
let age = 25; //全局变量
function greet() {
console.log(`Hello, ${name}!`);
}
function getBirthYear() {
return new Date().getFullYear() - age;
}
这种方式的问题在于,函数高度依赖它们被调用时的上下文(或者作用域)。整个程序中,任何地方的任意函数,都可以改变 name 的值(有意或者无意)。而“name”和“age”是这样的变量名太过普通,很可能被用在其他地方,用于其他的用途。因为 greet 和 getBirthYear 依赖全局变量,它们能正常工作的前提是,程序中其他地方正确地使用了 name 和 age 。
另一种更好的方式是将用户信息放到一个单独的对象中:
let user = {
name = "Irena",
age = 25,
};
function greet() {
console.log('Hello, ${user.name}!');
}
function getBirthYear() {
return new Date().getFullYear() - user.age;
}
在这个简单的例子中,仅仅是将全局作用域中标识符的数量减少了一个(以 user 代替了 name 和 age ),但想象一下如果有10条用户信息,会怎么样呢?如果有100个呢?
仍然有更好的实现方式,因为,即便函数 greet 和 getBirthYear 依赖了全局变量 user ,而 user 还是可以被任意修改。可以对函数做些优化,从而让它不再依赖全局作用域:
function greet(user) {
console.log('Hello, ${user.name}!');
}
function getBirthYear(user) {
return new Date().getFullYear() - user.age;
}
此时函数可以在任何作用域被调用了,需要显式传入一个 user (当学习了模块和面向对象编程后,会有更好的方式来处理它)。
如果所有程序都这么简单,那么用不用全局变量似乎都没有太大的影响。而当程序长达1 000行时,或者10万行时,就不可能凭借大脑去记住所有的作用域了(甚至全部显示在屏幕上都不可能)。此时,避免依赖全局作用域就显得更加至关重要了。