本文是自 ES6 以来,对常用的特性的一个总结,关于每个特性的内容,都有单独的用法讲述,以便快速学习与查阅
ES6(ECMAScript 2015)
ES6 的特性比较多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化。
1. let 与 const
在 ES6 规范还没制定之前,我们是这么声明变量的: var number = 1
而 var
是没有块级作用域的,会导致一些不必要的麻烦
let
和 const
的声明方式和 var
是一样的
let
用于声明变量
使用
let number = 1
let string = 'hello'
const 与 var 的区别
都是用来声明 变量
let 有块级作用域,是更完美的 var
我基本不用var
1. var 可声明前置
并且可以重复声明
a = 1
var a
var a = 2
// 不报错
2. let 不可以声明前置
a = 1
let a
// 报错
3. let 不可重复声明
let a = 1
let a = 2
// 报错
let a = 1
var a = 2
// 报错
4. let 存在块级作用域
作用域可以认为是两个大括号之间的区域
function fn() {
let a = 1
console.log(a)
}
console.log(a)
// 报错
5. let 暂时性死区
在 let 声明变量之前都是该变量的死区,在死区内不可使用该变量
这也需要拿出来说?
const
用来声明 常量
拥有 let 的特性,但是被声明的常量必须赋值,而且「值」不可改变!
被 const 声明的常量不可改变
示例1:
const a = 1
a = 2 // 报错
能理解!简单嘛!
示例2:
const obj = { a: 1 }
obj.a = 2 // 不报错
obj = { a: 2 } // 报错
啊你骗人,你明明说了不可改变,那为什么 obj.a = 2 不报错?!?!
我们先来说说示例1:
示例1的 a 是简单类型,所以直接赋值的话,相当于改变 a 的值
再说说示例2:
- 这里的 obj 是属于引用类型,或者复杂类型的时候,那 obj 存的就是 { a: 1 } 的地址啊!那只要地址不变,那就没有问题啊!
那给 obj.a = 2,那 obj 里面存的地址变没变啊?
因为指向的还是这个对象,只不过对象里面的东西发生了改变。
- 但是这么写 obj = { a: 2 },这个意思是我新生成了个对象,然后赋值给 obj,那这个 obj 的地址是不是变了呀?那就会报错嘛!
哦哦...似懂非懂
2. 箭头函数
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的
this
,arguments
,super
或new.target
等,也不能用作构造函数。
语法上
let fn = n => {n + 1} // 只有一个参数的话,小括号可以省略
// 等价于
let fn = function (n) {return n + 1}
let fn = () => 1 // 注意对比返回值
// 等价于
let fn = function () {return 1}
let fn = (a, b) => a + b // 看出区别了吗,直接 return 可以省略大括号
// 等价于
let fn = function (a, b) {return a + b}
关于 this
箭头函数没有
this
普通函数中的this
:
- 在简单调用中, 非严格模式 下指向
window
对象;
严格模式 下为undefined
。
function normal() {
console.log(this)
}
normal()
// => window {...}
// ------------------
'use strict';
function strictNormal() {
console.log(this)
}
strictNormal()
// => undefined
- 作为某个对象方法调用时,
this
指向该对象。
const obj = {
normal: function() {
console.log(this)
}
}
obj.normal()
// => { normal: f() }
- 被间接调用时,指向
call/apply/bind
的第一个参数;
若第一个参数为null
,this
的指向参考第一条的规则。
function normal() {
console.log(this)
}
const obj = {
name: 'Object'
}
normal.call(obj)
// => { name: 'Object' }
- 普通函数被用作构造函数,用
new
关键字构造实例时,this
指向新建的实例。
function Normal() {
console.log(this)
}
const normal = new Normal()
// => Normal {}
箭头函数中的this
:
- 箭头函数没有自己的this,箭头函数的this指向在定义(注意: 是定义时,不是调用时)的时候继承自外层第一个普通函数的this。所以,箭头函数中
this
的指向在它被定义的时候就已经确定了,之后永远不会改变。
由于箭头函数的this
由外部非箭头函数的this
决定,因此,若需要将一个函数作为回调函数去执行,并且不希望函数中的this
发生改变,这时用箭头函数最适合不过。如果是普通函数,则需要用bind()
进行this
绑定。
let obj = {
a: 10,
b: () => {
console.log(this.a) // undefined
console.log(this) // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
},
c: function() {
console.log(this.a) // 10
console.log(this) // {a: 10, b: ƒ, c: ƒ}
}
}
obj.b();
obj.c();
arguments 对象
普通函数: 普通函数内部,arguments
为特殊的类数组对象。包含了函数被调用时的参数列表。
箭头函数: 箭头函数内部是没有 arguments
对象,依赖于外部非箭头函数。 如果箭头函数想要拿到全部参数,也可以通过 剩余参数语法 获取到全部参数。
const arrow = (...arg) => {
console.log(arg)
}
arrow(1, [1, 2], [1, 2, 3])
// => [1, [1, 2], [1, 2, 3]]
3. Promise
Promise 是异步编程的一种解决方案,比传统的解决方案 callback 更加的优雅。它最早由社区提出和实现的,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。
不使用 ES6
嵌套两个 setTimeout 回调函数:
setTimeout(function()
{
console.log('Hello'); // 1秒后输出"Hello"
setTimeout(function()
{
console.log('Hi'); // 2秒后输出"Hi"
}, 1000);
}, 1000);
使用 ES6
-
第一步
return new Promise((resolve, reject) => { ... })
- 成功则调用
resolve(result)
- 失败则调用
reject(error)
resolve
和reject
会再去调用成功和失败函数
-
第二步
- 使用
.then(success, fail)
传入成功和失败函数
- 使用
let isGreenLight = true
const promise = new Promise((resolve, reject) => {
isGreenLight ? resolve('绿灯') : reject('红灯')
})
promise
.then((light) => {
console.log(`现在交通灯为${light},你可以大胆往前走了`)
})
.catch((light) => {
console.log(`现在交通灯为${light},你想走?那祝你好运!`)
})
.finally(() => {
console.log('但是最终 promise 还是执行了,灯也亮了')
})
// 现在交通灯为绿灯,你可以大胆往前走了
// 但是最终 promise 还是执行了,灯也是亮着的
上面的的代码使用两个 then 来进行异步编程串行化,避免了回调地狱
4. 解构赋值
解构赋值语法是 JavaScript 的一种表达式,可以方便的从数组或者对象中快速提取值赋给定义的变量。
获取数组中的值
从数组中获取值并赋值到变量中,变量的顺序与数组中对象顺序对应。
const foo = ["one", "two", "three", "four"];
var [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
//如果你要忽略某些值,你可以按照下面的写法获取你想要的值
const [first, , , last] = foo;
console.log(first); // "one"
console.log(last); // "four"
//你也可以这样写
const a, b; //先声明变量
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2
如果没有从数组中的获取到值,你可以为变量设置一个默认值。
const a, b;
[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7
通过解构赋值可以方便的交换两个变量的值。
const a = 1;
const b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
获取对象中的值
const student = {
name:'Ming',
age:'18',
city:'Shanghai'
};
const {name,age,city} = student;
console.log(name); // "Ming"
console.log(age); // "18"
console.log(city); // "Shanghai"
5. 函数参数默认值
ES6 支持在定义函数的时候为其设置默认值:
function foo(height = 50, color = 'red')
{
// ...
}
不使用默认值:
function foo(height, color)
{
var height = height || 50;
var color = color || 'red';
//...
}
这样写一般没问题,但当 参数的布尔值为false
时,就会有问题了。比如,我们这样调用 foo 函数:
foo(0, "")
因为 0的布尔值为false
,这样 height 的取值将是 50。同理 color 的取值为‘red’。
所以说,函数参数默认值
不仅能是代码变得更加简洁而且能规避一些问题。
6. 延展操作符
延展操作符...
可以在函数调用/数组构造时, 将数组表达式或者 string 在语法层面展开;还可以在构造对象时, 将对象表达式按 key-value 的方式展开。
语法
函数调用:
fn(...iterableObj)
数组构造或字符串:
[...iterableObj, '4', ...'hello', 6]
构造对象时,进行克隆或者属性拷贝(ECMAScript 2018 规范新增特性):
const objClone = { ...obj }
7. 模板字符串
ES6 支持模板字符串
,使得字符串的拼接更加的简洁、直观。
不使用模板字符串:
var name = 'Your name is ' + first + ' ' + last + '.'
使用模板字符串:
var name = `Your name is ${first} ${last}.`
8. 模块化
ES5 不支持原生的模块化,在 ES6 中模块作为重要的组成部分被添加进来。模块的功能主要由 export 和 import 组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过 import 来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。
导出(export)
ES6 允许在一个模块中使用 export 来导出多个变量或函数。
导出变量
//test.js
export var name = 'Rainbow'
心得:ES6 不仅支持变量的导出,也支持常量的导出。
export const sqrt = Math.sqrt;//导出常量
ES6 将一个文件视为一个模块,上面的模块通过 export 向外输出了一个变量。一个模块也可以同时往外面输出多个变量。
//test.js
var name = 'Rainbow';
var age = '24';
export {name, age};
导出函数
// myModule.js
export function myModule(someArg) {
return someArg;
}
导入(import)
定义好模块的输出以后就可以在另外一个模块通过 import 引用。
import {myModule} from 'myModule';// main.js
import {name,age} from 'test';// test.js
9. 类
对熟悉 Java,object-c,c#等纯面向对象语言的开发者来说,都会对 class 有一种特殊的情怀。ES6 引入了 class(类),让 JavaScript 的面向对象编程变得更加简单和易于理解。
class Animal {
// 构造函数,实例化的时候将会被调用,如果不指定,那么会有一个不带参数的默认构造函数.
constructor(name,color) {
this.name = name;
this.color = color;
}
// toString 是原型对象上的属性
toString() {
console.log('name:' + this.name + ',color:' + this.color);
}
}
var animal = new Animal('dog','white');//实例化Animal
animal.toString();
console.log(animal.hasOwnProperty('name')); //true
console.log(animal.hasOwnProperty('toString')); // false
console.log(animal.__proto__.hasOwnProperty('toString')); // true
class Cat extends Animal {
constructor(action) {
// 子类必须要在constructor中指定super 函数,否则在新建实例的时候会报错.
// 如果没有置顶consructor,默认带super函数的constructor将会被添加、
super('cat','white');
this.action = action;
}
toString() {
console.log(super.toString());
}
}
var cat = new Cat('catch')
cat.toString();
// 实例cat 是 Cat 和 Animal 的实例,和Es5完全一致。
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true
10. 对象属性简写
在 ES6 中允许我们在设置一个对象的属性的时候不指定属性名。
不使用 ES6
const name='Ming',age='18',city='Shanghai';
const student = {
name:name,
age:age,
city:city
};
console.log(student);//{name: "Ming", age: "18", city: "Shanghai"}
对象中必须包含属性和值,显得非常冗余。
使用 ES6
const name='Ming',age='18',city='Shanghai';
const student = {
name,
age,
city
};
console.log(student);//{name: "Ming", age: "18", city: "Shanghai"}
对象中直接写变量,非常简洁。
ES7(ECMAScript 2016)
1. Array.prototype.includes()
includes()
函数用来判断一个数组是否包含一个指定的值,如果包含则返回 true
,否则返回false
。
includes
函数与 indexOf
函数很相似,下面两个表达式是等价的:
arr.includes(x)
arr.indexOf(x) >= 0
复制代码
接下来我们来判断数字中是否包含某个元素:
在 ES7 之前的做法
使用indexOf()
验证数组中是否存在某个元素,这时需要根据返回值是否为-1 来判断:
let arr = ['react', 'angular', 'vue'];
if (arr.indexOf('react') !== -1) {
console.log('react存在');
}
使用 ES7 的 includes()
使用 includes()验证数组中是否存在某个元素,这样更加直观简单:
let arr = ['react', 'angular', 'vue'];
if (arr.includes('react')) {
console.log('react存在');
}
2. 指数操作符
ES8(ECMAScript 2017)
1. async/await
async/await 是以更舒适的方式使用 promise 的一种特殊语法,同时它也非常易于理解和使用。
基本语法
前面添加了async的函数在执行后都会自动返回一个Promise对象:
function foo() {
return 'imooc'
}
console.log(foo()) // 'imooc'
相当于
async function foo() {
return 'imooc' // Promise.resolve('imooc')
// let res = Promise.resolve('imooc')
// console.log(res)
}
console.log(foo()) // Promise
foo()
await后面需要跟异步操作,不然就没有意义,而且await后面的Promise对象不必写then,因为await的作用之一就是获取后面Promise对象成功状态传递出来的参数。
function timeout() {
return new Promise(resolve => {
setTimeout(() => {
console.log(1)
resolve() // resolve('success')
}, 1000)
})
}
// 不加async和await是2、1 加了是1、2
async function foo() {
await timeout() // let res = await timeout() res是success
console.log(2)
}
foo()
对于失败的处理
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('success')
reject('error')
}, 1000)
})
}
async function foo() {
return await timeout()
}
foo().then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
在async函数中使用await,那么await这里的代码就会变成同步的了,意思就是说只有等await后面的Promise执行完成得到结果才会继续下去,await就是等待。
2. Object.values()
Object.values() 返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同(for...in,但是for...in还会遍历原型上的属性值)。
const obj = {
name: 'baidu',
web: 'www.baidu.com'
}
console.log(Object.values(obj))
// ["baidu", "www.baidu.com"]
3. Object.entries()
4. String padding
5. 函数参数列表结尾允许逗号
此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。
function clownsEverywhere(
param1,
param2
) {
/* ... */
}
clownsEverywhere(
'foo',
'bar'
)
上面代码中,如果在param2或bar后面加一个逗号,就会报错。
如果像上面这样,将参数写成多行(即每个参数占据一行),以后修改代码的时候,想为函数clownsEverywhere添加第三个参数,或者调整参数的次序,就势必要在原来最后一个参数后面添加一个逗号。这对于版本管理系统来说,就会显示添加逗号的那一行也发生了变动。这看上去有点冗余,因此新的语法允许定义和调用时,尾部直接有一个逗号。
function clownsEverywhere(
param1,
param2,
) {
/* ... */
}
clownsEverywhere(
'foo',
'bar',
)
这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。