我们可以使用babel官网提供的在线编译工具,将ES6编译为对应的ES5代码,观察两者直接的不同有助于我们更加了解ES6的知识。
新的变量声明方式 let/const
在ES5中,我们使用var
来声明一个变量。新的变量声明方式带来了一些不一样的特性。其中最重要的就是具备了块级作用域并且不再有变量提升。
通过两个简单的例子来说明。
{
let a = 20;
}
console.log(a); // a is not defined
以上代码会被编译为:
{
let _a = 20;
}
console.log(a); // a is not defined
// demo02
// ES5
console.log(a); // undefined
var a = 20;
// ES6
console.log(a); // a is not defined
let a = 20;
通过demo02的例子我们可以看出,变量提升好像确实不存在了。那么这是什么原因导致的呢?
我们知道通过var声明的变量,会分别经历两个步骤,首先给变量赋值一个undefined,并将整个操作提升到了作用域的前面。因此会有:
var a = 20;
// 等同于
var a = undefined; // 该操作提升
a = 20;
但是当我们通过let/const声明变量时,其实提升的操作仍然存在,但是将不会给这个变量赋值为undefined。也就是说,虽然声明提前了,但是该变量并没有任何引用。所以当我们进行如下操作时,会报referenceError
。即引用错误。
console.log(a); // ReferenceError: a is not defined
let a = 20;
正是因为不会默认赋值为undefined,加上let/const会存在自己的作用域,因此会出现一个叫做暂时性死区的现象。例如下面的例子。
var a = 20;
if (true) {
console.log(a); // ReferenceError: a is not defined
let a = 30;
}
此处虽然a已经声明过了,但是由于暂时性死区的存在,仍然无法正常访问。因此我们在自己的代码中,仍然要注意到这些异常,尽量将声明主动放置于代码的前面。
使用ES6,我们需要全面使用let/const来替换掉之前非常常用的var。那么现在的问题是,什么时候用let,什么时候用const呢?
我们常常使用let来声明一个引用可以被改变的变量,而使用const来声明一个引用不能被改变的变量。
例如我们会使用let来声明一个值总是会变的变量。
let a = 20;
a = 30;
a = 40;
console.log(a);
这里需要注意的是,a值的改变,其实是引用改变了。
我们会使用const来声明一个常量。
const PI = 3.1415;
const MAX_LENGTH = 100;
// 试图改变引用
PI = 3; // Untaught TypeError: Assignment to constant variable
除此之外,当我们声明一个引用类型的数据时,也会使用const。尽管我们可能会改变该数据的值,但是必须保持它的引用不变。
const a = [];
a.push(1);
console.log(a); // [1]
const b = {}
b.max = 20;
b.min = 0;
console.log(b); // { max: 20, min: 0 }
下面大家可以领会一个这个例子。想想能不能这样用?想完之后,在浏览器中运行试试看吧。
const arr = [1, 2, 3, 4];
arr.forEach(function(item) {
const temp = item + 1;
console.log(temp);
})
箭头函数(arrow function)
与function相比,箭头函数是一个用起来更加舒服的语法。我们一起来看看箭头函数的基本使用。
// es5
var fn = function(a, b) {
return a + b;
}
// es6 箭头函数写法,当函数直接被return时,可以省略函数体的括号
const fn = (a, b) => a + b;
// es5
var foo = function() {
var a = 20;
var b = 30;
return a + b;
}
// es6
const foo = () => {
const a = 20;
const b = 30;
return a + b;
}
需要注意的是,箭头函数只能替换函数表达式,也就是使用var/let/const声明的函数。而直接使用function声明的函数是不能使用箭头函数替换掉的。
除了写法的不同,箭头函数还有一个非常重要的特性需要我们掌握。
学习过之前的this专题的话我想大家应该能够知道,函数内部的this指向,与它的调用者有关。或者使用call/apply/bind也可以修改内部的this指向。
通过下面的例子简单复习一下。
var name = 'TOM';
var getName = function() {
console.log(this.name);
}
var person = {
name: 'Alex',
getName: getName
}
var other = {
name: 'Jone'
}
getName(); // 独立调用,this指向undefined,并自动转向window
person.getName(); // 被person调用,this指向person
getName.call(other); // call 修改this,指向other
明白了this的指向,那么就能够很简单的知道这几个不同的方法调用时会输出什么结果。但是当我们将最初声明的getName方法修改为箭头函数的形式,输出结果会发生什么变化呢?我们来看一下:
var name = 'TOM';
// 更改为箭头函数的写法
var getName = () => {
console.log(this.name);
}
var person = {
name: 'Alex',
getName: getName
}
var other = {
name: 'Jone'
}
getName();
person.getName();
getName.call(other);
通过运行我们发现,三次调用都输出了TOM。
这也正是我要跟大家分享的箭头函数的不同。箭头函数中的this,就是声明函数时所处上下文中的this,并且不会被其他方式所改变。
因此这个例子中,getName在全局上下文中声明,那么this就会指向window对象。所以输出的结果全是TOM。
在实践中,我们常常会遇到this在传递过程中的改变给我们带来的困扰。例如:
var Mot = function(name) {
this.name = name;
}
Mot.prototype = {
constructor: Mot,
do: function() {
console.log(this.name);
document.onclick = function() {
console.log(this.name);
}
}
}
new Mot('Alex').do();
这个例子中当我们调用do方法时,我们期望点击document时,仍然也会输出‘Alex’。但是很遗憾,在onclick的回调函数中,this的指向其实已经发生了变化,它指向了document,因此此时我们肯定就得不到我们想要的结果。通常的解决方案我相信大家应该知道,这里也可以使用箭头函数来避免这样的困扰。
var Mot = function(name) {
this.name = name;
}
Mot.prototype = {
constructor: Mot,
do: function() {
console.log(this.name);
// 修改为箭头函数即可
document.onclick = () => {
console.log(this.name);
}
}
}
new Mot('Alex').do();
除此之外,arguments还有一个需要大家注意的不同。在箭头函数中,没有arguments对象。
var add = function(a, b) {
console.log(arguments);
return a + b;
}
add(1, 2);
// 结果输出一个类数组对象
/*
[
0: 1,
1: 2,
length: 2,
callee: ƒ (a, b),
Symbol(Symbol.iterator): ƒ values()
]
*/
var add = (a, b) => {
console.log(arguments);
return a + b;
}
add(1, 2); // arguments is not defined