作用域
1.1 作用域
几乎所有的编程语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。
作用域有全局作用域和局部作用域(一般是在函数内)之分。
1.1.1 全局作用域
在代码中任何地方都能访问到的对象拥有全局作用域。
一般来说,拥有全局作用域有以下几种情况:
(1)在最外层的函数和在最外层函数外面定义的变量拥有全局作用域
例子:
var name = 'tg';
function test(){
var name2 = 'tg';
}
在上面的例子中,变量name和函数test()就拥有全局作用域
(2)所有未定义而直接赋值(不用var关键字声明)的变量被自动声明为拥有全局作用域
funciton test(){
var age = 10;
name = 'tg';
}
test();
console.log(age); // 会报错undefined
console.log(name); // "tg"
变量name拥有全局作用域,而age在函数外面是无法访问到的
(3)所有window对象的属性拥有全局作用域
window对象的内置属性都由拥有全局作用域,比如window.location、Date对象等
1.1.2 局部作用域
局部作用域不像全局作用域,它被限定在一个范围内,最常见的局部作用域就是在函数内部,我们也称为 函数作用域 。
函数作用域 是指变量能够被使用的代码区间。超出作用域的变量值一般为undefined,或者被其他同名变量值所覆盖。
funciton test(){
var age = 10;
console.log(age);
}
test(); // 10
console.log(age); // 会报错undefined
在上面的例子中,在函数外面是无法访问到变量age的,但在函数内部是可以访问的。
要记住,对于变量或函数,它的作用域取决于定义时的作用域,而不是在调用它的作用域中。
1.2 作用域链
在ES 5中,将作用域链称为 词法环境 。
当代码在一个作用域中执行时,会创建变量对象的一个作用域链(scope chain)。
作用域链的用途:保证对执行环境有权访问的所有变量和函数的有序访问。
作用域链的前端,始终都是当前执行的代码所在作用域的变量对象。如果这个环境是函数,则将其活动对象作为变量对象,然后对于每一个函数的形参,都命名为该活动对象的命名属性。
活动对象在最开始只包含一个变量,即arguments对象(在全局环境中是不存在的)。
看一个例子:
var name = 'p';
function test(){
var name = 'tg';
function get(){
console.log(name);
}
get();
}
test(); // "tg"
当执行get()时,将创建函数get()的执行环境(调用对象),并将该对象置于作用域链的前端(开头),然后将函数test()的调用对象链接在之后,最后是全局对象,然后从作用域链的前端开始查找标识符name,很显然,变量name的值是”tg”;
作用域链:get() -> test() -> window
每次进入一个新的作用域,都会创建一个用于搜索变量和函数的作用域链。
函数的局部变量不仅有权访问函数作用域中的变量,而且有权访问其他包含(父)环境,乃至全局作用域。但是全局作用域只能访问在全局作用域中定义的变量和函数,不能直接访问局部作用域中的任何数据。
变量的作用域有助于确定应该何时释放内存。
1.3 JavaScript没有块级作用域
JavaScript中并没有块级作用域。也就是说,对于if、for、while、switch等块结构是没有作用域的。
if(true){
var name = 'tg';
}
console.log(name); // "tg"
在if语句里定义了一个name变量,但它并不会向在函数内那样,但函数执行结束后就销毁,而是会一直存在,因为它被添加到了当前的执行环境(也就是全局环境)中。
对于for循环也是一样:
for(var i = 0; i < 10; i++){
//循环体
}
console.log(i); // 10
(1)声明变量
使用var声明的变量会自动被添加到最接近的环境中,比如在函数内部,最接近的环境就是函数的局部环境。如果初始化变量没有使用var声明,会自动被添加到全局环境中。
(2)查询标识符
标识符的查询规则是:逐级向上查询,如果在局部环境中找到了该标识符,搜索过程就停止,变量就绪,否则,会继续向上查询,直到全局环境的变量对象,如果在全局环境中也没有找到这个标识符,就表示该变量尚未声明(通常会报错)。
var name = 'tg2';
function test(){
var name = 'tg';
console.log(name); // "tg"
}
test();
在上面的例子中,在函数内部已经找到了name标识符,所以返回的是”tg”