本文总结了 ES6 的新特性,用于复习。
ES 与 JS
ES 是一个语言标准,JS 实现了这个标准并进行了扩展。在浏览器环境下,就是 JS + DOM + BOM,在node环境下可以进行读写文件、网络等操作。
ES 从 2015 年开始,每年一个大版本迭代。ES6 有时指 ES2015,有时指 ES2015 起的所有新版本。
准备工作:使用浏览器运行即可,也可以使用nodemon。
ES6 的新特性
let
块级作用域
if(true){
var name = "zhangsan";
}
console.log(name)
es6
if(true){
let name = "zhangsan";
}
console.log(name) // 报错
另一个
for (var i = 0; i < 3; i++) {
for (var i = 0; i < 3; i++) {
console.log(i);
}
}
es6
for (let i = 0; i < 3; i++) {
for (let i = 0; i < 3; i++) {
console.log(i);
}
}
另一个
var elements = [{},{},{}]
for(var i = 0;i<3;i++){
elements[i].onclick = function(){
console.log(i);
}
}
elements[2].onclick() // 3
可用闭包解决
var elements = [{}, {}, {}]
for (var i = 0; i < 3; i++) {
elements[i].onclick = (function (i) {
return function () {
console.log(i);
}
})(i)
}
elements[0].onclick()
es6
var elements = [{}, {}, {}]
for (let i = 0; i < 3; i++) {
elements[i].onclick = function () {
console.log(i);
}
}
elements[1].onclick()
且 for 循环中let有两个作用域
for (let i = 0; i < 3; i++) {
let i = "hello"
console.log(i)
}
// 打印 3 次 hello
let 不会变量提升
console.log(foo) // undefined
var foo = "hello"
而 es6
console.log(foo) // 报错
let foo = "hello"
const
在 let 基础上添加只读特性。const 只能在声明时赋值。
const obj = {}
obj.name = 'zs' // ok
obj = {} // error
最佳实践:不用 var,主用 const,配合使用 let。
数组解构
const arr = [1, 2, 3]
const [foo, bar] = arr
const [, , baz, qux, foobar = 100] = arr
const [, ...rest] = arr
console.log(foo, bar, baz, qux, foobar) // 1 2 3 undefined 100
console.log(rest) // [ 2, 3 ]
对象解构
const obj = { name: 'zs', age: 20 }
const { name, gender = '男', age: myAge = 18 } = obj
console.log(name, gender, myAge)
模板字符串
const name = 'zhangsan'
const str = `
hello \`html\`,
I am ${name}, I am ${10 + 8} years old.
`
console.log(str)
带标签的模板字符串
const str = console.log`hello world!` // [ 'hello world!' ]
const name = 'Jack'
const gender = 'boy'
function func(strings, name, gender) {
console.log(strings)
console.log(name)
console.log(gender)
}
const result = func`My name is ${name}, I am a ${gender}.`
/*
[ 'My name is ', ', I am a ', '.' ]
Jack
boy
*/
字符串的扩展方法
- includes
- startsWith
- endsWith
const message = 'Error: foo is not defined.'
console.log(message.startsWith('Error'));
console.log(message.endsWith('.'));
console.log(message.includes('foo'));
参数默认值
ES5:
function foo(enable) {
enable = enable === undefined ? true : enable
console.log(enable)
}
ES6:
function foo(enable = true) {
console.log(enable)
}
注:默认参数需要在参数列表的最后。
剩余参数
ES5:
function foo() {
console.log(arguments)
}
foo(1, 2, 3, 4) // [Arguments] { '0': 1, '1': 2, '2': 3 }
ES6:
function foo(...args) {
console.log(args)
}
foo(1, 2, 3)
注:剩余参数需要在参数列表的最后,且只能出现一次。
展开数组
ES5:
const arr = ['foo', 'bar', 'baz']
console.log.apply(console, arr) // foo bar baz
ES6:
const arr = ['foo', 'bar', 'baz']
console.log(...arr) // foo bar baz
箭头函数
const func = n => n + 1
console.log(func(1)) // 2
const foo = (n, m) => {
return n + m
}
箭头函数不会改变 this 的指向。
const person = {
name: 'tom',
say1: function () {
console.log('1:' + this.name)
},
say2: () => {
console.log('2:' + this.name)
},
say3: function () {
setTimeout(function () {
console.log('3:' + this.name)
})
},
say4: function () {
const _this = this
setTimeout(function () {
console.log('4:' + _this.name)
})
},
say5: function () {
setTimeout(() => {
console.log('5:' + this.name)
})
},
say6: () => {
setTimeout(() => {
console.log('6:' + this.name)
})
}
}
person.say1() // 1:tom
person.say2() // 2:undefined
person.say3() // 3:undefined
person.say4() // 4:tom
person.say5() // 5:tom
person.say6() // 6:undefined
对象字面量增强
const bar = '345'
const obj = {
foo: 123,
// bar: bar,
bar,
func1: function () { },
func2() { }, // this 指向与普通 function 定义的方法相同
[Math.random()]: 123,
[bar]: '345'
}
console.log(obj)
/*
{
'345': '345',
foo: 123,
bar: '345',
func1: [Function: func1],
func2: [Function: func2],
'0.18417476633785768': 123
}
*/
对象扩展的方法
- Object.assign 将多个源对象的属性复制到一个目标属性中
const source1 = {
a: 1,
b: 2
}
const source2 = {
b: 3,
d: 4
}
const target = {
a: 100,
c: 200
}
const result = Object.assign(target, source1, source2)
console.log(result) // { a: 1, c: 200, b: 3, d: 4 }
console.log(result === target) // true
const obj = Object.assign({}, source1, source2) // 浅拷贝
- Object.is
判断某些特殊情况下的相等性
console.log(
+0 === -0, // true
Object.is(+0, -0), // false
NaN === NaN, // false
Object.is(NaN, NaN) // true
)
监听对象属性的变化
ES5: Object.defineProperty
ES6: Proxy
const person = {
name: 'zs',
age: 20
}
const proxy = new Proxy(person, {
get(target, property) {
// console.log(target, property)
// return 100
return property in target ? target[property] : '默认'
},
set(target, property, value) {
if (property === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('error')
}
}
target[property] = value
// console.log(target, property, value)
},
deleteProperty(target, property) {
console.log('delete', property)
delete target[property]
}
})
console.log(proxy.name) // zs
console.log(proxy.score) // 默认
delete proxy.name // delete name
console.log(person) // { age: 20 }
proxy.age = '20' // error
使用 Proxy 监视数组
const list = []
const proxy = new Proxy(list, {
set(target, property, value) {
console.log('set', property, value)
target[property] = value
return true // 表示设置成功
}
})
proxy.push(1)
/*
set 0 1
set length 1
*/
Reflect
只提供静态方法,内部封装了一系列操作对象的方法
const obj = {
name: 'zs',
age: 20
}
// ES5 中操作对象的方式太过凌乱
console.log('name' in obj)
console.log(Object.keys(obj))
console.log(delete obj['age'])
// ES6 统一了操作对象的方法
console.log(Reflect.has(obj, 'name'))
console.log(Reflect.ownKeys(obj))
console.log(Reflect.deleteProperty(obj, 'age'))
Class
ES5:
function Person(name) {
this.name = name
}
Person.prototype.say = function () {
console.log(this.name)
}
ES6:
class Person {
constructor(name) {
this.name = name
}
say() {
console.log(this.name)
}
static eat() {
console.log('eat')
}
}
const person = new Person('张三')
person.say() // 张三
Person.eat() // eat
class Student extends Person {
constructor(name, number) {
super(name)
this.number = number
}
speak() {
super.say()
console.log('Hi')
}
}
const student = new Student('李四', 2020)
student.speak() // 李四 Hi
Set 集合
const s = new Set()
s.add(1).add(2).add(1)
console.log(s) // Set { 1, 2 }
console.log(s.size) // 2
console.log(s.has(100)) // false
s.forEach(i => console.log(i)) // 1 2
console.log(s.delete(1)) // true
console.log(s) // Set { 2 }
s.clear()
console.log(s) //Set {}
数组去重
const arr = [1, 2, 1, 3, 4, 1, 1]
const arr2 = Array.from(new Set(arr))
const arr3 = [...new Set(arr)]
console.log(arr2) // [ 1, 2, 3, 4 ]
console.log(arr3) // [ 1, 2, 3, 4 ]
Map
普通对象中的键都是字符串,不是字符串的也会被转为字符串
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ name: 'zs' }] = '张三'
console.log(Object.keys(obj)) // [ '123', 'true', '[object Object]' ]
console.log(obj['[object Object]']) // 张三
ES6 中的 Map
const m = new Map()
const obj = { name: 'tom' }
m.set(obj, 99)
// m.has()
// m.delete()
// m.clear()
console.log(m) // Map { { name: 'tom' } => 99 }
console.log(m.get(obj)) // 99
m.forEach((key, val) => { // 99 { name: 'tom' }
console.log(key, val)
})
Symbol
// 引入第三方插件,由于对象的属性都是字符串
// 可能会引发一些错误
// plugin.js
const plugin = {}
// a.js ========= 在 a.js 中扩展
plugin['foo'] = '1'
// b.js ========= 在 b.js 中扩展
plugin['foo'] = '2' // 发生冲突,覆盖了
ES6 中的 Symbol 是一个新的原始类型,表示独一无二的值。每一个通过 Symbol 函数创建的值都是不同的
const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // Symbol
console.log(Symbol() === Symbol()) // false
为了便于打印调试,Symbol 函数可以传递一个字符串
console.log(Symbol('foo')) // Symbol(foo)
console.log(Symbol('bar')) // Symbol(bar)
console.log(Symbol('foo') === Symbol('foo')) // false
可以将 Symbol 用于对象属性,避免属性名冲突:
const a = Symbol()
const b = Symbol()
const obj = {
[a]: 1,
[b]: 2
}
console.log(obj) // { [Symbol()]: 1, [Symbol()]: 2 }
console.log(obj[a]) // 1
由于 Symbol 的唯一性,可以用它实现对象的私有属性:
如,在 a.js 中创建一个对象,其中 name 属性想要设为私有,则:
// a.js ===========
const name = Symbol()
const obj = {
[name]: '张三',
say() {
console.log(this[name])
}
}
这样在 b.js 中便不能访问 obj 的 name 属性了:
// b.js ===========
console.log(obj['name']) // undefined
console.log(obj[Symbol()]) // undefined
注:Symbol 目前最主要的作用就是为对象添加一个独一无二的属性名,且最常用于私有属性。无法通过
for ... in,Object.keys,JSON.stringify获取对象的 Symbol 属性。可以通过Object.getOwnPropertySymbols(obj)获取对象的 Symbols 属性。
如果想创建两个一样的 Symbol,可以使用 Symbol.for() 函数:
console.log(Symbol.for('foo') === Symbol.for('foo')) // true
Symbol.for() 使用字符串与 Symbol 对象做关联,因此会将传入的参数转为字符串类型。
console.log(Symbol.for(true) === Symbol.for('true')) // true
Symbol 内部有一些静态属性,用于实现不同目的:
const obj = {
[Symbol.toStringTag]: 'Oh My Object' // 重写对象的 toString 的返回值
}
console.log('' + obj) // [object Oh My Object]
// console.log(Symbol.iterator)
// console.log(Symbol.hasInstance)
遍历
- for 遍历数组
- for...in 遍历键值对
- arr.forEach 遍历数组,不能 break 循环
- arr.some, arr.every 遍历数组,可以提前终止
- arr.map 遍历数组,不能提前终止
以上的遍历方式都有一定的局限性。ES6 引入了全新的 for...of 循环,用于遍历所有数据结构的统一方式。
const arr = [1, 2, 3, 4, 5]
for (const item of arr) {
console.log(item)
if (item > 3) break
}
for...of 还能遍历伪数组,Set,Map。
const map = new Map()
map.set('zs', 23)
map.set('ls', 25)
for (const item of map) { // 也可用数组解构语法 for (const [key, value] of map)
console.log(item)
}
/*
[ 'zs', 23 ]
[ 'ls', 25 ]
*/
ES6 提供了 Iterable 接口,要想使用 for...of ,必须先实现 Iterable 接口。如,使用 for...of 遍历 Set 对象是可以的,因为 Set 实现了这个接口:
const set = new Set([1, 2])
const iterator = set[Symbol.iterator]()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: undefined, done: true }
for...of 通过调用每个对象迭代器的 next 方法实现遍历。
for...of 不能直接遍历对象,要实现 Iterable 接口:
const obj = { // 实现 Iterable 接口
nums: [1, 2, 3, 4],
[Symbol.iterator]() {
let index = 0
const self = this
return { // 实现 Iterator 接口
next() {
const res = { // 实现 IterationResult 接口
value: self.nums[index],
done: index >= self.nums.length
}
index += 1
return res
}
}
}
}
for (const o of obj) {
console.log(o) // 1 2 3 4
}
迭代器模式
实现一:
const todoList = {
life: ['吃饭', '睡觉'],
study: ['英语', '数学'],
work: ['设计', '写代码'],
each(callback) {
const list = [].concat(this.life, this.study, this.work)
list.forEach(item => callback(item))
}
}
todoList.each(item => console.log(item)) // 吃饭 睡觉 英语 数学 设计 写代码
实现二:
const todoList = {
life: ['吃饭', '睡觉'],
study: ['英语', '数学'],
work: ['设计', '写代码'],
each(callback) {
const list = [].concat(this.life, this.study, this.work)
list.forEach(item => callback(item))
},
// 实现 Iterable 接口
[Symbol.iterator]() {
const list = [...this.life, ...this.study, ...this.work]
let index = 0
return {
next() {
return {
value: list[index],
done: index++ >= list.length
}
}
}
}
}
// todoList.each(item => console.log(item))
for (const item of todoList) { // 吃饭 睡觉 英语 数学 设计 写代码
console.log(item)
}
生成器
先来看一下生成器函数的简单使用。
function* foo() {
console.log('test')
return 1
}
const gen = foo()
console.log(gen.next()) // test { value: 1, done: true }
通过在 foo 函数定义时加上一个 * 号,便定义了一个生成器函数。直接调用 foo 函数,会生成一个生成器对象,再调用生成器对象的 next 方法,会得到前面所说的相同的 IterationResult 接口,即 { value: 1, done: true }。
生成器函数通常配合 yield 关键字使用。
function* foo() {
console.log(1)
yield 100
console.log(2)
yield 200
console.log(3)
yield 300
}
const gen = foo()
console.log(gen.next()) // 1 { value: 100, done: false }
console.log(gen.next()) // 2 { value: 200, done: false }
console.log(gen.next()) // 3 { value: 300, done: false }
console.log(gen.next()) // { value: undefined, done: true }
应用:
- ID 自动增长
function* createIDMaker() {
let id = 1
while (true) {
yield id++
}
}
const IDMaker = createIDMaker()
console.log(IDMaker.next().value) // 1
console.log(IDMaker.next().value) // 2
console.log(IDMaker.next().value) // 3
- 迭代器
const todoList = {
life: ['吃饭', '睡觉'],
study: ['英语', '数学'],
work: ['设计', '写代码'],
// 实现 Iterable 接口
[Symbol.iterator]: function* () {
const list = [...this.life, ...this.study, ...this.work]
for (const item of list) {
yield item
}
}
}
for (const item of todoList) { // 吃饭 睡觉 英语 数学 设计 写代码
console.log(item)
}
ES 2016
数组的 includes 方法
首先看 ES5 中数组的 indexOf 方法:
const arr = ['foo', 1, NaN, false]
console.log(arr.indexOf('foo')) // 0
console.log(arr.indexOf(1)) // 1
console.log(arr.indexOf(false)) // 3
console.log(arr.indexOf(NaN)) // -1
indexOf 不能查找 NaN,而 includes 方法可以:
const arr = ['foo', 1, NaN, false]
console.log(arr.includes('foo')) // true
console.log(arr.includes(1)) // true
console.log(arr.includes(false)) // true
console.log(arr.includes(NaN)) // true
指数运算符
ES5 中使用 Math.pow 来进行指数运算,ES 2016 新增了指数运算符 ** 。
console.log(Math.pow(2, 10)) // 1024
console.log(2 ** 10) // 1024
ES 2017
Object.values
返回对象中所有值组成的数组:
const obj = {
foo: 'value1',
bar: 'value2'
}
console.log(Object.values(obj)) // [ 'value1', 'value2' ]
Object.entries
返回对象中所有键值对组成的数组:
const obj = {
foo: 'value1',
bar: 'value2'
}
console.log(Object.entries(obj)) // [ [ 'foo', 'value1' ], [ 'bar', 'value2' ] ]
如:
const obj = {
foo: 'value1',
bar: 'value2'
}
for (const [key, value] of Object.entries(obj)) {
console.log(key, value)
}
/*
foo value1
bar value2
*/
const map = new Map(Object.entries(obj))
console.log(map) // Map { 'foo' => 'value1', 'bar' => 'value2' }
Object.getOwnPropertyDescriptors
返回给定对象所有的属性描述。
const obj = {
name: 'zs'
}
console.log(Object.getOwnPropertyDescriptors(obj))
/*
{
name: { value: 'zs', writable: true, enumerable: true, configurable: true }
}
*/
字符串 padStart / padEnd
字符串填充方法
const books = {
html5: 30,
css3: 8,
javascript: 120
}
for (const [name, count] of Object.entries(books))
console.log(name.padEnd(12, '-') + count.toString().padStart(3, '0'))
/*
html5-------030
css3--------008
javascript--120
*/
函数参数支持尾逗号
function foo(
bar,
baz, // 此处添加逗号不报错
) { }
async / await
async / await 及异步编程会开一篇文章进行讲解。