JavaScript的变量提升及作用域
JavaScript的语法简直不要太灵活,灵活到导出是坑,甚至有人说JavaScript语言不够成熟。。
Code once,debug everywhere.
下面简单整理下常见的坑坑。
变量提升(hoisting)
javascript的变量声明具有hoisting机制,JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的最前面:1
2
3
4
5var v = "hello";
(function(){
console.log(v);
var v = "world";
})();
倘若这是网页中的一部分,则第三行代码输出undefined
,若是在浏览器DevTools的console中运行的话,则是两条undefined
结果,第一条是console.log()
输出的,第二条是执行(闭包)函数时返回的默认值(没有定义返回值时)undefined
。
其实上边👆的代码等价于:👇1
2
3
4
5
6
7var v = "hello";
(function(){
var v; //declaration hoisting 变量声明提升
console.log(v);
v = "world";
})();
//第三行中,只重新声明了v,没有定义(初始化赋值),所以返回undefined。
下面给我们稍加改动下,,👇1
2
3
4
5
6var v = "hello";
if(true){
console.log(v);
var v = "world";
}
// hello
如果JavaScript有块作用域的话,应该会报错吧,,内容大概是第4行变量v已经被定义什么的。这样说不太明显,,看这个:👇1
2
3
4
5for(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
9var 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
10var 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
7var i = function(){
var h = 9;
console.log("inside h:"+h);
}
console.log("outside h:"+h)
// 第5行直接报错:h is not defined。
其实,我们还可以再改,,关于隐式声明的全局变量^11
2
3
4
5
6
7
8
9
10
11var 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
11var 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
})();