一、ES6之前声明变量的方式
在ES5及以前版本的JavaScript采用的是var关键字来声明变量,并且不支持块级作用域,也就是说var声明的变量时作用在全局作用域,下文会提到作用域的概念。
并且JavaScript是弱类型语言,也就是说声明变量时不需要指定变量的类型。
一个关于JS中数据类型的小例子
var web = 'web前端';
console.log(typeof web); // 输出string
var web = 99;
console.log(typeof web); // 输出number
var web = {};
console.log(typeof web); // 输出object
关于变量提升的问题
console.log(web);
var web = 'web前端'; // 这里代码不会报错,会输出一个undefined
// 这里的过程相当于这样
var web;
console.log(web);
web = 'web前端'
因为var声明的变量会提升到全局,这里相当于是先把var声明的变量放在前面,但是没有赋值,因此输出的时候此时web的值为undefined,而let和const不存在变量提升,因为它们是只支持块级作用域。
函数中的变量提升
function hd(){
if(false){
var str = 'web前端'
}
console.log(str);
}
hd(); // 此时也会输出一个undefined
// 这里的过程跟上面类似,实际解析过程是这样的
function hd(){
var str;
if(false){
str = 'web前端'
}
console.log(str);
}
hd();
var声明的变量无论其实际声明的位置在何处,都会被JS引擎调整到所在函数(或全局)的顶部,这就是变量提升。
二、ES6中新增的声明变量的方式
1.let关键字
基本用法:
{
let a = 1;
var b = 2;
}
console.log(a);
console.log(b);
这里的代码会报错,提示a没有定义,因为在JS中,大括号{}括起来的内容是一个块级作用域,因此let声明的变量仅在其代码块内有效,所以外部的输出会报错,var声明的变量虽然可以输出但是因为此时程序报错了,所以不会执行剩余的代码
重复声明:
function fn(){
let a=10;
let a=1;
console.log(a);
}
fn() // 这里代码会出错
function fn(){
var a=10;
var a=1;
console.log(a);
}
fn() // 这里会输出1
因为 let声明的变量不能重复声明,而var声明的变量就可以。
for循环中的应用
for(let i = 0; i < 10; i++){
console.log(i); // 这里会输出 0到 9
}
console.log(i); // 这里代码就会报错
for循环的计算器很适合使用let命令,因为let声明的变量只在其代码块内有效,for循环是一个作用域,因此后面的i就访问不到了。这样后面的代码就不会容易混淆。
for(var i = 0; i < 10; i++){
console.log(i); // 这里会输出 0到 9
}
console.log(i); // 这里代码就不会报错了,并且会输出10
因为var声明的变量会被提升到全局,所以此时经历了最后一次i++后i的值就会被修改为10,因此最后一个会输出10。
2.暂时性死区(TDZ)
代码如下
var temp = 123;
if (true) {
temp = 'abc';
let temp;
}
let声明的变量必须先声明后使用,ES6明确规定,如果区块中存在let或者const命令,那么这个区块对这些命令声明的变量从一开始就形成了一个封闭的作用域。在代码块内,使用let声明的变量之前,该变量都是不可用的,这个区域就被成为暂时性死区.
3.JS中的作用域
- 变量或函数起作用的区域。
- JavaScript采用的是“词法作用域”,即变量作用域取决于变量所在的代码区域。
- ES6中新增了“块级作用域”,也包含了ES5中的“全局作用域”和“函数作用域”
全局作用域
- 在所有函数之外定义的变量拥有全局作用域,该变量为全局变量。
- 全局变量可以在当前页面中任何JavaScript代码中访问。
函数作用域
- 在函数中声明的变量(包括函数参数)指定在其所声明的函数内被访问。
块作用域
- 由大括号{}限定的代码区域,let和const声明的变量具备可访问的块级作用域。
实例代码:
var v1 = 100;
function f1() {
console.log(v1, v2); // 输出两个undefined
var v1 = 110;
var v2 = 200;
function f2() {
let v3 = 300;
console.log(v1, v2, v3); // 输出110,200,300
}
f2();
console.log(v1, v2, v3); // 报错
}
f1();
console.log(v1, v2, v3); // 报错
第一个会输出两个undefined,因为在函数内var声明的会提升到所在函数作用域的顶部或者全局,此时v1,v2提升后的值就为undefined了。
第二个会输出110,200,300,因为在函数作用域中有一个就近原则,f2函数中没有v1,v2就会到上面去找,再找不到就会到全局去找,然后输出
第三个就会报错了,因为let声明的变量只在其作用域有效,此时let声明的变量在函数内,因此外部的就不能访问到let的值。
第四个也会报错,var声明的变量只会提升到所在作用域的顶部,此时var声明的变量在函数内,因此全局的访问v2,v3也是访问不到的。
ES5及以前只有全局作用域和函数作用域,从而会导致一些问题出现。
var tmp = new Date();
function f() {
console.log(tmp); // 这里输出undefined
if (false) {
var tmp = 'hello';
}
}
f();
这会导致函数内层的变量会覆盖掉外层的变量,此时tmp就不是全局的值了,而是内部提升的值。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(s,i); // 输出hello 5
用来计数的循环变量就会泄露为全局变量。
4.const关键字
基本用法:const声明一个只读的常量,一旦声明,那么其值不能改变且必须初始化。除此之外,与let的用法都是一致。
const foo={y:10};
foo.x=100;
console.log(foo.x); // 输出100
console.log(foo); // 输出{y:10,x:100}
当const声明的常量保存的不是一个值,而是一个地址的时候,该常量所引用的对象是可以更改成员的,只是不能更改常量保存的地址。
let b = 100;
var a = 200;
console.log(window.a); // 输出200
console.log(window.b); // 报错,没有定义
浏览器的顶层对象为window,var声明的变量会关联到顶层对象中,而let和const就不会。
5.var、let和const的区别
- var 声明的变量属于函数作用域或全局作用域,而 let 和 const 声明的变量只属于块级作用域
- var 存在变量提升,而 let 和 const 不存在变量提升
- var 声明的变量可以重复声明,并且在同一个作用域下,let 和 const 不能重复声明,且const不能修改。
总结
ES6新增了let和const命令,所声明的变量不属于顶层对象window属性,但var和function声明的全局变量依然是顶层对象的属性。
由于ES6及以后有了块级作用域的概念,更好的作用域识别,因此在循环计数等其他情况下尽量使用let关键字声明变量,用const来声明常量