作用域
在 JavaScript 中有两种作用域
- 全局作用域
- 局部作用域
当变量定义在一个函数中时,变量就在局部作用域中,而定义在函数之外的变量则从属于全局作用域。每个函数在调用的时候会创建一个新的作用域。
全局作用域
最外层函数定义的变量拥有全局作用域,全局作用域里的变量能够在其他作用域中被访问和修改。
var name = "Tennant";
console.log(name);//Tennant
function logName(){
console.log(name);
}
logName();//Tennant
局部作用域
和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部
function fn(){
var name = "Tennant";
}
fn();
console.log(name);//undefined
需要注意的是,函数内部声明变量的时候,一定要使用var命令。如果不用的话,实际上声明了一个全局变量:
function fn(){
name = "Tennant";
}
fn();
console.log(name);//Tennant
再如以下代码,只要函数内定义了一个局部变量,函数在解析的时候都会将这个变量“提前声明” :
var scope = "global";
function fn(){
console.log(scope);//undefined
var scope = "local";
console.log(scope);//local
}
fn();
var scope = "global";
function fn(){
var scope;//提前声明了局部变量
console.log(scope);//undefined
scope = "local";
console.log(scope);//local
}
fn();
没有块级作用域
Javascript没有块级作用域,在其他类C的语言中,由花括号封闭的代码块都有自己的作用域,因此支持条件来定义变量。例如,以下代并不会得到想要的结果:
if(true){
var name = "Tennant";
}
console.log(name);//Tennant
这里是在一个if语句中定义了变量name。如果是在C、C++或者Java中,name会在if语句执行完毕后被销毁。但在JavaScript中,if语句中的变量声明会将变量添加到当前的执行环境(在这里是全局环境)中。在使用for语句时要特别注意,例如:
for (var i = 0; i < 10; i++){
doSomething(i);
}
console.log(i);//10
对于有块级作用域的语言来说,for语句初始化变量的表达式所定义的变量,只会存在于循环的环境之中。而对于JavaScript来说,由for语句创建的变量i即使在for循环执行结束后,也依旧会存在于循环外部的执行环境中。
作用域链(Scope chain)
当代码在一个环境执行时,会创建变量对象的一个作用域链。作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问。
执行环境
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境是最外围的执行环境,全局执行环境被认为是window对象,因此所有的全局变量和函数都作为window对象的属性和方法创建的。
JavaScript的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个环境栈中。而在函数执行之后,栈将该函数的变量对象弹出,把控制权交给之前的执行环境变量对象。
举个例子:
var color = "blue";
function changeColor(){
if(color === "blue"){
color = "red";
}else{
color = "blue";
}
}
changeColor();
alert(color);//red
在以上例子中,函数changeColor()的作用域链包含两个对象:
它自己的变量对象(其中定义着arguments对象)和全局环境的变量对象。可以在函数内部访问变量color,就是因为可以在这个作用域链中找到它。
看如下代码:
var a = 1
function fn1(){
function fn2(){
console.log(a)
}
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
var fn = fn1()
fn() //输出多少
//输出a=2
//执行fn2函数,fn2找不到变量a,接着往上在找到创建当前fn2所在的作用域fn1中找到a=2
var a = 1
function fn1(){
function fn3(){
function fn2(){
console.log(a)
}
var a
fn2()
a = 4
}
var a = 2
return fn3
}
var fn = fn1()
fn() //输出多少
//输出undefined
//函数fn2在执行的过程中,先从自己内部找变量找不到,再从创建当前函数所在的作用域fn去找,注意此时变量声明前置,a已声明但未初始化为undefined
以上查找方向为:
- 函数在执行的过程中,先从自己内部找变量(注意找的是变量的当前的状态)
- 如果找不到,再从创建当前函数所在的作用域去找, 以此往上