块级作用域
① 全局作用域和函数作用域
在ES6之前只有 全局作用域和函数作用域,由于变量提升带来了一些不便。
1. 第一种场景,内层变量可能会覆盖外层变量。
```
var tmp = new Date();
function f(){
console.log(tmp);
if(false){
var tmp = 'hello world';
}
}
f(); //输出 undefined
```
if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是函数f执行后,输出的结果为undefined,
原因:在于变量的提升,导致内层的tmp变量覆盖了外层的tmp变量,在赋值tmp之前调用此变量所以结果为undefined。
变量提升:JavaScript 中,函数及变量的声明都将被提升到函数的最顶部,
注意:虽然函数声明和变量声明都会被提升,但是函数会首先被提升,然后才是变量。
在变量声明过后,它的赋值依然保留在原来的位置。
例:
上面的代码变量提升后
function f(){
var tmp;
console.log(tmp);
if(false){
tmp = "hello world";
}
}
var tmp ;
tmp= new Date();
f();
补充:使用匿名函数的方式不存在函数提升,因为函数名称使用变量表示,只存在变量提升。
例:
var getName=function(){
console.log(2);
}
function getName(){
console.log(1);
}
getName();//结果为2
--------------------------
//变量提升后
//首先函数先提升到顶部
function getName(){
console.log(1);
}
//其次变量提升
var getName;
getName=function(){ //变量赋值保留在原来的位置
console.log(2);
}
getName();//结果:2
JS运行机制(阮一峰)
JS运行机制(线程)
2. 第二种场景,用来计数的循环变量泄露为全局变量。
```
var s ="hello";
for(var i =0;i<s.length;i++){
console.log(s[i]);
}
console.log(i);//结果为5
--变量提升---------
var s;
var i;
s ="hello";
for(i=0;i<s.length;i++){
console.log(s[i]);
}
console.log(i);//结果为5
```
变量<span style="color:red;">i</span>只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量,<span style="color:red;">i</span>最终一直增值到字符串的长度5。
> ECMAScript参考文档关于作用域和变量提升的部分:
>  如果变量在函数体类声明,则它是函数作用域。否则,它是全局作用域(作为global的属性)。变量将会在执行进入作用域的时候被创建。块(比如if(){})不会定义新的作用域,只有函数声明和全局性质的代码(单个JS文件)才会创造新的作用域。变量在创建的时候被初始化为undefined。如果变量声明语句里面带有赋值操作,则赋值操作只会在被执行到的时候才会发生,而不是创建的时候。
② ES6的块级作用域
1. 什么是块级作用域
```
{
let i =0; //块级作用域
}
```
> 注意:使用块级作用域一般要使用let来声明变量
- ES6允许块级作用域的任意嵌套。外层作用域无法读取内层作用域的变量。
```
{{{{
{let i=6;}
console.log(i);//Uncaught ReferenceError: i is not defined
}}}}
```
- 内层作用域可以定义外层作用域的同名变量,但是外层代码块不受内存代码块的影响。
```
{{{{
let i =5;
{let i = 6;}
console.log(i);//5
}}}}
```
> 注意如果使用var定义 就没有了块级作用域,受变量提升的影响输出值为6
> ```
> var i;
> var i;
> {{{{
> i=5;
> {i=6;}
> console.log(i);//6
> }}}}
> ```
> 补充:块级作用域的出现,使得立即执行函数表达式(IIFE)不再必要了。
> ```
> //IIFE写法
>(function{
> var i =5;
>})();
>//块级作用域写法
>{
> let i =5;
>}
> ```
2. 块级作用域与函数声明
- ES5规定,函数只能再顶层作用域和函数作用域之中声明,不能在块级作用域中声明。
```
if(true){
function f(){}
}
try{
function f(){}
}catch(e){}
```
上面的两种函数声明根据ES5都是非法的。
但是,浏览器为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此以上两种情况实际都能运行,不会报错;但是在严格模式下,还是会报错;不过现在的浏览已经适用的ES6,不会报错。
> ```
> 'use strict';
> if(true){
> function func(){}//ES5报错/ES6不会
> }
> ```
- ES6引入了块级作用域,明确允许在块级作用域之中声明函数。ES6规定,块级作用域之中,函数声明语句的行为类似let,在块级作用域之外不可引用。
```
//ES5环境
function f(){console.log('I am outside!');}
(function{
if(false){
function f(){console.log('I am inside!');}
}
f();//I am inside!
})();
------经过变量提升---------
function f(){console.log('I am outside!');}
(function{
function f(){console.log('I am inside!');}
if(false){
}
f();//I am inside!
})();
```
ES6浏览器块级作用域的函数声明规则:
 允许在块级作用域内声明函数
 函数声明类似于var ,即会提升到全局作用域或函数作用域的头部。
 同时,函数声明还会提升到所在的块级作用域的头部。
```
//ES6环境
function f(){console.log('I am outside!');}
(function{
if(false){
function f(){console.log('I am inside!');}
}
f();//Uncaught TypeError: f is not a function
})();
------经过变量提升---------
function f(){console.log('I am outside!');}
(function{
var f =undefined;
if(false){
function f(){console.log('I am inside!');}
}
f();//Uncaught TypeError: f is not a function
})();
```
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。
如果确实需要,也应该写成函数表达式,而不是函数声明语句。
```
//函数声明语句
{
let a ='secret';
function f(){}
}
//函数表达式
{
let a ='secret';
let f = function(){}
}
```
> 注意:ES6的块级作用域允许声明函数的规则,只在使用大括号{}的情况下成立,如果没有使用大括号,就会报错。
> ```
> //不报错
> 'use strict';
> if(true){
> function f(){}
> }
> //报错
> 'use strict';
> if(true)
> function f(){}
> ```