一. ECMAScript概述
1. ECMAScript与Javascript直接的关系
ECMAScript只提供最基本的语法,而Javascirpt还包含运行平台提供的API。
在浏览器环境中的 Javascript相当于:ECMAScript 加上 Web APIs(BOM、DOM)。
在node环境中的 Javascript相当于: ECMAScript 加上 Node APIs(fs net etc.)。
2. ECMAScript(ES)版本迭代
从2015年开始ES开始保持每年一个版本的迭代。
ES2015就是ES6,后面为了方便都以新版本的发布年份命名,如ES2016、ES2017、ES2109等。
3. ECMAScript2015这个版本,相比之前的版本更新非常大,其新特性主要分为如下几类:
解决原有语法上的问题或不足
对原有语法进行增强
全新的对象 方法 功能
全新的数据类型和数据结构
二. ECMAScript2015新特性
1. let、const 的块级作用域
ES2015以前,变量的作用域只有全局和函数内部。
在ES2015中新增的let、const变量声明关键字,其作用域新增了块级作用域(就是{}括起来的范围)。
if(true){
let foo = 'abc'
}
console.log(foo) // 会报错 ReferenceError: foo is not defined
最佳实践:不用var 主用const 配合let。
2. 数组和对象的解构
数组的解构:
const arr = [100, 200, 300]
const [a, b, c] = arr
console.log(a, b, c) // 100, 200, 300
const [, e] = arr
console.log(e) // 200
const [foo, ...rest] = arr
console.log(foo) // 100
console.log(rest) // [200, 300]
const [a1 = 123, b1, c1, d1 = 'four'] = arr // 解构中设置的默认值仅在取不到相应数据时生效
console.log(a1, b1, c1 ,d1) // 100 200 300 four
对象的解构:
const obj = { name: 'jack', age: 18 }
const { name: objName } = obj
console.log(objName) // jack
const { age = 10, sex = 'male', remark } = obj
console.log(age, sex, remark) // 18 male undefined
3. 模板字符串、字符串的拓展方法
传统的字符串不支持换行,如果有换行的话需要手动拼入 /n
而模板字符串直接敲换行,就会生成换行, 对于输出html字符串非常方便。
const str = `hello,
es2015 ${1 + 5}`
console.log(str)
/*
hello,
es2015 6
*/
带标签的模板字符串,可对拼接内容进行处理:
const name = 'tom'
const gender = false
function myTag1(strings, name, gender){
console.log('strings: ', strings) // strings: [ 'Hey, ', ' is a ', '.' ]
const sex = gender ? 'man' : 'woman'
return strings[0] + name + strings[1] + sex + strings[2]
}
const result = myTag1`Hey, ${name} is a ${gender}.`
console.log(result) // Hey, tom is a woman.
字符串的拓展方法(startsWith、endsWith、includes):
const message = 'Error: foo is not defined.'
console.log(message.startsWith('Error'))
console.log(message.endsWith('.'))
console.log(message.endsWith('defined.'))
console.log(message.includes('foo'))
4. 参数默认值、剩余参数、数组展开
如果函数有多个参数,则有默认值的参数需要排在最后。
参数默认值:
function foo(arg1 = 10){
console.log(arg1)
}
foo() // 10
foo(20) //20
function bar(a, b, c = 3){
console.log(a, b, c)
}
bar(10, 20) // 10 20 3
剩余参数:
function f2(...args){
console.log(args) // args是存放了所有实参的数组
}
f2(1,2,3,4) // [ 1, 2, 3, 4 ]
function f3(first, ...args){ // ...args是剩余所有参数 所以只能用在形参最末尾
console.log(first)
console.log(args)
}
f3(1,2,3,4)
/*
1
[ 2, 3, 4 ]
*/
数组展开:
const arr = ['foo', 'bar', 'baz']
console.log(arr[0], arr[1], arr[2])
console.log.apply(console, arr) // 这里log方法是console调用的 所以首个参数传console
console.log(...arr) // foo bar baz
5. 箭头函数
箭头函数内的this就是函数外部的this。
const person = {
name: 'Lilei',
sayHiAsync: function () {
setTimeout(() => {
console.log(this.name)
}, 1000)
},
}
person.sayHiAsync() // Lilei
6. 对象字面量增强
const bar = 123
const obj = {
foo: 10,
bar,
// method1: function(){
// },
method1(){ // 省略function关键字 同用function声明等价
},
[1 + 2]: 456
}
console.log(obj) // { '3': 456, foo: 10, bar: 123, method1: [Function: method1] }
7. 对象的拓展方法(Object.assign、Object.is)
Object.assign:
const target = { b: 0, c: 0 }
const source1 = { a: 1, b: 2 }
const source2 = { a: 11, b: 22, c: 33 }
Object.assign(target, source1)
console.log(target) // { b: 2, c: 0, a: 1 }
const r = Object.assign(target, source1, source2)
console.log(r) // { b: 22, c: 33, a: 11 } 排在后面的参数会覆盖排在前面参数的值
Object.is:
const o1 = {id: 1}
const o2 = o1
console.log(o1 === o2) // true
console.log(Object.is(o1, o2)) // true
console.log(Object.is(NaN, NaN)) //true
console.log(Object.is(+0, -0)) // false
8. Proxy类
Proxy类的基本用法:
const person = {
name: 'Lily',
age: 18
}
const personProxy = new Proxy(person, {
get(target, property){
return property in target ? target[property] : 'default value'
},
set(target, property, value){
if(property === 'age'){
if(!Number.isInteger(value)){
throw new TypeError(`${value} is not an int`)
}
}
target[property] = value
}
})
console.log(personProxy.name)
personProxy.age = 20
console.log(personProxy)
console.log(personProxy.age)
/*
Lily
{ name: 'Lily', age: 20 }
20
*/
personProxy.age = 'heheda' // 报错 TypeError: heheda is not an int
ES2015 Proxy 对比 Object.defineProperty:
defineProperty 只能监视对象属性的读取和写入。
Proxy更强大 还能监视更多对对象的操作,如属性的删除 对象方法的调用等。
Proxy可监听对象属性的删除,可更好地监听数组对象,是以非侵入的方式监管了对象读写(不需要对要监视的对象本身做任何操作 就可以监视到对象内部成员的读写)。
Proxy 对比 Object.defineProperty:
可监听对象属性的删除:
const person = {
name: 'Lily',
age: 18
}
const personProxy = new Proxy(person, {
deleteProperty(target, property) {
console.log('in delete: ', property)
delete target[property]
}
})
delete personProxy.age
console.log(personProxy)
console.log(person)
/*
in delete: age
{ name: 'Lily' }
{ name: 'Lily' }
*/
可更好地监听数组对象
vue2.0中想监听数组 是通过重写数组的操作方法 如push shift等
vue3.0中使用了Proxy 就不用重写数组操作方法了:
const list = []
const listProxy = new Proxy(list, {
set(target, property, value){
console.log('in set: ', property, value)
target[property] = value
return true // 表示设置成功
}
})
listProxy.push(100)
setTimeout(() => {
listProxy.push(200)
}, 2000)
/*
in set: 0 100
in set: length 1
in set: 1 200
in set: length 2
*/
9. Relect 统一的对象操作API
Refelct是一个静态类,封装了一些对对象的底层操作,推荐用它操作对象。
const obj = {
name: 'abec',
age: 18
}
console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))
/*
true
true
[ 'name' ]
*/
10. Promise
ES2015 Promise类,详见:异步编程-学习笔记 https://mp.csdn.net/editor/html/109552446
11. class类、类的静态方法、类的继承
class Person {
constructor(name){
this.name = name
}
say(){
console.log(`hi, my name is ${this.name}`)
}
static create(name){
console.log(this) // [class Person] 静态方法里的this指向类型而不是实例
return new Person(name)
}
}
var p2 = new Person('Judy')
p2.say()
const p3 = Person.create('Mom')
p3.say()
class Student extends Person{
constructor(name, number){
super(name) // super始终指向父类 通过super调用父类的方法
this.number = number
}
hello(){
super.say() // 调用父类的say()
console.log('my school number is: ', this.number)
}
}
const s = new Student('jack', '100')
s.hello()
12. Set、Map
Set是一个无重复值的集合:
const s = new Set()
s.add(1).add(2).add(2)
console.log(s) // Set(2) { 1, 2 }
s.forEach(i => console.log(i))
for(let i of s){
console.log(i)
}
// Set数据转换成Array
console.log(Array.from(set)) // [1, 2]
console.log([...set]) // [1, 2]
Map 数据结构是一个键值对对象,可用任意类型的数据作为键。
而普通对象只能用字符串作为键(传入非字符串的键会被转化为字符串作为键)。
const map = new Map()
const tom = { name: 'tom' }
map.set(tom, 90)
console.log(map) // Map(1) { { name: 'tom' } => 90 }
console.log(map.has(tom)) // true
console.log(map.delete(tom)) // true
13. Symbol
Symbol一个独一无二的符号。
字符串 和 Symbol符号 都可以作为对象的属性名。
目前Symbol最主要的作用就是为对象添加一个独一无二的属性名。
console.log(Symbol('a') === Symbol('a')) // false
const name = Symbol('name')
const obj = {
[name]: 'Jack'
}
console.log(obj) // { [Symbol(name)]: 'Jack' }
console.log('name: ' + obj[name]) // name: Jack
Symbol.for 维护一个全局内唯一的以字符串为标识的Symbol:
const s1 = Symbol.for('name') // Symbol.for 只认字符串
const s2 = Symbol.for('name')
console.log(s1 === s2) // true
Symbol.toStringTag 修改对象toString()方法的返回值:
const obj1 = {}
console.log(obj1.toString()) // [object Object]
const obj2 = { [Symbol.toStringTag]: 'MyObject' }
console.log(obj2.toString()) // [object MyObject]
Symbol类型作为对象属性名时不可被枚举,用 for...in、Object.keys 和 JSON.stringify都访问不到,
可以用 Object.getOwnPropertySymbols 获取对象内部的Symbol的属性们:
const obj3 = {
[Symbol('id')]: 'symbol-value',
age: 20,
memo: 'memo'
}
for(var key in obj3){
console.log(key)
}
/*
age
memo
*/
console.log(Object.keys(obj3)) // [ 'age', 'memo' ]
console.log(JSON.stringify(obj3)) // {"age":20,"memo":"memo"}
// 可以用 Object.getOwnPropertySymbols 获取对象内部的Symbol的属性们
const symborProp = Object.getOwnPropertySymbols(obj3)[0]
console.log(obj3[symborProp]) // symbol-value
14. for...of循环
作为遍历所有(实现了Iterable接口的)数据结构的统一方式,还可以用break终止遍历。
可以遍历Array、Set、Map、arguments、节点列表等。
但不可遍历对象,因为对象未实现Iterable接口。
const arr = [10, 20, 30, 40]
for(const item of arr){
console.log(item)
if(item > 20){
break; // 终止遍历
}
}
/*
10
20
30
*/
15. ES2015可迭代接口、迭代器模式
实现可迭代接口Iterable:
const obj = {
store: ['a', 'b', 'c'],
[Symbol.iterator]: function(){
let index = 0
const self = this
return {
next: function(){
const result = {
value: self.store[index],
done: index >= self.store.length
}
index++
return result
}
}
}
}
for(const item of obj){
console.log('循环 ', item)
}
/*
循环 a
循环 b
循环 c
*/
迭代器模式:对外提供统一遍历接口 不用关心数据内部的结构:
// 迭代器模式: 对外提供统一遍历接口 不用关心数据内部的结构
const todos = {
life: ['吃饭', '睡觉'],
learn: ['Java', 'Php'],
work: ['喝茶'],
[Symbol.iterator]: function(){
const all = [...this.life, ...this.learn, ...this.work]
let index = 0
return {
next: function(){
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
for(const item of todos){
console.log('todo: ', item)
}
/*
todo: 吃饭
todo: 睡觉
todo: Java
todo: Php
todo: 喝茶
*/
16. Generator生成器函数
Generator生成器函数,目的是为了避免异步编程中回调嵌套过深的问题。
调用一个生成器函数 会返回一个生成器对象。
调用生成器对象的next方法,生成器函数内部代码才开始执行,
执行到遇到的下一个yield语句则中断执行。
再次次调用生成器对象的next方法,生成器函数内部代码继续往下执行,
执行到遇到的下一个yield语句。
直到函数执行完毕。
yield 关键字后面的数据将会返回给引发它执行的next。
function * foo(){
console.log('11')
yield 100
console.log('22')
yield 200
console.log('33')
yield 300
}
const generatorObj = foo()
console.log(generatorObj.next())
/*
11
{ value: 100, done: false }
*/
console.log(generatorObj.next())
/*
22
{ value: 200, done: false }
*/
console.log(generatorObj.next())
/*
33
{ value: 300, done: false }
*/
Generator生成器函数应用:
案例1 发号器:
// 案例1 发号器
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
案例2 使用Generator函数实现 iterator 方法:
const todos = {
life: ['吃饭', '睡觉'],
learn: ['Java', 'Php'],
work: ['喝茶'],
// [Symbol.iterator]: function(){
// const all = [...this.life, ...this.learn, ...this.work]
// let index = 0
// return {
// next: function(){
// return {
// value: all[index],
// done: index++ >= all.length
// }
// }
// }
// }
[Symbol.iterator]: function * () {
const all = [...this.life, ...this.learn, ...this.work]
for(const item of all){
yield item
}
}
}
for(const item of todos){
console.log('todo: ', item)
}
/*
todo: 吃饭
todo: 睡觉
todo: Java
todo: Php
todo: 喝茶
*/
17. ES Modules - 语言层面的模块语法
ES Modules 语言层面的模块化规范,此处暂不详说。
三. ECMAScript2016新特性
ECMAScript2016新增了两个小功能 数组的includes方法、指数运算符 **
const arr = ['a', 11, NaN]
console.log(arr.includes('a')) //true
console.log(arr.indexOf(NaN)) // -1 indexOf无法检测NaN的存在
console.log(arr.includes(NaN)) // true includes可以检测NaN
// 求2的10次方 2是底数 10是指数
console.log(Math.pow(2, 10)) // 1024
console.log(2 ** 10) // 1024
四. ECMAScript2017新特性
ECMAScript2017新增了如下新特性:
Object.values 和 Object.entries
Object.getOwnPropertyDescriptors
String.prototype.padStart / String.prototype.padEnd(为字符串前后填充字符)
允许在函数参数中添加尾逗号
标准化了 Async / Await (使用promise的语法糖)。
示例:
Object.values 和 Object.entries:
const obj = {
foo: 'value1',
bar: 'value2'
}
console.log(Object.values(obj)) // [ 'value1', 'value2' ]
console.log( Object.entries(obj) ) // [ [ 'foo', 'value1' ], [ 'bar', 'value2' ] ]
for(const [key, value] of Object.entries(obj)){
console.log(key, value)
}
/*
foo value1
bar value2
*/
console.log(new Map(Object.entries(obj)))
/*
Map(2) { 'foo' => 'value1', 'bar' => 'value2' }
*/
Object.getOwnPropertyDescriptors:
const p1 = {
firstName: 'Lei',
lastName: 'Wang',
get fullName(){
return this.firstName + ' ' + this.lastName
}
}
console.log(p1.fullName) // Lei Wang
// 不能正确拷贝带有 get 方法的对象
const p2 = Object.assign({}, p1)
console.log(p2) // { firstName: 'Lei', lastName: 'Wang', fullName: 'Lei Wang' }
p2.firstName = 'Abc'
console.log(p2) // { firstName: 'Abc', lastName: 'Wang', fullName: 'Lei Wang' }
// 用Object.getOwnPropertyDescriptors 配合 Object.defineProperties正确拷贝
const descriptors = Object.getOwnPropertyDescriptors(p1)
const p22 = Object.defineProperties({}, descriptors)
console.log(p22) // { firstName: 'Lei', lastName: 'Wang', fullName: [Getter] }
p22.firstName = 'Bbb'
console.log(p22, p22.fullName) // { firstName: 'Bbb', lastName: 'Wang', fullName: [Getter] } Bbb Wang
String.prototype.padStart / String.prototype.padEnd:
const obj = {
name: 'Juddy',
age: 22,
number: 123456
}
for(const [key, value] of Object.entries(obj)){
const text = `${key} | ${value}`
console.log(text)
}
/* 打印效果不整齐
name | Juddy
age | 22
number | 123456
*/
for(const [key, value] of Object.entries(obj)){
const text = `${key.padEnd(10, '-')} | ${value.toString().padStart(10, ' ')}`
console.log(text)
}
/* 打印效果整齐
name------ | Juddy
age------- | 22
number---- | 123456
*/
允许在函数参数中添加尾逗号:
function add (a, b,){
console.log('arguments: ', arguments.length) // arguments: 2
}
add(1, 2)
console.log('length: ', add.length) // length: 2
本文 完。