0%

理解变量声明提升以及作用域

#作用域
JavaScript采用的是词法作用域,是指作用域在函数定义的地方。与之对应的是动态作用域,指作用域在函数调用的地方。
JavaScript中变量是函数级作用域而不是块级作用域(即变量定义的作用域并不是离最近的封闭语句或者代码块,而是包含它们的函数),ES5的try{}catch除外,ES6开始支持。
变量声明提升:函数声明和变量声明总是被JavaScript解释器提升到包含它们的作用域顶部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function foo() { 
var num;
for(var i=0;i<3;i++) {
num = i;
}
console.log('i',i); // i 3
console.log('num',num); // num 2
}
// 相当于以下
function foo() {
var num,i;
for(i=0;i<3;i++) {
num = i;
}
console.log('i',i); // i 3
console.log('num',num); // num 2
}

要注意变量声明中,赋值是不会提升的(赋值函数也是),仅仅是变量名字被提升了。而函数声明则是整个函数体被提升。
函数声明优先于变量声明。 因为函数在JavaScript是 “一等公民”…

编写习惯

为了减少变量声明混乱,建议编写时候使用“单var”形式。

1
2
3
4
5
function fn() {
var i,
a=3,
b='str';
}

使用IIFE函数创建局部作用域

考虑以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
function fn(a) {
var arr= [],i,n;
for(i=0,n=a.length; i< n;i++) {
arr[i] = function() {
console.log('i',i);
return a[i]
}
}
return arr;
}
var obj = fn([1,2,3,4,5]); // i 5
var f = obj[0];
console.log(f()); // 输出undefined,而不是10

搞清楚这道程序在于理解绑定与赋值的区别。在运行时进入一个作用域,JavaScript会为每一个绑定到该作用域的变量在内存分配一个“槽”,fn函数绑定了三个局部变量即arr、i和n。在循环的每次迭代中,循环体都会为嵌套函数分配一个闭包,而闭包函数里面存的是变量i的引用,由于每一次函数创建后i都发生了变化,因此内部函数最终看到的是i最后的值。(值得注意的是闭包存储的是其外部变量的引用而不是值)
可以改为:

1
2
3
4
5
6
7
8
9
function fn(a) {
var arr= [],i,n;
for(i=0,n=a.length; i< n;i++) {
(arr[j] = function() {
return a[j]
})(i)
}
return arr;
}

但值得注意的是:代码块不能包含break与continue语句,因为在函数外使用它们是不合法的。

总结:

ECMAScript标准里写道:

如果在一个函数中声明变量,这些变量就被定义在了在该函数的函数作用域中。不然它们就是被定义在全局的作用域内(即它们被创建为全局对象的成员),当进入执行环境的时候,变量就被创建。一个语句块不能定义一个新的作用域。只有一个程序或者函数声明能够产生一个新的作用域。创建变量时,被初始化为undefined。如果变量声明语句里面带有赋值操作,则赋值操作只有被执行到声明语句的时候才会发生,而不是创建的时候。