作用域(Scope)
作用域概述
任何程序设计语言都有作用域的概念,简单地说,作用域就是变量与函数可访问的范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域分为全局作用域(Global Scope)和局部作用域(Local Scope)(ps:在ES5中没有块级作用域)相应的,变量在全局作用域下称为全局变量,在局部作用域下称为局部变量。
全局作用域
在整个程序生命周期内都是有效的,在任意的函数内部都能访问的变量或函数拥有全局作用域。通俗的说就是全局声明的变量和定义的函数,在JS代码任何位置都可以使用。以下几种情形拥有全局作用域
在最外层定义的变量和函数拥有全局作用域
<script>
var num = 10;
function fn() {
var sum = 0;
function getSum() {
console.log(sum);
}
getSum();
}
console.log(num); //打印10
console.log(sum); //报错,变量sum不是全局变量
fn(); //打印0
getSum(); //报错 getSum函数在fn函数内部
</script>
没有声明直接赋值的变量拥有全局作用域
<script>
function fn() {
num = 10; //没有声明,直接赋值
}
fn();
console.log(num); //打印10
</script>
定义在window对象上的属性和方法,拥有全局作用域
<script>
window.num = 10;
function fn() {
console.log(num); //打印10
console.log(window.parseInt("10A")); //打印10
}
fn();
</script>
局部作用域
与全局作用域相反,局部作用域一般只在固定代码片段可访问到。最常见的例如函数内部声明的变量。在某个函数内部声明的变量或函数拥有局部作用域。它们只能被该函数的语句使用,函数外部是不可访问的。函数的参数也属于函数内部的变量,因此拥有局部作用域。
<script>
function fn(n) { //n为形参
var num = 10;
var sum = 0;
sum = num + n;
console.log(sum); //打印30
};
fn(20); //传入参数
console.log(num); //报错 num不是全局变量
</script>
ES6的块级作用域
- 在某个块("{ }")的内部声明的变量拥有块级作用域(在块级作用域内部定义函数需要特殊对待)。它们只能被该代码块内部的语句使用,代码块外部不可访问。代码块在创建的时候,变量的块级作用域已经确定下来。块级作用域和函数作用域也可以统称为局部作用域。
- ES5 只有全局作用域和函数作用域,没有块级作用域。ES6的 let 声明的变量只在它所在的代码块内有效,实际上就是为JavaScript新增了块级作用域。
- ES5 规定,函数只能在全局作用域和函数作用域之中声明。ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,在块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。但是这样的处理规则显然会对老代码产生很大的影响,出于兼容老代码的考虑,在块级作用域中声明的函数依然可以在作用域外部引用。如果需要函数只在块级作用域中起作用,可以写成函数表达式,而不是函数声明语句。
<script>
if (1 >= 0) {
let num = 1;
let fn = function() {
console.log(num + 1);
}
}
console.log(num); //报错
fn(); //报错
</script>
作用域链
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。因此,在当前作用域中无法搜索到某个变量时,引擎就会在外层嵌套的作用域中继续搜索,直到搜索到该变量,或抵达最外层的作用域(也就是全局作用域)为止。这样一条有序的列表,称为作用域链,作用域链的最前端一定是当前作用域。通俗的讲:先在当前自己的作用域的中找有没有声明的变量,如果没有往上层作用域找,如果上层作用域也没有该变量,就报错。
<script>
var num = 10086;
function getNum() {
var number = 10010;
function swapNum() {
var tempNumber = number;
number = num;
num = tempNumber;
//在这里可以访问到num,number,tempNumber;
console.log(num, number, tempNumber);
}
swapNum();
//在这里可以访问到num,number;
console.log(num, number);
}
//在这里只访问到num;
console.log(num);
getNum();
</script>
上面共涉及到三个执行环境:全局作用域环境、getNum()局部作用域环境以及swapNum()局部环境。
在全局变量环境中有一个变量num和一个函数getNum()。getNum()局部作用域环境中有一个变量number和一个函数swapNum()。但它也可以访问到全局作用域环境中的num变量。swapNum()局部作用域环境中有一个变量tempNumber,该变量只可以在这个环境中访问到。然而在swapNum()内部则可以访问其他两个环境中的所有变量,因为那两个环境是它的父级作用域环境。
总而言之,内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。
在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。