1.ES2015官方文档
https://262.ecma-international.org/6.0/
pdf地址:https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf
2.ES2015新特性分类
-
解决原有语法上的一些问题或者不足
-
对原有语法进行增强
-
全新的对象、全新的方法、全新的功能
-
全新的数据类型和数据结构
3.作用域
在ES2015之前,只存在两种作用域,全局作用域和函数作用域,在ES2015中新增了块级作用域。
if(true) {
var foo = 'foo'
}
console.log(foo)
// 输出为'foo', 使用var 没有块级作用域
if(true) {
let bar = 'bar'
}
// console.log(bar)
// ReferenceError: bar is not defined,使用let定义的变量只在块级作用域中有效
for(var i = 0; i < 3; i++) {
for(var i = 0; i < 3; i++) {
console.log(i)
}
}
// 只会输出0, 1, 2, 相当于只走了内层的for循环
for(let i = 0; i < 3; i++) {
for(let i = 0; i < 3; i++) {
console.log(i)
}
}
// 改成let后就会输出三次0, 1, 2,因为两个i只会在自己的作用域生效
// 模拟DOM元素上绑定点击事件
var elements = [{}, {}, {}]
for(var i = 0; i < elements.length; i++) {
elements[i].onclick = function () {
console.log(i)
}
}
elements[1].onclick()
// 无论调用哪个元素的点击事件都会输出3,因为var是全局作用域的变量,循环执行完成后i的值都会变成3
// 之前的解决方法是使用闭包来解决
var elements = [{}, {}, {}]
for(var i = 0; i < elements.length; i++) {
elements[i].onclick = (function (i) {
return function (){
console.log(i)
}
})(i)
}
elements[1].onclick()
// 输出为1
// 改为let声明就可以解决
var elements = [{}, {}, {}]
for(let i = 0; i < elements.length; i++) {
elements[i].onclick = function () {
console.log(i)
}
}
elements[1].onclick()
// 输出为当前i的值1
for (let i = 0; i < 3; i++){
let i = 'foo'
console.log(i)
}
// 输出三次foo,虽然变量名称相同,但是作用域互不影响,可以看成三次if判断
console.log(zoo)
// 因为var定义的变量存在声明提升,所以不会报错,打印的是undefined
var zoo = 'zoo'
// console.log(zoo1)
// let定义的变量不存在声明提升,所以报错,打印的是ReferenceError: Cannot access 'zoo1' before initialization
let zoo1 = 'zoo1'
const baz = 'baz'
// baz = 'baz1'
// const定义的变量无法再赋值,且必须在声明的时候赋值;TypeError: Assignment to constant variable.
const obj = {}
obj.name = 'cxz'
// 这里的赋值不会报错,是因为我们没有修改obj的内存地址的指针,只是修改了对应的值
最佳实践:不用var,主用const,配合let
4.数组的解构
因为数组有顺序,所以可以使用位置下标按顺序进行解构
const arr = [100, 200, 300]
// 之前数组的取值
// const foo = arr[0]
// const bar = arr[1]
// const baz = arr[2]
// console.log(foo, bar, baz)
// 输出:100 200 300
// 使用解构
const [, bar, baz] = arr
console.log(bar, baz)
// 输出:200 300
const [foo, ...rest] = arr
console.log(foo, rest)
// 输出:100 [ 200, 300 ], 使用...rest进行剩余成员收集
const [a, b, c, d = 400, e] = arr
console.log(a, b, c, d, e)
// 输出:100 200 300 400 undefined,可以赋值默认值, 如果取不到,就是undefined
// 获取字符串指定位置的值
const str = 'foo/bar/baz'
const strArr = str.split('/')
const str1 = strArr[1]
console.log(str1)
// 使用解构获取
const [, , str2] = str.split('/')
console.log(str2)
5.对象的解构
对象是没有顺序和下标的,所以必须按照key进行解构
const obj = { name: 'zxc', age: 18 }
const { name } = obj
console.log(name)
// 输出:zxc
// 如果当前作用域中已存在和key相同的变量,这时需要使用重命名
const age = 20
// const { age } = obj
// 会提示age已存在,需要使用以下写法
const { age: objAge } = obj
console.log(age, objAge)
// 输出:20 18
const { sex = 'man' } = obj
console.log(sex)
// 输出:man
// 如果需要在别名后面加默认值,直接加等号就行
const { level: objLevel = 2 } = obj
console.log(objLevel)
// 输出:2
// 也可以解构出log方法
const { log } = console
log(123)
// 输出:123
6.模板字符串
const str = `hello \'template string\'`
console.log(str)
// 支持换行
const str1 = `
hello
\' template string \'
`
console.log(str1)
// 支持插值表达式在字符串中嵌入变量的值
const name = 'template'
const str2 = `hello '${name} string'`
console.log(str2)
7.带标签的模板字符串
const str = console.log`tagged template`
// 输出:[ 'tagged template' ]
const name = 'tom'
const gender = true
function myTagFunc(strings, name, gender) {
console.log(strings)
// 输出:[ 'hey, ', ' is a ', '' ],是按照表达式分隔的静态内容
console.log(name, gender)
// 输出:tom true,可以得到差值表达式的返回值
// 这种好处就是可以更方便的处理字符串中的变量,例如:
const sex = gender ? 'man' : 'woman'
return strings[0] + name + strings[1] + sex + strings[2]
}
const result = myTagFunc`hey, ${name} is a ${gender}`
console.log(result)
// 输出:hey, tom is a man
8.字符串的扩展方法
includes()、endsWith()、startsWidth
const message = 'Error: foo is not defined.'
console.log(
message.startsWith('Error'),
message.endsWith('.'),
message.includes('foo')
)
// 输出:true true true
9.参数默认值
function foo(enable) {
// 之前默认值需要加判断,这种短路运算传入是false的时候,也会取默认值true
// enable = enable || true
// 正确的做法是判断是否是undefined
enable = enable === undefined ? true : enable
console.log(enable)
}
foo(false)
foo()
// 使用参数默认值的方式
function foo1(enable = true) {
console.log(enable)
}
foo1(false)
foo1()
10.剩余参数
function foo(){
console.log(arguments)
}
foo(1, 2, 3)
// 输出:[Arguments] { '0': 1, '1': 2, '2': 3 }
function foo1(...rest) {
console.log(rest)
}
foo1(1, 2, 3, 4)
// 输出:[ 1, 2, 3, 4 ]
// 因为...rest是全部的参数,所以只能放在最后,且只能使用一次
function foo2(first, ...rest) {
console.log(first, rest)
}
foo2(1, 2, 3, 4)
// 输出:1 [ 2, 3, 4 ]
11.展开数组
const arr = [0, 1, 2, 3]
console.log(
arr[0],
arr[1],
arr[2],
arr[3]
)
// 输出:0 1 2 3
// 如果参数不确定,之前我们需要使用apply方法获取实参
console.log.apply(console, arr)
// 输出:0 1 2 3
// 使用数组展开操作符
console.log(...arr)
// 输出:0 1 2 3
12.箭头函数
function foo(num) {
return num + 1
}
console.log(foo(100))
// 箭头函数
const foo1 = num => num + 1
console.log(foo1(100))
const arr = [1, 2, 3, 4, 5]
// 过滤奇数
const odd = arr.filter(function (num) {
return num % 2
})
console.log(odd)
// 箭头函数
const odd1 = arr.filter(num => num % 2)
console.log(odd1)
13.箭头函数与this
const person = {
name: 'tom',
sayHi: function () {
console.log(`Hi, my name is ${this.name}`)
// 输出:Hi, my name is tom
}
}
person.sayHi()
// 改成箭头函数的方式
const person1 = {
name: 'tom',
sayHi: () => {
console.log(`Hi, my name is ${this.name}`)
// 输出:Hi, my name is undefined,因为箭头函数没有this机制,不会改变this指向
}
}
person1.sayHi()
const person2 = {
name: 'tom',
sayHi: function () {
setTimeout(function (){
console.log(`Hi, my name is ${this.name1}`)
},0)
// 输出:Hi, my name is undefined,因为setTimeout会放在全局作用域去执行,这时不存在person对象的name属性
}
}
person2.sayHi()
// 可以通过箭头函数修改
const person3 = {
name: 'tom',
sayHi: function () {
setTimeout(() =>{
console.log(`Hi, my name is ${this.name}`)
},0)
// 输出:Hi, my name is undefined,因为setTimeout会放在全局作用域去执行,这时不存在person对象的name属性
}
}
person3.sayHi()
14.对象字面量增强
const baz = '123'
const obj = {
name: 'tom',
// baz: baz 如果key和变量名相同可以简写为:
baz,
// method1: function () {
// console.log('method1')
// }
// 方法也可以省略冒号和function,简写为:
method1() {
console.log('method1')
},
// Math.random(): 'abx'
// key必须是确定的值,或者定义完成后,通过[]的方式添加
// ES6以后可以直接使用[]的方式定义动态属性名,如下
[Math.random()]: 'abx'
}
// 如果key不确定,只能通过这种方式添加
// obj[Math.random()] = 'abx'
console.log(obj)
obj.method1()
15.Object.assign
将多个源对象中的属性复制到一个目标对象中,如果属性名相同,会进行覆盖。
对于Object.assign()而言,如果对象的属性值为简单类型(string,number),通过Object.assign({},srcobj);得到的新对象为深拷贝;如果属性值为对象或其他引用类型,那对于这个对象而言其实是浅拷贝的,这是Object.assign()特别需要注意的地方。
const source1 = {
a: 123,
b: 123,
}
const source2 = {
b: 789,
d: 789
}
const target = {
a: 456,
c: 456
}
const res = Object.assign(target, source1, source2)
console.log(res === target) // true
console.log(target)
// 浅拷贝
let obj = {
foo: {
bar: {
baz: 1
}
}
}
let objCopy = Object.assign({}, obj)
objCopy.foo.bar.baz = 2
console.log(obj, objCopy)
// 深拷贝
let obj1 = {
foo: '11'
}
let objCopy1 = Object.assign({}, obj)
objCopy1.foo = '22'
console.log(obj1, objCopy1)
16.Object.is
console.log(
0 == false,
0 === false,
+0 === -0,
NaN === NaN
)
// true false true false
console.log(Object.is(+0, -0)) // false
console.log(Object.is(NaN, NaN)) // true
17.Proxy
const person = {
name: 'tom',
age: 18
}
const proxyPerson = new Proxy(person, {
get(target, property) {
// console.log(target, property)
// return 100
return property in target ? target[property] : 'default'
},
set(target, property, value) {
// console.log(target, property, value)
// 数据校验
if(property === 'age') {
if(!Number.isInteger(value)){
throw new TypeError(`${property} is not an int `)
}
}
target[property] = value
}
})
console.log(proxyPerson.name) // tom
console.log(proxyPerson.name1) // default
proxyPerson.gender = true
console.log(proxyPerson)
proxyPerson.age = '20' // TypeError: age is not an int
18.Proxy vs defineProperties
Proxy 可以监视到更多的对象操作
const person = {
name: 'aa',
age: 18
}
// 可以监听到删除属性操作
const proxyPerson = new Proxy(person, {
deleteProperty(target, p) {
console.log('delete', p)
delete target[p]
}
})
delete proxyPerson.age
console.log(person)
Proxy 可以更好的支持数组对象的监视
const list = []
const listProxy = new Proxy(list, {
set(target, property, value) {
console.log('set', property, value)
target[property] = value
return true // 表示设置成功
}
})
listProxy.push(100)
19.Reflect
统一的对象操作API,按照后端语言的说法,Reflect属于一个静态类(不能通过new创建对象,只能调用其静态方法,像Math对象一样),Reflect 内部封装了一系列对对象的底层操作,Reflect 成员方法就是 Proxy 处理对象的默认实现,意义在于统一提供了操作对象的API。
const obj = {
foo: '123',
bar: '456'
}
const proxy = new Proxy(obj, {
get(target, p) {
console.log('watch logic~')
return Reflect.get(target, p)
}
})
console.log(proxy.bar)
// 示例
const person = {
name: 'tom',
age: 18
}
// 之前的这些操作方法和操作符后续可能废弃,统一使用Reflect的方式
// console.log(
// 'name' in person,
// delete person['age'],
// Object.keys(person)
// )
// Reflect 方式
console.log(
Reflect.has(person, 'name'),
Reflect.deleteProperty(person, 'age'),
Reflect.ownKeys(person)
)
20.Promise
解决了传统异步编程中回调函数嵌套过深的问题,细节在《异步编程》章节中,这里不再演示
21.class类
// 之前函数式定义对象
function Person(name) {
this.name = name
}
Person.prototype.say = function (){
console.log(`Hi, ${this.name}`)
}
new Person('tom').say()
// 类方式定义
class PersonClass {
constructor(name) {
this.name = name
}
say() {
console.log(`Hi, ${this.name}`)
}
}
new PersonClass('jack').say()
22.静态方法
ES2015 中新增添加静态成员的static关键字
class Person {
constructor(name) {
this.name = name
}
say() {
console.log(`Hi, ${this.name}`)
}
static create(name) {
return new Person(name)
}
}
Person.create('jack').say()
23.类的继承
class Person {
constructor(name) {
this.name = name
}
say() {
console.log(`Hi, ${this.name}`)
}
}
class Student extends Person {
constructor(name, number) {
super(name)
this.number = number
}
hello() {
super.say()
console.log(`my school number is ${this.number}`)
}
}
let s = new Student('Tom', '96281')
s.hello()
24.Set
与数组类似,但是不允许重复
const s = new Set()
s.add(1).add(2).add(3).add(4).add(2)
console.log(s)
s.forEach(i => console.log(i))
for(let i of s) {
console.log(i)
}
console.log('size:', s.size)
console.log(s.has(100))
console.log(s.delete(2))
s.clear()
console.log(s)
// 去重
const arr = [1, 2, 1, 3, 4]
const res = new Set(arr)
const arr1 = Array.from(res)
console.log(arr1)
// 展开运算符将Set转为Array
console.log([...res])
25.map
与Object对象类似,Object只能用字符串作为键,Map可以用任意类型的值作为键
// Object
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{a: 1}] = 'value'
console.log(Object.keys(obj))
// 输出:[ '123', 'true', '[object Object]' ]
// 可以看到是将所有的key进行了toString转换,如果我们的key是不同的对象,最后会变成相同的key,即为'[object Object]'
console.log(obj['[object Object]']) // value
// Map 的key必须是字符串,就可以避免以上问题
const m = new Map()
const tom = {name: 'tom'}
m.set(tom, 90)
console.log(m)
// 输出:Map(1) { { name: 'tom' } => 90 }
console.log(m.get(tom))
console.log(m.has(tom))
// m.clear()
// m.delete()
m.forEach((value, key) => console.log(key, value))
26.Symbol
一种全新的原始数据类型,当前最主要的作用就是为对象添加一个独一无二的属性名
// shared.js===============================共享文件
const cache = {}
// a.js==============================存储一个共享变量
cache['foo'] = '123'
// b.js==因为不知道已经存在foo的key,也存储了键为foo的变量
cache['foo'] = '456'
// 这时获取全局共享的变量,值就有问题了
console.log(cache['foo'])
// 使用Symbol
const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // Symbol
console.log(Symbol() === Symbol()) // false
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj)
// 私有属性
const name = Symbol()
const person = {
[name]: 'Tom',
say(){
console.log('Hi, ', this[name])
}
}
person.say()
Symbol的更多使用:
// Symbol创建的值肯定是唯一的,无论传入的值是否相同
console.log(
Symbol() === Symbol(),
Symbol('foo') === Symbol('foo')
)
// false false
// Symbol 提供了for()方法,传入相同的字符串,返回的值相同,
// 如果传入的不是字符串,会使用toString转成字符串后传入,也就是说在for方法中,
// 传入数字10和字符串10得到的值也是相同的
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
console.log(Symbol.for(10) === Symbol.for('10')) // true
const obj = {
[Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString()) // [object XObject]
// 通过Symbol 定义的属性名在外面是拿不到的,所以更好的作为私有属性
const obj11 = {
[Symbol()]: 'symbol value',
'foo': 'normal value'
}
// 输出: foo
for (var key in obj11) {
console.log(key)
}
console.log(Object.keys(obj11)) // 输出:[ 'foo' ]
console.log(JSON.stringify(obj11)) // 输出:{"foo":"normal value"}
// 可以通过以下方法获取属性为Symbol的属性名
console.log(Object.getOwnPropertySymbols(obj11)) // [ Symbol() ]
27.for...of
作为遍历所有数据结构的统一方式
const arr = [1, 2, 3, 4]
for(const i of arr) {
console.log(i)
}
// 以前使用forEach
arr.forEach(i => console.log(i))
for(const i of arr) {
console.log(i)
// 可以终止循环
if(i > 1) break;
}
// arr.forEach() // 不能终止遍历
// 遍历Set
const s = new Set(['foo', 'bar'])
for(const i of s) {
console.log(i)
}
// foo bar
// 遍历map
const m = new Map()
m.set('foo', 123)
m.set('bar', 456)
for(const i of m) {
console.log(i)
}
// [ 'foo', 123 ] [ 'bar', 456 ]
// 遍历对象
const obj = {foo: 123, bar: 456}
for (const i of obj) {
console.log(i)
}
// 报错:TypeError: obj is not iterable
// 29小节解决
28.可迭代接口
因为ES中能够表示有结构的数据类型越来越多(之前的数组和对象,新增的Set和Map),为了给各种各样的数据结构提供统一的遍历方式,ES2015提供了Iterable接口,实现Iterable就是for...of的前提。
在浏览器的开发人员工具中,打印我们可以for...of的3种数据,可以在原型链上看到他们都实现了Symbol(Symbol.iterator)方法,如图所示依次是Set、数组和Map:
接着我们定义一个数组,去调用Symbol(Symbol.iterator)方法,可以得到如图结果,
可以看到Symbol(Symbol.iterator)方法中有一个next方法,调用该方法,可以依次得到数组的值和一个done标识,done表示是否迭代完数据。
29.实现可迭代接口
// const obj = {
// [Symbol.iterator]: function () {
// return {
// next: function (){
// return {
// value: 'zxc',
// done: true
// }
// }
// }
// }
// }
const obj = {
store: ['foo', 'bar', 'baz'],
[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)
}
30.迭代器模式
// 协同开发一个任务清单应用
// A 的代码:保存数据
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '英语'],
work: ['喝茶'],
each: function (callback) {
const all = [].concat(this.life, this.learn, this.work)
for(const i of all) {
callback(i)
}
},
[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
}
}
}
}
}
// B的代码:展示数据
// 如果直接遍历,可以拿到
for(const i of todos.life) { console.log(i) }
for(const i of todos.learn) { console.log(i) }
// 但是如果A的数据结构发生变化,比如增加一个work项,那么B也要增加
for(const i of todos.work) { console.log(i) }
// 所以最好的办法是A的代码中提供一个迭代方法each,B只管拿值使用就行
todos.each((i) => console.log(i))
// 最后再使用迭代器的方式解决一下,实现[Symbol.iterator]方法
for(const i of todos) { console.log(i) }
31.生成器
避免异步编程中回调嵌套过深的问题,提供更好的异步编程解决方案
function * foo(){
console.log('zcz')
return 100
}
const result = foo()
console.log(result)
// 输出:Object [Generator] {}
console.log(result.next())
// zcz
// { value: 100, done: true }
// 使用yield关键字
function * bar () {
console.log('111')
yield 100
console.log('222')
yield 200
console.log('333')
yield 300
}
const generator = bar()
console.log(generator.next())
// 111
// { value: 100, done: false }
console.log(generator.next())
// 222
// { value: 200, done: false }
console.log(generator.next())
// 333
// { value: 300, done: false }
32.生成器应用
// 案例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
console.log(idMaker.next().value) // 4
// 案例2:使用Generator 函数实现Iterable方法,修改之前的示例
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '英语'],
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 i of all) {
yield i
}
}
}
for(const i of todos) { console.log(i) }
33.ES2016
新增了数组方法 includes 和指数运算 **
// 新增1:Array.prototype.includes
const arr = ['foo', 1, NaN, false]
// 之前判断数组是否包含某值
console.log(arr.indexOf('foo')) // 0
console.log(arr.indexOf(1)) // 1
console.log(arr.indexOf(NaN)) // -1,这就是问题,不能查询NaN
// includes方法, 直接返回布尔值表示存不存在
console.log(arr.includes(1)) // true
console.log(arr.includes(NaN)) // true
console.log(arr.includes(2)) // false
// 新增2:指数运算符
// 之前使用Math.pow
console.log(Math.pow(2, 10)) // 1024
// 使用**
console.log( 2 ** 10) // 1024
34.ES2017
const obj = {
foo: 'value1',
bar: 'value2'
}
// 新增1:Object.values 返回对象值组成的数组
console.log(Object.values(obj)) // [ 'value1', 'value2' ]
// 新增2:Object.entries 返回键值对组成的数组
console.log(Object.entries(obj)) // [ [ 'foo', 'value1' ], [ 'bar', 'value2' ] ]
// 这样就可以直接使用for...of方法了,因为之前看到Object对象无法直接使用for...of
for(const i of Object.entries(obj)) { console.log(i) }
// [ 'foo', 'value1' ]
// [ 'bar', 'value2' ]
// 新增3:Object.getOwnPropertyDescriptors 获取对象的完整描述信息
console.log(Object.getOwnPropertyDescriptors(obj))
const p1 = {
firstName: 'Lei',
lastName: 'Li',
get fullName () {
return this.firstName + ' ' + this.lastName
}
}
console.log(p1.fullName)
const p2 = Object.assign({}, p1)
p2.firstName = 'mao'
console.log(p2)
// 输出为:{ firstName: 'mao', lastName: 'Li', fullName: 'Lei Li' }
// 可以发现 fullName没有改变,是因为Object.assign复制的时候,将fullName当成普通属性复制的
// 使用Object.getOwnPropertyDescriptors获取对象属性的完整信息,并且重新定义一个新的对象
const p3 = Object.defineProperties({}, Object.getOwnPropertyDescriptors(p1))
p3.firstName = 'mao'
console.log(p3.fullName) // mao Li
// 新增4:String.prototype.padStart 和 String.prototype.padEnd
// 给指定的字符串填充指定的符号(开始或者结束),达到给定的长度
const books = {
html: 5,
css: 6,
js: 128
}
for(const [name, count] of Object.entries(books)) {
console.log(name, count)
}
// html 5
// css 6
// js 128
// 看起来输出有些乱,可以使用pad方法做一个对齐
for(const [name, count] of Object.entries(books)) {
console.log(`${ name.padEnd(16, '-') } | ${count.toString().padStart(3, '0')}`)
}
// 新增5:在函数参数的末尾可以添加尾逗号
function foo (
bar, baz,
){
}
// 数组也可以
const arr = [100, 200, 300,]
// 新增6:Async/await 异步编程小节中有详细解释
35.后续版本更新
https://github.com/tc39/proposals/blob/main/finished-proposals.md
36.示例代码
https://github.com/guduqiucai/web/tree/main/JavaScript/newFeaturesOfES6
标签:const,name,特性,obj,ECMAScript,console,foo,log From: https://www.cnblogs.com/caicai521/p/17015276.html