一、声明变量
es5之前,声明变量只有var声明、隐式声明和函数声明。而es6中则多了let和const。
(1)let和const的“块级作用域”:
var a = 1;
function test(){
var a = 2;
}
test();
console.log(a);
最终结果是"1",这是因为在js中,var有“函数作用域”,所以var a = 2的声明只能在函数test区域里面有效,在外面则不会生效,现在去掉函数:
var a =1;
{
var a = 2;
}
console.log(a);
得到的结果则是“2”,这是因为var没有“块级作用域”。
像c、java等其他语言中,变量的声明都有“块级作用域”,所谓“块”,就是以花括号{}为边界,而花括号里面的区域,就是这个声明可以被访问的域区,而花括号之外就不可以了。在es6中,新添加的let的const同样拥有“块级作用域”,将上面代码中的var改成let:
let a = 1;
{
let a = 2;
}
console.log(a);
最后输出的结果依然是“1”,改成const也是这个结果。
块级作用域,最大的应用,就是每篇let和const教学文章都会提到的for循环:
for( var i = 0; i < 3; i++){
setTimeout(function(){
console.log(i);
},100)
}
3次输出的结果都是“2”;
而改用let:
for( let i = 0; i < 3; i++){
setTimeout(function(){
console.log(i);
},100)
}
则3次输出的结果分别是“0、1、2”;
(2)var有提升变量的作用,而let和const则没有。
console.log(a);
var a = 1;
则输出的结果是:
因为var有提升变量的功能,但是不能提升赋值,所以输出的结果就是undefined,上面的代码就相当于:
var a;
console.log(a);
a = 1;
如果把var改成let或者const,例如:
console.log(a);
let/const a = 1;
则得到的结果就是:
造成这样的原因,是因为在let和const声明某个变量之前,会形成一个“Temporal Dead Zone”(暂时性死区),在这个区域里是无法访问这个变量。
(3)var可以重复声明,而let和const在同一块级作用域中不可以:
(1)var a = 1;
var a = 2;
(2)let a = 1;
let a = 2;
(3)const a = 1;
const a = 2;
(4)let a = 1;
const a = 2;
(5)let a = 1;
var a = 2;
(6)const a = 1;
var a = 2;
console.log(a);
后面5种情况都会报错:
[注意,let和const在不同块级作用域则可以同时存在,参见(1)“块级作用域部分”];
(4)const是常量不可变,let可变:
const的全拼“constant”就是不可变的意思
let a = 1;
a = a+1;
console.log(a);
输出的结果是“2”,如果改成const:
const a = 1;
a = a +1;
console.log(a);
则会报错:
需要注意的是,const的不可变并不是完全不可变,例如:
const a = [];
a.push(1);
console.log(a);
则输出的结果是“[1]”,这是因为const更像是“constant reference”。
也就是说,地址不能改变,而地址指向的值可以改变。
二、解构赋值(Destructuring):
es6之前,变量声明的结构是十分固定的,变量名放在等号的左边,而数组[]、对象{}和字符串则是放在等号的右边。而在es6中,它们的位置可以互相调换了。
let arr = [1,2,3];
let first = arr[0];
console.log(first);
则输出的结果是‘1’。
let arr = [1,2,3];
let [first] = arr;
console.log(first);
则输出的结果还是‘1’。
而对象的解构,属性名与变量名需要保持一致,否则会输出undefined
let {first}={'a':1};
console.log(first); //undefined;
左侧的变量可以通过逗号的形式跳过右侧对应的值:
var [,b] = [1,2,3];
console.log(b); // 输出结果为‘2’;
解构赋值的优势,就是能省去不少额外的声明,最典型的应用,就是json,例如后台通过ajax传过来的数据都是json,那是用的时候就不需要一个属性一个属性的声明了:
let json = {'username':xxx, 'option':'a', 'score':'100'}
let {username, option, score} = json;
console.log(username);
// 不用解构的话,想获取json中username的值,就要写成 json.username。
还有一个就是给函数的参数设定默认值:
function test(a,b){
if(a == true){
var b = 1;
}else{
var b = 2;
}
console.log(b);
}
test(false);
而使用解构赋值的话:
function test(a,{b = 2}){
if(a == true){
var b = 1;
}
console.log(b);
}
test(true,{});
另外要注意一点,解构赋值只能获取值,不能改变值:
let json = {num:1, num2:2};
let { num2 } = json;
console.log(num2 ); // 输出的结果为2;
num2 = 3;
console.log( json) // 输出的结果为{num:1, num2:2},而不是{num:1, num2:3};
通过解构赋值,json里num2的值被赋给了num2。但是反过来,如果你想通过直接修改num2的值来修改json里num2的值,这样是不行的。
所以有时你可能还要再包一层json:
let json = { nums : { num:1 ,num2:2} };
let {nums} = json;
nums.num2 = 3;
console.log(json) // 输出结果为{nums : { num:1 ,num2:3} };
三、模板字符串(template strings)----字符串拼接:
es5之前字符串的拼接是通过把字符串放在双引号或单引号里,而变量则通过‘+’和字符串拼接起来,而在es6中则是把字符串放在反引号`` 里,变量则放在$符号和花括号里,例如:
const word = "world";
console.log(`hi! ${word}.`);
输出的结果为:
而这个${}被称为“模板替换”(template substitutions),在这里你可以放任意JavaScript表达式(运算表达式,函数的调用)或者是某个对象属性、另一个模板字符串。
(1)换行
模板字符串添加了一个新的功能,支持换行。把两句话放在一个双引号或者单引号里,在两句话之间通按回车键进行换行,即:
console.log('this is line1,
this is line2');
js会认为这句话少了半边引号,导致报错,要想实现换行,只能在两句话之间加转移字符‘\n’。
如果换用反引号,即:
这样是不会报错的,只是输出的结果则为:
这是因为第二句话并不是在第二行最开始的地方书写,而是留有一定的空隙。由于模板字符串会保留反引号之内每一行的所有空格,所以导致这些空隙也被保留了下了。如果改成:
那么两句话的开头就是对齐的。
当然,如果你想要实现在html里换行,模板字符串里直接回车也是不会生效的,只有加上换行标签</br>才行。
document.write(`this is line1,
</br>this is line2`)
(2)嵌套
提到模板字符串,大部分的文章都会提到类似“模板字符串之间还可以进行嵌套”之类的话,而这个嵌套,也主要是指在${}里放入另一个模板字符串。例如声明一个变量:
let b = 'world';
console.log(``${a}``); // 报错,可见模板字符串不能直接嵌套在另一个反引号里。
console.log(`${ `hello ${b}` }`); // 输出‘hello world’
如果声明多个变量:
let a = 'hello';
let b = 'world';
console.log(`${a `${b}`}`); //报错:a is not a function(…),可见${}里面如果存在变量时,也不能放入另一个模板字符串。
(3)标签模板(tagged template)
定义一个test函数:
function test(){
console.log('hello');
};
test ``;
最后结果输出:
然而``调用函数和()调用函数还是有一定的区别,现在改成通过传参的方式调用函数:
function test(data){
console.log(data);
};
test `hello`;
结果输出的却是数组:
这就是模板字符串的‘标记模板’属性,所谓“标记模板”,就是定义一个函数,然后把该函数的函数名放在模板字符串的前面,这样就能将该函数和模板字符串联系起来,然后通过这个函数对模板字符串进行处理。