JavaScript的变量提升及作用域

JavaScript的变量提升及作用域

JavaScript的语法简直不要太灵活,灵活到导出是坑,甚至有人说JavaScript语言不够成熟。。

Code once,debug everywhere.

下面简单整理下常见的坑坑。

变量提升(hoisting)

javascript的变量声明具有hoisting机制,JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的最前面:

1
2
3
4
5
var v = "hello";
(function(){
console.log(v);
var v = "world";
})();

倘若这是网页中的一部分,则第三行代码输出undefined,若是在浏览器DevTools的console中运行的话,则是两条undefined结果,第一条是console.log()输出的,第二条是执行(闭包)函数时返回的默认值(没有定义返回值时)undefined
其实上边👆的代码等价于:👇

1
2
3
4
5
6
7
var v = "hello";
(function(){
var v; //declaration hoisting 变量声明提升
console.log(v);
v = "world";
})();
//第三行中,只重新声明了v,没有定义(初始化赋值),所以返回undefined。

下面给我们稍加改动下,,👇

1
2
3
4
5
6
var v = "hello";
if(true){
console.log(v);
var v = "world";
}
// hello

如果JavaScript有块作用域的话,应该会报错吧,,内容大概是第4行变量v已经被定义什么的。这样说不太明显,,看这个:👇

1
2
3
4
5
for(var i = 0;i < 10; i++){
console.log(i);
}
console.log(i);//10
console.log(window.i);//10

上边在for循环中声明的变量i应该只存在与for循环的代码块中,但是却可以在代码块外访问,外边声明的变量默认是window对象的属性。由此可见javascript是没有块级作用域的。但函数是JavaScript中唯一拥有自身作用域的结构。就像文章开头的第一个代码块一样,再小改下👇:

1
2
3
4
5
6
7
8
9
var v = "hello";
(function(){
console.log("v inside:"+v);
var v = "world";
})();
console.log("v outside:"+v);

//v inside:undefined
//v outside:hello

此时,闭包函数外是访问不到内部的变量v = "world"的,,所以,outside输出的值为hello。若是不死心,,我们再改:👇

1
2
3
4
5
6
7
8
9
10
var v = "hello";
(function(){
var v = "world";
console.log("v inside:"+v);
})();
console.log("v outside:"+v);

//v inside:world
//v outside:hello
//此处不解释。

还是关于函数内的块级作用域,,我再换种方式:👇

1
2
3
4
5
6
7
var i = function(){
var h = 9;
console.log("inside h:"+h);
}
console.log("outside h:"+h)

// 第5行直接报错:h is not defined。

其实,我们还可以再改,,关于隐式声明的全局变量^1

1
2
3
4
5
6
7
8
9
10
11
var i = function(){
h = 9; //此处去掉关键字var
console.log("inside h:"+h);
}
i();
console.log("outside h:"+h);

// inside h:9
// outside h:9
// 在函数内部声明变量时省略var关键字,会默认当做全局变量。
// 另外,如果没有第五行的i();函数i调用,则第六行还是会报错Uncaught ReferenceError: h is not defined

》》》2017-12-19-补充《《《
1
2
3
4
5
6
7
8
9
10
11
var x = 0;

function f(){
var x = y = 1; // x在函数内部声明,y不是!
}
f();

console.log(x, y); // 0, 1
// x是全局变量。
// y是隐式声明的全局变量。
// 第4行可以近似理解为 y = 1;var x = y;

需要注意的是,提升的不光有变量,函数声明也会提升,但是通过函数字面量定义的函数不会被提升,详见[JavaScript执行顺序]。(https://someoneiscoding.github.io/2017/12/02/JavaScript%E6%89%A7%E8%A1%8C%E9%A1%BA%E5%BA%8F/)

名字解析顺序

javascript中一个名字(name)以四种方式进入作用域(scope),其优先级顺序如下:
1、语言内置:所有的作用域中都有 this 和 arguments 关键字
2、形式参数:函数的参数在函数作用域中都是有效的
3、函数声明:形如function foo() {}
4、变量声明:如 var;
名字声明的优先级如上👆所示,也就是说如果一个变量的名字与函数的名字相同,那么函数的名字会覆盖变量的名字,无论其在代码中的顺序如何。但名字的初始化却是按其在代码中书写的顺序进行的,不受以上优先级的影响。看代码:

1
2
3
4
5
6
7
8
9
(function(){
function foo(){}
console.log(typeof foo); //function
var foo;
console.log(typeof foo); //function

foo = "foo";
console.log(typeof foo); //string
})();