本文转自公众号 前的端
在一些类似C语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的,我们称之为块级作用域(block scope),而JavaScript中没有块级作用域。JavaScript取而代之地使用了函数作用域(function scope)
在下面代码中,在不同位置定义了变量i、j和k,它们都在同一个作用域内——这三个变量在函数体内都是有定义的。
function test(o){
var i = 0; //i在整个函数体内是有定义的
if(typeof o =="object"){
var j = 0; //j在整个函数体内是有定义的,不仅仅是在这个代码段内
for(var k=0; k < 10;k++){ //k在函数体内是有定义的,不仅仅是在循环内
console.log(k); //输出数字1~9
}
console.log(k); //k已经被定义过了,输出10
}
console.log(j); //j已经定义了,输出0(但也可能没有初始化值)
}
JavaScript的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。有意思的是,这意味着变量在声明之前甚至已经可用。
这个特性被非正式地称为声明提前(hoisting),也就是说JavaScript函数里声明的所有变量(不包括赋值)都被"提前"到函数体的顶部,上代码:
var scope = "global"
function f(){
console.log(scope);//输出"undefined",而不是“global”
var scope = "local";//变量在这里赋初始值,但变量本身在函数体内任何地方均是有定义的
console.log(scope);//输出“local”
}
你可能会误以为函数的第一行会输出“local”,因为代码还没有执行到var语句声明局部变量的地方。
其实不然,由于函数作用域的特性局部变量在整个函数体内部始终是有定义的,也就是说,在函数体内局部变量遮盖了同名全局变量。尽管如此,只有在程序执行到var语句的时候,局部变量才会被真正赋值。
所以,上述代码过程等价于:将函数内的变量声明“提前”至函数体顶部,同时变量初始化留着原来的位置:
function f(){
var scope; //在函数顶部声明局部变量
console.log(scope); //变量存在,但它的值是"undefined"
scope = "local"; //这里将其初始化并赋值
console.log(scope); //这里它具有了我们期望的那个值
}
在具有块级作用域的编程语言中,在狭小的作用域里让变量声明和变量的代码尽可能靠近彼此,通常来讲,这是一个非常不错的编程习惯。由于JavaScript没有块级作用域,因此一些程序员特意将变量声明放在函数体顶部,而不是将声明放在使用变量之处。这种做法使得他们的源代码非常清晰地反映了真实的变量作用域。
注意:声明”提前“只有在var定义变量时适用,使用ES6新增加的let定义变量时则不会出现这种情况!