ES6之后,JavaScript的变量声明经历了翻天覆地的变化。直到 ECMAScript 5.1, var都是声明变量的唯一关键字。ES6不仅增加了let和 const两个关键字,而日还让这两个关键字压倒性地超越var,成为首选。
1.使用var的函数作用域声明
在使用var声明变量时,变量会被自动添加到最接近的上下文。在函数中,最接近的上下文就是函数的局部上下文。在with语句中,最接近的上下文也是函数上下文。如果变量未经声明就被初始化了,那么它就会自动被添加到全局上下文,如下面的例子所示:
function add(num1,num2){
var sum = num1 + num2;
return sum;
}
let result = add(10,20);//30
console.log(sum); //报错:sum在这里有效变量
这里,函数add ()定义了一个局部变量sum,保存加法操作的结果。这个值作为函数的值被返回,但变量sum 在函数外部是访问不到的。如果省略上面例子中的关键字var,那么sum在add ()被调用之后就变成可以访问的了,如下所示:
function add(num1 ,num2){
sum =num1 +num2 ;
return sum;
}
let result = add(10,20);//30
console.log(sum);
这一次,变量 sum被用加法操作的结里初始化时并沿有使用var声明。在调用add()之后,sum被添加到了全局上下文,在函数退出之后依然存在,从而在后面可以访问到。
var声明会被拿到函数或全局作用域的顶部,位干作用域中所有代码之前。这个现象叫作“提升”( hoisting )。提升让同一作用域中的代码不必考虑变量是否已经声明就可以直接使用。可是在实践中,提升也会导致合法却奇怪的现象,即在变量声明之前使用变量。下面的例子展示了在全局作用域中两段等价的代码:
var name="Jake";
//等价于:
name = "Jake";
var name;
//下面是两个等价的函数:
function fnl() {
var name='Jake" ;
}
//等价于:
function fn2(){
var name;
name= 'Jake" ;
}
//通过在声明之前打印变量,可以验证变量会被提示。声明的提升意味着会输出 undefined 而不是Reference Error:
console.log(name); //undefined
var name = 'Jake';
function() {
console.log(name);//undefined
var name = 'Jake';
}
2.使用let的块级作用域声明
ES6新增的let关键字跟var很相似,但他的作用域是块级的,这也是JavaScript中的新概念。块级作用域最近的一对包含花括号{}
界定。换句话说。if块、while快、function快,甚至连单独的块级也是let声明变量的作用域
if(true){
let a;
}
console.log(a);//ReferenceError:a没有定义
while(true){
let b;
}
console.log(b);//ReferenceError:b没有定义
function foo(){
let c;
}
console.log(c);//ReferenceError:c没有定义
//这没有可奇怪的
//var 声明也会导致报错
//这不是对象字面量,而不是一个独立的快
//JavaScript解释器会根据其中内容识别出来
{
let d;
}
console.log(d); //ReferenceError:d没有定义
//let 与 var 的另一个不同之处是在同一件作用域内不能声明两次。重复的var声明会被忽略,而重复的let声明会抛出SyntaxError
var a;
var a;
//不会报错
{
let b;
let b;
}
//EyntaxError:标识符b已经声明过了
//let 的行为非常适合在循环中声明迭代变量,使用var声明的迭代变量会泄露到循环外部,这种情况应该避免。来看下面两个例子
for (var i = 0; i < 10;++i){};
console.log(i);//10
for(let j=0; j< 10; ++j){};
console.log(j);//ReferenceError:j 没有定义
//严格来讲,let在JavaScript运行时也会被提升,但由于“暂时性死区”的缘故,实际上不能在声明之前使用let变量。因此,从写JavaScript代码的角度说,let的提示跟var是不一样的
3.使用const的常量声明
除了let,ES6同时还增加了const关键字。使用const声明的变量必须同时初始化为某个值,一经声明,在其声明周期的任何时候都不能再重新赋予新值。
const a; //SyntaxError:常量声明时没有初始化
const b = 3;
console.log(b);//3
b = 4;//TypeError:给常量赋值
const 除了要遵循以上规则,其地方与let声明是一样的
if(true){
const a = 0;
}
console.log(a);//ReferenceError:a 没有定义
while (true){
const b = 1;
}
console.log(b); //ReferenceError:b没有定义
function foo(){
const c = 2;
}
console.log(c);//ReferenceError:c没有定义
{
const d = 3;
}
console.log(d);//ReferenceError:d没有定义
const声明只应用到顶级原语或者对象。换句话说,赋值为对象的const 变量不能再被重新赋值为其他引用值,但对象的键则不受限制。
const ol = {};
ol = {}; //TypeError:给常量赋值
const = o2 = {};
o2.name = 'Jake';
console.log(o2.name);//'Jake'
如果想让整个对象都不能修改,可以使用Object.freeze(),这样再给属性赋值时虽然不会报错,但会静默失败
const o3 = Object.freeze({});
o3.name = 'Jake';
console.log(o3.name);//undefined
//由于const声明暗示变量的值是单一类型且不可修改,JavaScript运行时编译器可以将其所有实例都替换成实际的值,而不会通过查询表进行变量查找。谷歌的V8引擎就执行这种优化。