作用域
变量作用域有两种:全局变量和局部变量。
变量在函数外定义,即为全局变量,全局变量有全局作用域:网页中所有脚本和函数均可使用。例如:
var arr = 'hello';
function fn() {
console.log(arr);
}
fn(); // hello
变量在函数内申明,变量为局部作用域,局部变量:只能在函数内部访问。例如:
function fn() {
var arr1 = 'world';
}
fn();
console.log(arr1); // arr1 is not defined
注:声明局部变量时一定要使用var,否则声明的是全局变量。
因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。
在函数内定义一个局部变量在函数体内始终是可见的,函数在解析时会将变量声明提前。例如:
var arr2 = 'hello';
function fn() {
console.log(arr2); // undefined
var arr2 = 'world';
console.log(arr2); // world,局部变量覆盖了全局变量
}
fn();
var arr2 = 'hello';
function fn() {
var arr2; // 提前声明了局部变量
console.log(arr2); // undefined
arr2 = 'world';
console.log(arr2); // world,局部变量覆盖了全局变量
}
fn();
注:局部变量的优先级要高于同名的全局变量,也就是当局部变量与全局变量重名时,局部变量会覆盖全局变量。
作用域链
执行环境:
执行环境是在运行和执行代码的时候才存在的,运行浏览器的时候会创建全局的执行环境,在调用函数时,会创建函数执行环境。所以执行环境有全局执行环境(全局环境)和函数执行环境之分。
全局执行环境:是最外围的一个执行环境,在web浏览器中可以认为是window对象,因此所有的全局变量和函数都是作为window对象的属性和方法创建的。代码载入浏览器时,全局环境被创建,关闭网页或关闭浏览器时全局环境被销毁。
函数执行环境:每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就被推入一个环境栈中,当函数执行完毕后,栈将其环境弹出,把控制权返回给之前的执行环境。
每个函数运行时都会产生一个执行环境,执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有与之对应的变量对象,保存着该环境中定义的所有变量和函数。
作用域链:
全局作用域和局部作用域中变量的访问权限,其实是由作用域链决定的。
每次进入一个新的执行环境,都会创建一个用于搜索变量和函数的作用域链。作用域链是函数被创建的作用域中对象的集合。作用域链可以保证对执行环境有权访问的所有变量和函数的有序访问。
作用域链的最前端始终是当前执行代码所在环境的变量对象(如果该环境是函数,则将其活动对象作为变量对象),下一个变量对象来自包含环境(包含当前运行环境的环境),下一个变量对象来自包含环境的包含环境,依次往上,直到全局执行环境的变量对象。全局执行环境的变量对象始终是作用域链中的最后一个对象。
标识符解析是沿着作用域一级一级的向上搜索标识符的过程。搜素过程始终是从作用域的前端逐地向后回溯,直到找到标识符(找不到就会导致错误发生)。
例1:
var a = 1;
function fn1() {
function fn2() {
console.log(a); // ③、执行fn2输出a
}
function fn3() {
var a = 4;
fn2(); // ②、fn3执行的结果得到fn2
}
var a = 2;
return fn3; // ①、fn1执行结果的得到fn3
}
var fn = fn1(); // fn1赋值fn,fn1执行结果为fn3
fn(); // 2
解析:
fn()执行结果为fn1,fn1执行结果为fn3,fn3执行结果为fn2,fn2执行结果是输出a的值。
在函数fn2()执行环境中没有变量a,解析器沿着函数fn2()的作用域链一级级向后回溯查找变量a,在父环境即函数fn1()中找到变量a,var a = 2,输出在控制台上。(由于局部变量的优先级要高于同名的全局变量,所以全局var a = 1被覆盖。)
结论:
- 函数的局部环境可以访问函数作用域中的变量,也可以访问和操作父环境(包含环境)乃至全局环境的变量;
- 父环境只能访问其包换环境和自己环境中的变量和函数,不能访问其子环境中的变量和环境。(所以函数fn1()不能访问子环境fn3()中的var a = 4。)
例2:
var a = 1;
function fn1() {
function fn3() {
var a = 4;
fn2(); // ②、fn3执行结果得到fn2
}
var a = 2;
return fn3; // ①、fn1执行结果返回fn3
}
function fn2() {
console.log(a); // ③、执行fn2输出a
}
var fn = fn1(); // fn1赋值fn,fn1执行结果为fn3
fn(); // 1
解析:
fn()执行结果为fn1,fn1执行结果为fn3,fn3执行结果为fn2,fn2执行结果是输出a的值。
在函数fn2()执行环境中没有变量a,解析器沿着函数fn2()的作用域链一级级向后回溯查找变量a,在父环境(包含环境)即全局环境中找到变量a,var a = 1,输出在控制台上。
总结:
全局环境只能访问全局环境中的变量和函数,不能直接访问局部环境中的任何数据。(所以函数fn2()在全局环境不能直接访问函数fn1()内的局部环境中的var a = 4或var a = 2。)