一万八千字,先码后看,感谢收藏。
上篇我们介绍了函数式编程:【大前端专栏】突破桎梏(二):函数式编程
今天我们来介绍 【诶可码 · 斯柯瑞噗特】和它的新特性。
系统化的学习 ECMAScript 很有必要,而网上的 ECMAScript 资料比较零散,因此我们根据下面问题从 ES2015 开始梳理 ES2015 的发展与新特性介绍。
名称 | 标准版本 | 发行时间 |
---|---|---|
ECMAScript 2019(ES2019) | 10 | 2019年6月 |
ECMAScript 2018(ES2018) | 9 | 2018年6月 |
ECMAScript 2017(ES2017) | 8 | 2017年6月 |
ECMAScript 2016(ES2016) | 7 | 2016年6月 |
ECMAScript 2015(ES2015) | 6 | 2015年6月 |
ECMAScript 5.1(ES5.1) | 5.1 | 2011年6月 |
ECMAScript 5(ES5) | 5 | 2009年12月 |
ECMAScript 4(ES4) | 4 | |
ECMAScript 3(ES3) | 3 | |
ECMAScript 2(ES2) | 2 | |
ECMAScript 1(ES1) | 1 |
从上表我们能够讲出如下几点:
ES6 是 ECMAScript 标准的代表版本,原因如下:
目前有很多开发者还喜欢用 ES6 这个名称泛指从 ES5.1 以后所有的新版本。
例如 “使用 ES6 的 async 和 await”,实际上 async 是 ES2017 中制定的标准。
因此我们需要注意分辨文章中的 ES6 是特指 ES2015 还是 泛指 ES2015之后的所有新标准。
ES2015 长达 26 个章节,链接:ES2015
接下来我们来重点介绍 ES2015 在 ES5.1 基础上的变化,变化归纳为 4 类。
包含到 ES2019 的 Node 版本号:12.14.0。
Nodemon 工具:修改完代码后自动执行代码。
执行 js 文件命令只发生如下变化:node index.js 这样执行变化为 nodemon index.js。
如下所示,在 if 花括号内使用 let 定义变量,在全局打印输出为:foo is not defined
for(let i = 0; i < 3; i++){
let i = 'foo';
console.log(i)
}
上面代码的执行过程类似下面这样:
let i = 0
if(i < 3) {
let i = 'foo'
console.log(i)
}
i++
console.log(foo)
var foo = 'foo'
// undefined
// 错误示例 1
const name = 'mn'
name = 'nm'
// 错误示例 2
const name
name = 'nm'
// 正确示例
const obj = {}
obj.name = 'mn'
// 错误示例
obj = {}
变量声明的最佳实践:不用 var,主用 const,配合 let
const arr = [100, 200, 300]
// 不使用解构
const foo = arr[0]
const bar = arr[0]
const baz = arr[0]
// 使用解构 1
const [foo, bar, baz] = arr
// 使用解构 2 -> 只获取某个位置的值
const [, , baz] = arr
console.log(baz) // 300
// 使用解构 3 -> 提取数组中剩余所有值,该写法仅可用于最后一个位置
const [foo, ...rest] = arr
console.log(rest) // [200, 300]
// 使用解构 4 -> 获取数量少于数组内实际数量
const [foo] = arr
console.log(foo) // 100
// 使用解构 5 -> 获取数量大于数组内实际数量
const [foo, bar, baz, more] = arr
console.log(more) // undefined
// 使用解构 6 -> 设置变量默认值,当未提取出值时默认给予该值
const [foo, bar, baz, more = 'default more'] = arr
console.log(more) // default more
对象的解构大都和数组的解构相同,基础使用和特殊用法如下示例:
// 基础使用
const obj = { name: 'mn', age: 18}
const { name } = obj
console.log(name) // mn
// 特殊用法 -> 由于解构时花括号内填写的必须是对象中存在的key,那么我们遇到下面情况可以这样解决
const name = 'tom'
const { name:objName = 'jack' } = obj
console.log(objName)
模板字符串具有以下三个
// 1. 多行字符串,无需/n 直接使用回车即可
const str = `my name is
name`
// 2. 插入变量
const name = 'mn'
const str = `my name is ${name}`
// 3. 标签字符串。
// 标签函数能够获取到译${}分割后的字符串数组
// 标签函数的返回值就是模板字符串的值
const name = 'tom'
const gender = true
function myTagFunc (strings, name, gender){
console.log(strings, name, gender) // ['hey', 'is a','.'] 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
一组判断字符串内是否包含某些内容的方法
const message = 'Error: foo is not defined.'
message.includes('foo') // true
const message = 'Error: foo is not defined'
message.starsWith('Error') // true
const message = 'Error: foo is not defined'
message.endsWith('.') // true
// 无参数默认值语法时,使用短路赋值而导致的错误示例
function foo (enable){
enable = enable || true
console.log(enable) // true
}
foo(false)
// 正确的应该是下面这样,判断是否为 undefined 才赋予默认值
function foo (enable){
enable = enable === undefined ? true : enable
console.log(enable) // true
}
foo()
// 使用参数默认值语法,原理和上面正确用法相同,没有传递实参或传递undefined时使用默认值
function foo (enable = true){
console.log(enable) // true
}
foo()
// ES2015前,使用arguments来接收所有参数,arguments是一个伪数组
function foo(){
console.log(arguments) // {'0': 1, '1': 2, '2': 3, '3': 4}
}
// ES2015里,使用下面语法接收真实传入实参数量之后剩余的参数
function foo(a, ...args){
console.log(args) // [2, 3, 4]
}
foo(1, 2, 3, 4)
const arr = ['foo', 'bar', 'baz']
// ES2015 前
console.log(arr[0], arr[1], arr[2])
// ES2015
console.log(...arr)
// 简化函数定义和增加新特性
// 示例 1
const inc = n => n + 1
console.log(inc(100)) // 101
// 示例 2 // 不带花括号的箭头函数的返回值是自动返回不需要 return 的
const inc = (n, m) => n + m
// 示例 3 // 带花括号的箭头函数的返回值需手动返回
const inc = (n, m) => {
return n + m
}
// 普通函数里,this 指向调用者自身
const person = {
name: 'tom',
sayHi: function () {
console.log(`hi, my name is ${this.name}`)
}
}
person.sayHi() // hi, my name is tom
// 箭头函数没有 this 的机制
const person = {
name: 'tom',
sayHi: () => {
console.log(`hi, my name is ${this.name}`)
},
// 下面函数只能通过声明 that 利用闭包来获取 name
sayHiAsync: function () {
const _that = this
setTimeout(function () {
console.log(_that.name) // tom
}, 1000)
},
// 箭头函数能够解决上面问题,this 可直接访问到 name
sayHiAsyncTwo: function () {
setTimeout(() => {
console.log(this.name) // tom
}, 1000)
},
}
person.sayHi() // hi, my name is undefined
// 1.属性名和变量名相同,可省略
// 2.函数声明语法可精简,可省略冒号和function关键字。
需要注意的是,这里的方法中的this和普通函数function中的this相同,指向调用者自身。
// 3.计算属性名:对象的属性名可动态添加
const bar = '123'
const obj = {
foo: 123,
bar, // 和 bar:bar 等价
method1 () { console.log(this) } // 和 method1: function() 等价
[bar]: 123
}
将多个源对象中的属性复制到一个目标对象中,如果对象之间有相同的属性,那么源对象中的属性会覆盖掉目标对象中的属性。
const source1 = {
a: 123,
b: 123
}
const target = {
a: 456,
c: 456
}
const result = Object.assign(target, source1)
console.log(target) // {a: 123, c: 456, b: 123}
console.log(result === target) // true
能够看到,target 居然和 result 相同,也就是说 target 确确实实被改变了,而不是重新生成新对象。
但我们同样能够利用这个特性来复制一个对象,如下示例:
const result = Object.assign({}, {name: 'tom'})
console.log(result) // {name: 'tom'}
判断两个值是否相等,该方法不常用,通常我们使用严格等号 ===。
// ES2015 之前
console.log(0 == false) // true
console.log(0 === false) // false
console.log(+0 === -0) // true
console.log(NaN === NaN) // false
// ES2015 新的同值比较的方法
Object.is(NaN, NaN)
监视某个对象中的属性读写,我们可以使用 ES5 Object.defineProperty。
ES2015 中 Proxy 是专门为对象设置访问代理器的,其中代理可理解为门卫,我们进出屋子都要经过这个代理。通过 Proxy 就可以轻松监视属性的读写,它也比 defineProperty 更强大。
const person = {
name: 'mn',
age: 20
}
const personProxy = new Proxy(person, {
get(target, property) {
console.log(target, property) // {name: 'mn', age:20} name
return 100
},
set(target, property, value) {
console.log(target, property, value) // {name: 'mn', age:20} gender true
target[property] = value
}
})
console.log(person.name) // 100
personProxy.gender = true
第一个参数为需要代理的对象,第二个参数为代理的处理对象,其中 get 方法用来监视属性的访问,返回值作为访问结果。set 方法用来监视属性的设置
const person = {
name: 'mn',
age: 20
}
const personProxy = new Proxy(person, {
deleteProperty(target, property) {
console.log('delete', property) // delete name
delete target[property]
}
})
delete personProxy.age
console.log(person) // {name: 'mn'}
Proxy 中处理对象的 deleteProperty 方法能够监视目标对象中的 delete 操作。此外有更多方法都能够监视到目标对象的属性异动。
const list = []
const listProxy = new Proxy(list, {
set (target, property, value){
console.log('set', property, value) // set 0 100
target[property] = value
return true // 表示设置成功
}
})
listProxy.push(100) // proxy 猜测到 property 下标 为 0
统一的对象操作 API,Reflect 属于静态类,不可通过 new 方法构建实例对象,只能调用其中的静态方法,如同 Math 一样 。Reflect 内部封装了一系列针对对象的底层操作(目前 13 个尚在使用的方法)。这些静态方法的方法名是和 Proxy 能够监视到对象的方法名一致的,其实 Reflect 的这些方法就是 Proxy 处理对象的默认实现,解释如下:
const obj = {
foo: '123',
bar: '456'
}
const proxy = new Proxy(obj, {
get (target, property) {
console.log('watch logic~')
return Reflect.get(target, property)
}
})
Reflect 最大的价值就是它统一了一套用于操作对象的 API。举例如下:
const obj = {
name: 'mn',
age: 18
}
// 之前我们需要使用不同的关键词或对象API
console.log('name' in obj)
console.log(delete obj['age'])
console.log(Object.keys(obj))
// 统一使用 Reflect,体验更合理更舒适
console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))
ECMAScript 之后定会逐渐的把之前的方法废弃掉。
ES2015 新出的一种更优的异步编程解决方案,解决了传统异步编程中回调函数嵌套过深的问题。具体可看我在 JavaScript 异步编程话题中的详细分析:JavaScript 异步编程中回调函数的替代方案:Promise,或你对 Promise 内部实现原理感兴趣,可以查看我的这篇文章:Promise实现原理(附源码)。
在此之前,ECMAScript 通常使用 function 或 property 来实现类。
如今我们能够使用 ES2015 提供的 Class 关键字语法来实现更容易理解且结构更清晰的类定义。
如下所示:
// 在此之前
function Person (name) {
this.name = name
}
Person.property.say = function () {
console.log(`my name is ${this.name}`)
}
// ES2015 Class
class Person {
// 当前类的构造函数
constructor(name){
this.name = name // this 访问当前类的实例对象
}
say () {
console.log(`my name is ${this.name}`)
}
static create (name) {
return new Person(name)
}
}
const p = new Person('tom')
console.log(p.say()) // my name is tom
const tom = Person.create('tom')
tom.say() // my name is tom
class Person {
constructor(name){
this.name = name
}
say () {
console.log(`my name is ${this.name}`)
}
}
class Student extends Person {
construct (name, number) {
super(name)
this.number = number
}
hello () {
super.say()
console.log(`my number is ${this.number}`)
}
}
const student = new Student('mn', 20)
student.hello()
// my name is mn
// my number is 20
子类中的 super 就代表了父类,调用 super(name) 也就调用了父类的构造方法,同样地,super.say() 即调用了父类的 say 方法。
ES2015 中提供了一个全新的数据结构,和数组类似,但其中的元素不允许重复,也就是每个元素在其中都是唯一的,我们可以称之为:集合。
const s = new Set()
// add 方法会返回集合本身,因此可链式调用
s.add(1).add(2).add(3).add(2)
console.log(s) // Set {1, 2, 3} 重复添加的元素会被忽略
// 方法 1 使用 Set 自带 forEach 方法
s.forEach(i => console.log(i))
// 方法 2 使用 ES2015 新语法 for
for (let i of s){
console.log(i)
}
// 1.获取集合长度
console.log(s.size)
// 2.判断集合当中是否存在某个值
console.log(s.has(100)) // false
// 3.删除集合中某个指定值,方法会返回是否删除成功
console.log(s.delete(3)) // true
// 4.清空集合
s.clear()
console.log(s) // Set {}
const arr = [1, 2, 1, 3, 4, 2]
// 方法 1
const result_1 = Array.from(new Set(arr))
console.log(result_1) // [1, 2, 3, 4]
// 方法 2
const result_2 = [...new Set(arr)]
console.log(result_2) // [1, 2, 3, 4]
Map 和对象很像,它们本质上都是键值对集合。不同的是,对象的键只能存放字符串类型,这就会导致我们在存放复杂数据时遇到一些问题,如下所示:
// object 键上存放复杂数据时遇到的问题
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'
console.log(Object.keys(obj)) // ['123', 'true', '[object Object]']
Map 就是为了解决上面的问题诞生的,Map 是严格意义上的键值对集合,它能够映射两个任意类型数据之间的对应关系。
const m = new Map()
const tom = { name : 'mn' }
// 1.设置键值映射关系
m.set(tom, 90)
console.log(m) // Map { { name: 'tom' } => 90}
// 2.根据键获取对应值
m.get(tom)
// 3.判断某键是否存在
m.has(tom)
// 4.删除某个键
m.delete(tom)
// 5.清空所有键
m.clear()
// 6.遍历所有键值,需要注意的是首个参数是值,第二参数是键
m.forEach((value, key) => {
console.log(value, key)
})
一种全新的原始数据类型,这里的原始指的可是基本数据类型,也就是说自 ES2015 起,JavaScript 的基础类型会变为 7 种:String、Number、Boolean、Object、Null、Undefined、Symbol。
ES2015 之前我们对象中的属性名都能是字符串类型,而字符串是也可能重复的,重复就会导致冲突,冲突的场景可能如下:
我们使用第三方模块时,很多时候我们会去扩展第三方模块中提供的一些对象,而我们并不知道对象是否已经存在某一个指定的键,如果我们冒然扩展,就会出现冲突问题。
那么 ES2015 为了解决这个问题,提供了一个**【独一无二的值】**的原始数据类型:Symbol。
console.log(Symbol() === Symbol())
console.log(Symbol('foo')) // Symbol(foo)
console.log(Symbol('bar')) // Symbol(bar)
console.log(Symbol('baz')) // Symbol(baz)
const obj = {
[Symbol()]: '123', // 使用对象字面量方法动态声明键值
[Symbol()]: '456'
}
console.log(obj) // { [Symbol()]: '123', [Symbol()]: '456' }
// a.js =====================================
const name = Symbol()
const person = {
[name]: 'mn',
say () {
console.log(this[name])
}
}
// b.js =====================================
person.say() // 只能这样才能调用,因为没有 name
Symbol 最主要的作用就是为对象添加独一无二的属性名。
之后还会增加一个 bigint 基本数据类型,用于存放更长的数字。
目前该类型还处在 stage-4 阶段,预计下个版本会被标准化。
到时候就有 8 个基本数据类型了。
由于 Symbol 创建出的值始终是独一无二的,因此即使传入的描述相同,产生出的值也会仍然不相等
console.log(Symbol('foo') === Symbol('foo')) // false
那么我们如何多次创建相同的 Symbol 呢?我们可以使用 Symbol 的 for 方法,示例如下:
console.log(Symbol.for('foo') === Symbol.for('foo')) // true
需要特别注意的点:for 方法的实现实际上是由于 Symbol 内部维护了一个注册表,这个注册表为字符串和值创建了一一对应的关系,重点是:字符串,所以在 for 方法中我们传入的值都会被转换为字符串,因此会出现下面情况:
console.log(Symbol.for(True) === Symbol.for('true')) // true
console.log(Symbol.for(0) === Symbol.for('0')) // true
Symbol 内置了一些常用的 Symbol 常量,作用是为内部方法的标识,这些标识可以让自定义对象实现一些 JS 当中内置的接口。
const obj = {}
console.log(obj.toString()) // [object Object]
// 想要自定义对象的 toString 标签,我们就可以向这个对象添加一个特定的成员来标识
// 如果用普通的字符串进行标识,那么就也可能和对象内部成员重复导致冲突,所以 ECMAScript 要求我们使用 Symbol 值实现这个接口
const obj = {
[Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString()) // [object XObject]
toStringTag 就是 Symbol 内置的 Symbol 常量,这种 Symbol 在外面为对象实现迭代器时会经常用到。
const obj = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
// 示例 1 - for in 获取不到 Symbol
for (let key in obj){
console.log(key) // foo
}
// 示例 2 - Object.keys 获取不到 Symbol
console.log(Object.keys(obj)) // ['foo']
// 示例 3 - JSON.stringify 会忽略 Symbol
console.log(JSON.stringify(obj)) // {'foo': 'normal value'}
上面这些点都表明:Symbol 特别适合用来定义对象的私有属性。
那么上面方法我们都获取不到 Symbol 键值,我们又该如何正常获取到 Symbol 属性和对应值呢?
// 获取对象中所有 Symbol 属性名
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol()]
ECMAScript 中的循环方法有很多:for(let i=0;i<3;i++) 循环适合遍历数组,for…in 循环适合遍历对象键值对,再如一些对象的遍历方法:forEach,但这些遍历方式都有一定的局限性,所以 ES2015 借鉴了许多语言引入了全新的 for…of 循环,**这种遍历方法以后将会作为遍历所有数据结构的统一方式。**也就是说:明白了 for…of 的原理也就可以遍历任意自定义数据结构。
// 示例 1 - 遍历数组
const arr = [100, 200, 300, 400]
for (const item of arr){
console.log(item)
}
// 100
// 200
// 300
// 400
// 示例 2 - 遍历 Set 对象,和遍历数组无差异
const s = new Set(['foo', 'bar', 'baz'])
for (const item of s){
console.log(item)
}
// foo
// bar
// baz
// 示例 3 - 遍历 Map 对象,注意此时的输出,会同时输出键和值
const m = new Map()
m.set('foo', '123')
m.set('bar', '456')
for (const item of m){
console.log(item)
}
// ['foo', '123']
// ['bar', '456']
// 示例 4 - 遍历普通对象,无法正常遍历!!!
const obj = { foo: 123, bar: 456}
for (const item of obj){
console.log(item)
}
// obj is not iterable -> obj 对象是不可迭代的
这样,for…of 就可以替代数组的 forEach 方法,同时 for…of 方法内也可以使用 break 随时终止循环,而 forEach 是无法终止遍历的。
从上面 for…of 的最后例子我们能够看到大多数据结构都能够被 for…of 遍历,而普通对象确不能够,这是为什么呢?这就要我们从 ES2015 提供的 iterable 接口谈起了:
在数据结构的不断发展过程中,ES 从原有的数据结构 数组、对象等延申出了愈来愈多的数据结构:Set、Map 等等,之后还会诞生更多数据结构,我们也能够通过复杂组合来产生自定义的数据结构。**ECMAScript 为了给各种各样的数据结构提供统一的遍历方式,ES2015 提供了 iterable 接口来解决这个问题。**接口实际上就是对外提供的方法,如大多数据结构都向外提供了 toString 接口,这个接口实际上就是 ECMAScript 所制定出的标准接口,这些数据结构都据此实现了该方法。
在这里的 iterable 可迭代接口,就是提供可以被 for…of 统一遍历访问的标准。可以这么说:只要这个数据结构实现了可迭代接口,那么它就能够被 for…of 遍历。
因此,我们上面能够被 for…of 遍历的数组、Set、Map 等内部都已经实现了 iterable 接口,普通对象却没有实现。
console.log([])
console.log(new Set())
console.log(new Map())
我们可以在控制台分别输出数组、Set、Map 来观察他们的内部方法,在它们的 _ proto_ 原型对象上我们能够很容易地找到它们都具有下面这个相同的属性实现:
三个能够被 for…of 遍历的数据结构的原型对象上都实现了属性名为 Symbol 常量:Symbol.iterator 的方法,它是一个函数方法。
所以,iterable 接口约定的就是对象中必须挂载 iterator 这个方法。
我们定义一个数组并通过 Symbol.iterator 常量调用这个方法,能够看到这个方法调用后会返回一个包含 next 方法的迭代器对象。
我们再次将返回的迭代器对象赋值给变量,然后调用它内部的 next 方法,输出如下:
next 方法的返回值是一个包含 value 和 done 属性的对象。value 是数组第一个值,done 是一个布尔值 false。我们再连续调用三次该迭代器对象的 next 方法,输出如下:
到这里我们就应该能够想到,在这个迭代器当中内部应该是维护了一个数据指针,next 每调用一次指针就会向后移动一位,而 done 属性则表示数组中的元素是否全部被遍历完了。
这也就是 iteratable 接口的实现原理,我们也就理解了为什么 for…of 能够遍历所有的数据结构:所有对象都可以自定义实现 iteratable 接口,只要实现了这个接口,for…of 自然而然就可以遍历实现了 iteratable 接口的对象了。
// 实现可迭代接口(iterable)
const obj = {
name: 'mn',
age: 20,
[Symbol.iterator]() { // iterator
const keys = Object.keys(this)
return {
next: () => {
const done = keys.length === 0 ? true : false
const key = done ? undefined : keys.pop()
const value = done ? undefined : [key, this[key]]
return { value, done } // iteration result
}
}
}
}
// 示例 1 - 手动调用
const iterator = obj[Symbol.iterator]()
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// 示例 2 - for...of 调用
for (const item of obj) {
console.log(item);
}
迭代器模式的核心:对外提供统一遍历接口,让外部不用再关心数据内部的结构是怎样的。
ES 的迭代器是语言层面的定义,因此适合任何数据结构。
Generator 的意义和 Promise 相同:避免异步编程中回调嵌套过深,从而提供更好的异步编程解决方案。
function * foo() {
console.log('mn')
return 100
}
const result = foo()
console.log(result) // Object [Generator] {}
在函数前添加 * 来定义一个生成器函数,调用后该生成器函数会返回一个生成器对象,生成器对象拥有和迭代器对象相同的 next 方法。
console.log(result.next()) // { value: 100, done: true }
实际上,生成器也实现了 iteratable 接口。
function * foo() {
yield 100
yield 200
yield 300
}
const generator = foo()
console.log(generator.next()) // { value: 100, done: false }
console.log(generator.next()) // { value: 200, done: false }
console.log(generator.next()) // { value: 300, done: false }
console.log(generator.next()) // { value: undefined, done: true }
// 示例 1 - 生成 id
function * createIdMaker() {
let id = 1
whild (true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
ES Modules 是在语言层面的模块化标准,之后我在模块化开发的文章中再详细介绍。到时候我会把链接再贴到这里。
ES2016 毕竟是一个小版本,所以它只比 ES2015 多了两个小功能。
// Array.prototype.includes
const arr = ['foo', 1, NaN, false]
console.log(arr.includes(1))
// 在之前我们需要进行指数运算时要依赖于 Math 中的 pow 方法
console.log(Math.pow(2, 10))
// ES2016,使用两个星号即可
console.log(2 ** 10)
ES2017 也是一个小版本,它共扩充了几个小功能:Object 三个扩展方法、String 两个扩展方法、函数参数中添加尾逗号、Async/Await 标准化。
const obj = {
foo: 'value1',
bar: 'value2'
}
console.log(Object.values(obj)) // ['value1', 'value2']
console.log(Object.entries(obj)) // [['foo', 'value1'], ['bar', 'value2']]
// 作用 1 - 便于遍历操作
for (const [key, value] of Object.entries(obj)){
console.log(key, value)
}
// 作用 2 - 便于 Object 数据类型转换为 Map 数据类型,因为 Map 初始化需要的数据类型就是 entries 返回的数据类型
console.log(new Map(Object.entries(obj))) // Map { 'foo' => 'value1', 'bar' => 'value2' }
ES5 之后 对象中就可以定义 getter、setter 属性,但这些属性是不能通过 Object.assign 方法正常复制过去的,这种情况下,我们就可以先通过 getOwnPropertyDescriptors 来获取对象中所有属性的完整的信息,再通过 assign 复制过去,示例如下:
const p1 = {
firstName: 'ning',
lastName: 'mai',
get fullName() {
return this.firstName + ' ' + this.lastName
}
}
// 错误示例
const p2 = Object.assign({}, p1)
p2.firstName = 'li'
console.log(p2.fullName) // ning mai
// 正确示例
const descriptors = Object.getOwnPropertyDescriptors(p1)
const p2 = Object.assign({}, descriptors)
p2.firstName = 'li'
console.log(p2.fullName) // li mai
console.log('0.1'.padStart(3, '0')) // 0.10
console.log('0.1'.padEnd(3, '0')) // 00.1
function foo(
bar,
baz,
) {
}
这篇文章总共从 23 个点上叙述了关于 ECMAScript 的发展过程和特性剖析,这实际上是我在学习过程中的笔记,我们学技术的都知道好记性不如烂笔头,更何况在学习技术的过程中不动手敲下来的话,在实际应用过程中就根本无从下手,因此强烈建议读者能够按照给定的代码串下来,这篇文章坚持下来的话,相信诸位关于 ECMAScript 的知识积累不再零散。
该文章总结于我学习于拉勾教育出品的前端高薪训练营,因为我之前的学习也都过于零碎,所以综合考虑还是报名了这个课程来系统学习,学习成果你们也都看得到,如果需要报名可以私信找我,新老学员都会有五百元的返现,我的五百元返现不会拿在手里,其中一半会再返还给你,另一半我将捐赠给慈善机构(届时截图),有兴趣的朋友可以在评论区留言,感谢阅读。