ES5中只有全局作用域和函数作用域。(造成:内层变量可能会覆盖外层变量;用来计数的循环变量泄漏为全局变量)
ES6中新增了块级作用域、let命令和const命令。一对{}即为一个块级作用域。
1.变量提升
只有var才有变量提升。
var只有全局作用域和函数作用域,变量提升是指将变量声明提升到它所在作用域的最开始部分。即可以在变量声明之前使用该变量,但是只有变量声明被提升,赋值不会。
console.log(person);//undefined
var person = 'tom';
console.log(person);//tom
上面代码相当于
var person;
console.log(person);
person='tom';
console.log(person);
2.函数提升
函数创建有两种方式:1.函数声明方式;2.函数字面量方式。
只有函数声明方式才有函数提升
//函数声明方式
function person(){
}
//函数字面量方式
var person = function(){
}
函数提升优先级高于变量提升,且不会被同名变量声明时覆盖,但是会被变量赋值后覆盖
console.log(person)
console.log(person())
function person(){
console.log('tom')
}
var person = 'jack';
console.log(person)
相当于:
function person(){
console.log('tom')
}
var person;
console.log(person)
console.log(person())
person='jack';
console.log(person)
//打印出:
//1.ƒ person(){console.log('tom')}
//2.tom
//3.undefined;如果函数没有返回值,默认为undefined
//1.jack
3.let、const
let和const不能变量提升,它们只有块级作用域。
同一作用域下面let和const不能声明同名变量,但是var可以。
const声明的时候必须赋值,而且赋值之后不能修改。
但是,const定义的对象属性是可以改变的!!
因为对象是引用类型,const定义的变量保存的仅仅是对象的指针,它只保证指针不发生改变,修改里面的属性不影响到指针,所以是允许的。
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
4.for循环下的var、let声明变量方式区别
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
以上代码如果用var声明变量,最终输出结果为10。
因为首先for循环是瞬间执行,执行的特别快,而且这里的i是全局变量,进行了变量提升,所以等for循环执行完的时候全局变量i的值为10(for循环中声明了十个a[0]~a[9]字面量声明的函数),所以最后输出的值就为10。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
虽然for循环是瞬间执行,但是let声明的变量只在块级作用域内有效,所以每次循环的{}中的变量i都不是同一个,所以最终输出的是6。
上面let代码可以理解为:
var a = [];
for (let i = 0; i < 10; i++) {
let j=i;
a[j] = function () {
console.log(j);
};
}
a[6](); // 6
注:for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域(同一个作用域不可使用 let 重复声明同一个变量)。
let可以解决闭包问题。
例如,想要对点击列表进行操作,需要循环列表并给每一个li绑定点击函数,但是如果用var声明变量去进行循环,最后无论点击哪一个li,都是作用于最后一个li,可以用自执函数和闭包去解决这个问题。因为每个自执函数都返回了自己的i值,在循环过后,如果自执函数里面有事件,就在事件被触发的时候把自己保存要返回的i传递出来。
<ul>
<li>aa</li>
<li>bb</li>
<li>cc</li>
<li>dd</li>
<li>ee</li>
</ul>
var aa = document.getElementsByTagName('li');
for (var i = 0; i < aa.length; i++) {
aa[i].onclick = (function (){
var index=i;
return function(){
alert(index)
}
}())
}
但是可以直接使用let就可以轻松避免闭包问题。
var aa = document.getElementsByTagName('li');
for (let i = 0; i < aa.length; i++) {
aa[i].onclick = function (){
alert(i)
}
}
在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
const同样存在暂时性死区,只能在声明的位置后面使用。
ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。
var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
var a = 1;
window.a // 1
let b = 1;
window.b // undefined