14. ES2015 Promise
ES2015 新出的一种更优的异步编程解决方案,解决了传统异步编程中回调函数嵌套过深的问题。具体可看我的这篇文章:JavaScript 异步编程-------Promise,或你对 Promise 内部实现原理感兴趣,也可以查看这篇文章:JavaScript --手写Promise。
15. ES2015 Class
在此之前,ECMAScript 通常使用 函数function 以及 原型对象property 来实现类。
如今我们能够使用 ES2015 提供的 Class 关键字语法来实现更容易理解且结构更清晰的类定义。
// 在此之前
function Person (name) { //定义构造函数
this.name = name; //通过this访问实例对象
}
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
类中的方法存在两种:实例方法、静态方法。两者的区别就是实例方法需要构造的实例对象去调用,静态方法则是直接使用类本身去调用。
ES2015 中新增添加了静态方法定义的 static 关键字,如上述代码中 create 方法。还需要注意的是,静态方法由于是类本身调用的,因此静态方法中的 this 指向当前类本身。
类的继承
继承是面向对象非常重要的特性,通过继承我们可以抽象出相似类之间重复的部分。ES2015前我们通常使用 构造函数的property = 父类实例对象 的方式来实现继承,而 ES2015 中产生了专门用于继承的关键字:extends。这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
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('wjp', 20)
student.hello()
// my name is wjp
// my number is 20
子类中的 super对象 始终指向父类,调用 super(name) 也就调用了父类的构造方法,同样地,super.say() 即调用了父类的 say 方法。
注意:
- 如果子类没有定义
constructor
方法,这个方法会被默认添加,然后两个类完全一样,等于复制了一个Person
类。 - 如果子类定义了construct构造方法,那就必须在
constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this
对象。 - 子类构造器函数中,super关键字前面不能出现this关键字
class ColorPoint extends Point {
}
// 等同于
class ColorPoint extends Point {
constructor(...args) {
super(...args);
}
}
16.ES2015 Set 数据结构
ES2015 中提供了一个全新的数据结构Set,和数组类似,但Set中的元素不允许重复,也就是每个元素在其中都是唯一的,我们可以理解为:集合。
Set是一个类,通过这个类创造的实例来存放不重复的数据。
1.可以通过实例的add方法往集合当中添加数据。add方法可以返回集合对象本身,所以我们可以链式调用。如果通过add方法添加了一些重复的值,那么这个值会被忽略掉。
const s = new Set();
s.add(2).add(3).add(4).add(2).add(4); // add 方法会返回集合本身,因此可链式调用
console.log(s) //Set { 2, 3, 4 } 重复添加的元素会被忽略
2.想要遍历集合中的数据,可以使用集合对象的forach方法,也可以使用for of 循环。
const s = new Set();
s.add(2).add(3).add(4).add(2).add(4);
s.forEach(item=>console.log(item));
for(let item of s){
console.log(item )
}
3.在set对象中还可以通过size属性来获取集合的长度,这与数组的length属性类似。
console.log(s.size)
4.set对象的自有方法
// 判断集合当中是否存在某个值
console.log(s.has(100)) // false
// 删除集合中某个指定值,方法会返回是否删除成功
console.log(s.delete(3)) // true
// 清空集合
s.clear()
console.log(s) // Set {}
数组去重
Set构造函数可以接收一个数组作为参数,数组里面的元素会作为set集合对象的初始值,重复的值会被忽略掉。
const arr = [1,2,2,3,3,4,4,5];
// const newArr = Array.from(new Set(arr))
const newArr = [...new Set(arr)]
console.log(newArr)
17.ES2015 Map数据结构
Map数据结构与对象非常类似,本质上它们都是键值对的集合。
对象的键只能是字符串类型。 所以去存放一些复杂结构类型的时候会出现一些问题:
可以看到,我们将布尔值,数字,对象作为键名,此时语法上是通过的,但是通过Object.keys的方式将此时obj对象的键打印出来后,可以发现,它们都被转换成了字符串。 也就是说如果我们给对象添加的键不是不是string类型,那么对象内部会将这个数据toString的结果作为键。甚至通过obj['[object Object]'] 的方式也能取到结果。
const obj = {};
obj[true] = 'value';
obj[123] = 'value';
obj[{name:'wjp'}] = 'value';
const result = Object.keys(obj);
console.log(result)
//[ '123', 'true', '[object Object]' ]
console.log(obj['[object Object]'])
//value
Map数据结构就是来解决这个问题,map才能算是严格意义上的键值对集合,用来映射任意两个类型数据之间的对应关系。
首先,用new关键字创建一个map实例对象,让用它的set方法往里面添加数据,这里的键就可以是任意类型数据,也不用担心它会转换成字符串。
const person = {
name:"zce",
age:20
}
const map = new Map()
map.set(person , 999)
map.set("person" , 999)
console.log(map)
//Map { { name: 'zce', age: 20 } => 999, 'person' => 999 }
可以使用它的get方法去获取数据,也可以使用它的forEach方法遍历这个Map对象:
//1.get方法去获取数据
console.log( map.get(person) )
//2.forEach方法遍历数据
map.forEach((element,key) => {
console.log(element,key)
});
还有其他的操作:
// 3.判断某键是否存在
m.has(tom)
// 4.删除某个键
m.delete(tom)
// 5.清空所有键
m.clear()
作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组:
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
console.log(map);
//Map { 'name' => '张三', 'title' => 'Author' }
18.ES2015 Symbol数据类型
在ES2015之前, 对象的键只能是字符串类型,而字符串是有可能会产生重复的,从而就会产生冲突。重复就会导致覆盖冲突,冲突的场景可能如下:
我们使用第三方模块时,很多时候我们会去扩展第三方模块中提供的一些对象,而我们并不知道对象是否已经存在某一个指定的键,如果我们冒然扩展,就可能会出现冲突问题。
以前解决这种问题最好的方式就是约定,例如:约定 a.js 里面缓存对象放入的键都用a_作为开头。b.js 都采用b_作为开头。这样就不会产生冲突了。但是约定只是规避了问题,并不是解决问题。
// share.js================================
const cache = {};
// a.js================================
cache["a_foo"] = Math.random();
// b.js================================
cache["b_foo"] = 123;
console.log(cache)
ES2015为了解决这个问题,它提供了一种全新的数据类型:Symbol。它表示一个独一无二的值。
Symbol最主要作用:为对象添加独一无二的属性名!
Symbol的基本使用:
1.通过Symbol函数就能够创建一个symbol类型的数据。
2.而且这个数据的typeof 的结果就是symbol,表明它就是一个全新的数据类型。
3.它最大的特点就是独一无二 ,我们通过Symbol函数创建的每一个值都是唯一的,永远不会重复。
const s = Symbol();
console.log(s) //Symbol()
console.log( typeof s ) //symbol
console.log(Symbol() == Symbol()) //false
4.Symbol函数允许传入一个字符串作为这个值的描述文本,这样我们多次创建symbol的时候就可以在控制台中区分出来到底是哪一个symbol。
const s1 = Symbol('foo');
const s2 = Symbol('bar');
const s3 = Symbol('baz');
console.log(s1,s2,s3)
//Symbol(foo) Symbol(bar) Symbol(baz)
5.ES2015后可以使用symbol类型的数据作为对象的属性名。(也就是说现在对象的属性名可以是两种数据类型:string 和 symbol)
因为symbol的值都是独一无二的,所以就不用担心可能产生冲突的问题了。这里使用计算属性名的方式,直接在对象字面量的当中使用symbol作为属性名。
const obj = {
[Symbol()]:123,
[Symbol()]:456
};
console.log( obj )
//{ [Symbol()]: 123, [Symbol()]: 456 }
6.另外symbol除了可以避免对象属性名重复产生的问题,还可以借助symbol类型的特点去模拟实现对象的私有成员:使用symbol数据去创建私有成员的属性名
假定这里对b.js暴露了obj对象,那在b.js中只能调用obj.say(),因为在b.js内部拿不到变量name,它没有办法去创建一个完全相同的symbol。所以不能直接访问这个成员,只能访问普通成员。
// a.js================================
const name = Symbol()
const obj = {
[name]:'wjp',
say(){
console.log(this[name]);
}
}
// b.js================================
obj.say() //wjp
console.log(obj[Symbol()]) //undefined
未来 BigInt数据类型:存放更长的数字
Symbol要注意的点:
1.唯一性:每次通过symbol函数创建的值一定是唯一的值,不管我们传入的描述文本是不是相同的。
console.log(Symbol('foo') === Symbol('foo')) // false
但如果需要在全局复用一个相同的symbol的值,可以通过全局变量的方式去实现,比如a.js中的name变量。
或者使用symbol类型提供的for方法去实现。for方法 接收一个字符串作为参数,相同的字符串就一定会返回相同的symbol类型的值,这个方法维护了一个全局的注册表,为字符串和symbol值提供了一一对应的关系:
const s1 = Symbol.for('foo');
const s2 = Symbol.for('foo');
console.log(s1 === s2) //true
注意:但如果我们传入的不是字符串,for方法内部会自动把它转换成字符串,比如传入字符串'true'和布尔值的true,得到的symbol值也是一样的:
const s1 = Symbol.for('true');
const s2 = Symbol.for(true);
console.log(s1 === s2)
2.在Symbol类型当中还提供了很多内置的symbol常量,用来做内部方法的标识。这些标识符可以让自定义的对象实现一些js当中内置的接口
例如:我们定义一个对象,调用它的toString方法,结构默认是[object Object],这个字符串叫做对象的toString标签。
如果想要自定义这个toString标签,就需要在这个对象中添加一个特定的成员来标识。如果用普通的字符串进行标识,那么就也可能和对象内部成员重复导致冲突,所以 ECMAScript 要求我们使用 Symbol 值实现这个接口
const obj = {};
console.log(obj.toString());
// 想要自定义对象的 toString 标签,我们就可以向这个对象添加一个特定的成员来标识
const obj2 = {
[Symbol.toStringTag]:'XObject'
};
console.log(obj2.toString())
这里的toStringTag就是内置的一个Symbol常量,这种Symbol在后面为对象实现迭代器时会经常用到。
3.使用symbol值作为对象的属性名,会使得symbol类型的的属性特别适合作为一个对象的私有属性。
- 这个属性通过for in 循环是无法拿到的,
- 通过Object.keys也拿不到symbol类型的属性名,
- 通过JSON.stringify序列化对象为一个字符串时也会自动忽略掉symbol类型的属性
const obj = {
[Symbol()]:'value',
name:'wjp'
}
// 示例 1 - for in 获取不到 Symbol
for( let i in obj){
console.log(i)
}
// 示例 2 - Object.keys 获取不到 Symbol
console.log( Object.keys(obj) );
// 示例 3 - JSON.stringify 会忽略 Symbol
console.log( JSON.stringify(obj) )
//name
//[ 'name' ]
//{"name":"wjp"}
那么上面方法我们都获取不到 Symbol 键值,我们又该如何正常获取到 Symbol 属性和对应值呢? 使用Object.getOwnPropertySymbols方法,它与Object.keys方法类似,不同之处在于Object.keys只能获取到对象中所有字符串属性名,而Object.getOwnPropertySymbols方法获取的全都是symbol类型的属性名。
// 获取对象中所有 Symbol 属性名
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol()]
19.ES2015 for of循环
在js中遍历数据有很多方式,最基本的for(let i=0;i<3;i++) 循环适合遍历数组,for...in 循环适合遍历对象键值对,再如一些对象的遍历方法:比如数组对象的forEach方法等,但这些遍历方式都有一定的局限性,所以 ES2015 借鉴了许多语言引入了全新的 for...of 循环,这种遍历方法以后将会作为遍历所有数据结构的统一方式。
也就是说:明白了 for...of 的原理也就可以遍历任意自定义数据结构。
for of 的基本用法:可以看到循环中返回的每一个item都是元素而不是下标,同时如果满足某个条件的时候可以使用break关键词 ,跳出当前循环
//循环数组
const arr = [100,200,300,400,500];
for( let item of arr){
if(item >200){
break;
}
console.log(item);
}
//100 200
而数组实例的forEach方法是不能够随时跳出循环的。
以前要随时终止循环的话要是有数组的some 或者 every方法。
some方法:只要循环中有一次返回true它就的返回值就是true,并跳出循环。
every方法:只要循环中有一次返回false它就的返回值就是false,并跳出循环。
除了数组对象,还有一些其他的伪数组对象也可以使用for of循环遍历。比如:函数的arguments对象 和 dom操作时的一些dom节点列表。它们使用for of循环和数组基本没有任何区别;
1.set对象 使用for of循环和数组也基本没有任何区别;
const s = new Set([100,200,300,400,500]);
for( item of s ){
console.log(item)
}
//100 200 300 400 500
2.map对象
const m = new Map();
m.set('foo',100);
m.set('bar',200);
for( item of m ){
console.log(item)
};
//[ 'foo', 100 ] [ 'bar', 200 ]
使用for of循环遍历map对象时,每次得到的 item 都是一个数组,而且数组都是含有两个成员,分别是当前被遍历的键和值,因为一般键和值在循环体当中都需要被用到,所以这里得到的是一个数组的形式,它提供键和值。
所以在这里我们可以使用数组结构的方式在[ ]里定义当前循环的键和值,这样在循环体当中就可以直接使用了。
for( [key,value] of m ){
console.log(key,value)
};
//foo 100 , bar 200
3.遍历普通对象:
// 遍历普通对象,无法正常遍历!!!
const obj = { foo: 123, bar: 456}
for (const item of obj){
console.log(item)
}
// obj is not iterable -> obj 对象是不可迭代的
但刚才才说的for of循环可以作为遍历所有数据结构的统一方式,但现在它连最普通的对象都不能遍历?
20. ES2015 可迭代对象 iterable
从上面 for...of 的最后例子我们能够看到大多数据结构都能够被 for...of 遍历,而普通对象确不能够,这是为什么呢?这就要我们从 ES2015 提供的 iterable 接口谈起了:
在ES中,数据结构的种类是不断发展的,从数组和对象,到现在的set,map,而且我们开发者还可以组合使用这些数据类型去定义符合自己需求的数据结构,为了给各种各样的数据结构提供一种统一的遍历方式,ES2015就提出了一种 Iterrable 的接口。
如果不太理解编程语言中接口的概念,你可以把它理解为一种规格标准。例如在JS中任意一种数据类型都要toString方法,这是因为它们都实现的统一的规格标准。在编程语言中更专业一点的说法就是它们都实现的统一的接口。
可迭代接口(Iterable)就是一种可以被for of循环统一遍历访问的规格标准。换句话说,只要这个数据结构实现了可迭代接口(Iterable),那它就能够被for of循环遍历。
因此,我们上面能够被 for...of 遍历的数组、Set、Map 等内部都已经实现了 iterable 接口,普通对象却没有实现。
- 那么,iterable 接口标准是什么,Set、Map 它们又是如何实现满足标准呢?
我们现在看一下iterable到底约定了什么内容,在控制台打印一下数组,set对象和map对象:
console.log(['foo','bar','baz'])
console.log(new Set())
console.log(new Map())
可以看到,在它们的原型对象上都有一个相同的属性:
这三个可以被for of循环的数据结构都有一个方法,这个方法的名字叫Symbol(symbol.iterator)。
所以可以确定Iterable接口的约定:就是在对象中必须挂载一个iterator 方法,这个方法的名字是一个全局的Symbol常量--->Symbol(Symbol.iterator) 这个常量可以通过 Symbol.iterator(Symbol类的iterator属性)拿到。
我们调用一下这个方法,可以发现它返回的就是一个数组的迭代器对象,且这个迭代器对象有一个next方法:
我们再次将返回的迭代器对象赋值给变量,然后调用它内部的 next 方法,输出如下:
它返回的也是一个对象,分别有value属性和done属性,而且value属性的值就是数组中的第一个元素,done的值是false。
继续调用迭代器对象的next方法,得到的结果是一个相同结构的对象,它的value属性的值是数组中的第二个元素,done的值是false。连续调用后发现它依次把数组中的元素都返回了出来,直到数组元素都返回完毕后,done属性变成了true。
可以想到,在这个迭代器对象内部应该是维护了一个数据指针,没调用一次next方法,指针都会往后移动一位,done属性的作用就是表示数组中的元素是否全部被遍历完成。
总结:
- 所有可以直接被for of循环的数据结构都必须实现 Iterable 接口;
- 也就是在这个数据结构内部必须挂载一个Symbol(Symbol.iterator)方法,
- 这个方法的返回值是一个带有next方法的迭代器对象,
- 不断调用这个next方法就能完成对这个数据的遍历。
其实这就是for of循环内部的工作原理,for of循环内部就是按照这里的执行过程实现的遍历。
实现可迭代接口:
到这里我们就知道为什么说 for of 可以作为所有数据结构的统一方式了:因为for of循环内部就是去调用对象的iterator方法,得到一个迭代器,从而遍历所有数据。这也是iterable接口所约定的内容。换句话说,只要我们的对象也实现了iterable接口,那它就可以使用for of循环去遍历:
/在Js中实现iterable接口,实际上就是在对象中挂载一个iterator方法,在方法中返回一个迭代器对象。所以:
- 先在obj对象上添加一个成员,它的名称是Symnol(Symbol.iterator),这是一Symbol类型提供的个常量
- 这个成员的值是一个函数,它返回一个迭代器对象;
- 这个迭代器对象,需要提供一个next方法,用于执行向后迭代的逻辑
- next方法需要返回一个{ value:'',done: }形式的对象。value表示当前被迭代到的数据,done表示迭代是否执行完毕
const obj = {
name:'wjp',
arr:[1,2,3,4,5],
[Symbol.iterator]:function(){
let index = 0;
const that = this;
return {
next:function(){
let done = index>=that.arr.length?true:false
const result = {value:that.arr[index] , done:done}
index++;
return result;
}
}
}
}
for(item of obj){
console.log(item)
}
也可以去遍历普通对象的键值对:
const obj = {
name:'wjp',
age:18,
wife:'yyn',
[Symbol.iterator]:function(){
let keys = Object.keys(this);
let index = 0;
return {
next:()=>{
let done = index>=keys.length?true:false;
const value = [keys[index],this[keys[index]]]
const result = {value:value , done:done}
index++;
return result;
}
}
}
}
for([key,item] of obj){
console.log(key,item)
}
实现迭代器的目的 - 迭代器模式
迭代器模式的核心:对外提供统一遍历接口,让外部不用再关心数据内部的结构是怎样的。
ES 的迭代器是语言层面的定义,因此适合任何数据结构,但需要我们通过代码去实现iterator方法。
21. ES2015 Generator生成器对象
Generator的意义和promise相同:避免异步编程中回调嵌套过深,从而提供更好的异步编程解决方案。它们俩可以配合使用。
生成器的语法:
function * foo(){
console.log("wjp")
return 100;
}
const result = foo();
console.log(result)
可以看到在函数前添加 * 来定义一个生成器函数,调用后该生成器函数的函数体并没有执行,而是会返回一个生成器对象,打印发现这个生成器对象拥有和迭代器对象相同的 next 方法。
那执行它的next方法,可以看到函数体中的函数开始执行了:
console.log(result.next())
//{ value: 100, done: true }
并且它next方法的返回值和前面迭代器对象next方法的返回值很类似。这也就说明生成器对象也实现了迭代器iterator接口。
yield关键词
生成器函数使用的时候一般会配合yield关键词来使用,yield关键词和return比较类似:
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 }
总结:
- 生成器函数被调用的时候它的函数体并不会执行,而是为我们生成一个生成器对象。
- 调用生成器对象的 next 方法才会让函数体开始执行。
- 执行过程中一旦遇到 yield 关键词,函数的执行过程就会被暂停下来,同时 yield 后 面的值会被作为 next 的值返回。
- 我们继续调用 next 方法,函数体会从上一个 yield 暂停处继续执行,周而复始直到函数完全结束,届时 done 的值才会变为 true。
特点:惰性执行(抽一下,动一下)
生成器的应用:
1.实现一个自增的id:
function * createIdMaker(){
let id = 0;
while(true){
yield id++;
}
}
const g = createIdMaker();
console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.next())
这里不用担心while发生死循环的问题每次执行next方法到了yield关键词这里都会被暂停下来,直到下一次调用next方法 ,后还会被暂停下来。
这样就可以得到一个生成器对象,每调用一次它的next方法都会得到一个自增的id。
2.还可以通过生成器对函数实现对象的iterator方法,会比上面简单很多:
const obj = {
name:'wjp',
arr:[1,2,3,4,5],
[Symbol.iterator]:function *(){
for( item of this.arr){
yield {value:item,done:false};
};
yield {value:undefined,done:true};
}
}
for(item of obj){
console.log(item)
}
注意:Generator生成器函数最重要的目的还是去解决异步编程中回调嵌套过深所导致的问题。
22. ES Modules
ES Modules 是在语言层面的模块化标准,之后我在模块化开发的文章中再详细介绍。到时候会把它和commonJs以及一些其他的标准做一个统一的对比。
23.ES2016概述
ES2016 是一个小版本,所以它只比 ES2015 多了两个小功能。
1.数组的 includes 方法:判断数组中是否包含指定元素,返回值是布尔值。
之前我们想要判断数组中是否含有某个元素时需要用到数组的indexof方法:它可以帮我们返回元素在数组中对应的下标。如果没有找到则返回-1。它的缺点是不能查找数组中的NaN。
const arr = ['foo','bar',NaN,false];
console.log(arr.indexOf('bar')) //1
console.log(arr.indexOf('aaa')) //-1 没有找到
console.log(arr.indexOf(false)) //3
console.log(arr.indexOf(NaN)) //-1 不能查找数组中的NaN
使用include方法则直接返回布尔值:且能查找到NaN
console.log(arr.includes(NaN)) //true
console.log(arr.includes('bar')) //true
2.指数运算符
// 在之前我们需要进行指数运算时要依赖于 Math 中的 pow 方法
console.log(Math.pow(2, 10))
// ES2016,使用两个星号即可
console.log(2 ** 10)
相比Math中的pow方法,新的运算符对于数学密集型的应用是一个很好的补充!
24.ES2017概述
ECMAScript的第八个版本,发布于2017年六月。同样是一个小版本。它共扩充了几个小功能:Object 三个扩展方法、String 两个扩展方法、函数参数中添加尾逗号、Async/Await 标准化。
Object 三个扩展方法:
1.Object.values - 获取对象所有值,它与Object.keys方法类似,不过它返回的数组是值的集合。
const obj = {
name:'wjp',
age:18,
occupation:'it'
}
console.log( Object.values(obj) )
//[ 'wjp', 18, 'it' ]
2.Object.entries- 以数组的形式返回对象当中所有的键值对。
console.log( Object.entries(obj) )
//[ [ 'name', 'wjp' ], [ 'age', 18 ], [ 'occupation', 'it' ] ]
它的返回值:
- 就使得我们可以用for of循环间接的去遍历普通对象,
- 便于 Object 数据类型转换为 Map 数据类型,因为 Map 初始化需要的数据类型就是 entries 返回的数据类型
for( [key,value] of Object.entries(obj) ){
console.log(key,value)
}
//name wjp
//age 18
//occupation it
console.log(new Map(Object.entries(obj)))
// Map { 'foo' => 'value1', 'bar' => 'value2' }
3.Object.getOwnPropertyDescriptors - 获取对象中所有属性的完整的信息
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
两个字符串填充方法:padStart和padEnd
String.padStart / String.padEnd - 给字符串补充指定位数的指定字符串
例如:
// 字符串方法
const obj = {
html:5,
css:16,
javascipt:128
}
for([key,item] of Object.entries(obj)){
key = key.padEnd(15,"-");
item = item.toString().padStart(3,"0");
console.log(key+"|"+item)
}
//html-----------|005
//css------------|016
//javascipt------|128
函数参数中添加尾逗号
方便代码书写体验,不是功能层面的更新
function foo(
bar,
baz,
) {
}
标准化了 Async / Await,实质上就是 Promise 的语法糖
总结
这篇文章总共从 23 个点上叙述了关于 ECMAScript 的发展过程和特性剖析,三四年前我已经陆陆续续在工作中去学习过并使用过了,但总是有没学到的地方,很多地方也都是一知半解,说白了就是没有体系的去学习还不认真学导致的。es6这个东西也挂在嘴边听在耳边好几年了,借助这次学习的机会基本把知识点从概念到用法全部过一遍感觉收获很大!