学习笔记-ECMAScript新特性

ECMAScript

ECMAScript是一门脚本语言,一般缩写为ES,通常看做 JavaScript 的标准化规范。实际上 JavaScript 是 ECMAScript 的扩展语言。因为在 ECMAScript 中只是提供了最基本的语法,约定了代码该如何编写,例如如何定义变量和函数等,它只是停留在语言层面,并不能直接用来完成应用中的实际功能开发。而我们经常使用的 JavaScript 实现了 ECMAScript 语言的标准,并且在这个基础上做了扩展,使得我们可以在浏览器环境当中去操作 DOM 和 BOM, 在 node 环境中可以做读写文件等操作。

在浏览器中的 JavaScript 就等于 ECMAScript 加上 web 提供的API, 也就是DOM和BOM,在node环境中的 JavaScript 就等于 ECMAScript 加上 node 所提供的一系列API,例如 fs 或 net 这样的内置模块所提供的 API。所以说 Javascript 语言本身指的就是 ECMAScript。


image.png
image.png

从2015年开始,ES 保持每年一个大版本的迭代,伴随着这些新版本的迭代,很多新特性陆续出现,这也就导致我们现如今 JavaScript 这门语言的本身也就变的越来越高级,越来越便捷。

image.png

图中展示了 ECMAScript 每个迭代的名称,版本号和发行时间。从 ES5 到 ES2015,是前端发展的黄金时期,这个时期也新增了很多颠覆式的新功能,相较之前有了很大的改变,从 ES2015 后,开始每年一个迭代,符合当下互联网小步快走的精神,而且开始不按照版本号命名,按照年份命名。很多人也把 ES2015 称之为 ES6,但是也有很多人用 ES6 泛指所有的新标准。

详见ECMAScript标准规范http://www.ecma-international.org/ecma-262/6.0/

下面按以下分类讲解 ES6 的新特性。

  • 解决原有语法上的一些问题或者不足
  • 对原有语法进行增强
  • 全新的对象,全新的方法,全新的功能
  • 全新的数据类型和数据结构

let const

作用域 - 某个成员能够起作用的范围。

在ES2015之前,ES中只有两种作用域,全局作用域和函数作用域。
在ES2015中新增了块级作用域。指的是用{}括起来的作用域,例如 if 语句和 for 循环语句{}括号括起来的部分。

以前块没有单独的作用域,所以,在块中定义的成员外部也可以访问。eg:

if(true){
  var foo = 'aaa'
}
console.log(foo) // aaa

let

如果使用 let 来声明变量,它只能在声明的块中被访问到。

if(true){
  let foo = 'aaa'
}
console.log(foo) // foo is not defined

这就表示在块中声明的成员,在外部是无法访问的。

let 声明的变量不能提升。

// 变量提升
// foo已经被声明了,只是还没有赋值
console.log(foo)
var foo = 'aaa'

// let 取消了变量提升
console.log(bar)  // 控制台报错
let bar = 'bar'

const

const 声明一个只读的恒量/常量。只是在let基础上多了一个只读。

最佳实践:不用 var,主用 const,配合 let。

总结

  • let 和 const 声明的变量,只能在声明的块中被访问到,外部无法访问
  • let 和 const 声明的变量,不做变量提升
  • let 和 const 声明的变量,不能在同一个作用域内多次声明
  • const 声明的变量,无法修改

数组的解构

ES2015 新增了从数组和对象中快速获取元素的方法,这种方法叫解构。

数组结构的用法:

const arr = [100, 200, 300]
// 按照变量名的位置,分配数组中对应位置的值
const [foo, bar, baz] = arr
console.log(foo, bar, baz) // 100 200 300

// 如果想获取最后一个值,可以把前边的变量删掉,但是要保留逗号
const [, , f] = arr
console.log(f) // 300


// 变量前加 ...   表示提取从当前位置开始往后的所有成员,成员放在数组中
// ... 的用法只能在最后位置使用
const [a, ...rest] = arr
console.log(rest) // [ 200, 300 ]

// 如果变量少于数组长度,就会按顺序提取,后边的成员不会被提取
const [y] = arr
console.log(y) // 100


// 如果变量长度大于数组长度,多出来的变量就是 undefined
const [z, x, c, v] = arr
console.log(v) // undefined

// 可以给变量默认值,如果数组中没有这个成员,就用默认值
const [q, w, e = 1, r = 2] = arr
console.log(e, r) // 300 2

对象的解构

对象的解构是通过属性名来解构提取。

使用方法:

const obj = { name: 'aaa', age: 18 }

const { name } = obj
console.log(name)

const age = 20
// 如果要提取的变量名已经在外部声明过,可以将变量赋值给另一个变量名
// 可以设置默认值
const { age: objAge = 33 } = obj
console.log(age, objAge)  // 20 18

模板字符串字面量

const name = 'es2015'
const str = `hello ${name},this is a \'string\'`
console.log(str) // hello es2015,this is a 'string'
// 可以给模板字符串添加一个 标签函数
const str = console.log`hello world` // [ 'hello world' ]


const name = 'tom'
const gender = true
// 定义一个标签函数
// 标签函数的第一个参数是模板字符串中的内容 按照表达式 分割后的静态内容的数组
// 后边的参数就是模板字符串中出现的表达式的变量
// 返回值就是字符串最终的内容
// 可以在标签函数中对变量进行加工
function tag1(string, name, gender) {
  console.log(string) // [ 'hey, ', ' is a ', '' ]
  console.log(name, gender) // tom true
  const sex = gender ? 'man' : 'woman'
  return string[0] + name + string[1] + sex + string[2]
}
const str1 = tag1`hey, ${name} is a ${gender}`
console.log(str1) // hey, tom is a man
  • 传统字符串不支持换行,模板字符串支持换行
  • 模板字符串支持嵌入变量,比字符串拼接
  • 模板字符串可以使用标签函数对字符串进行加工

字符串的扩展方法

  • includes() 查找字符串中是否包含某个字符
  • startsWith() 字符串是否以某字符开头
  • endsWith() 字符串是否以某字符结尾
const message = 'Error: foo is not defined.'
console.log(message.startsWith('Error')) // true
console.log(message.endsWith('.'))  // true
console.log(message.includes('foo'))  // true

函数的扩展

参数默认值

// 在此之前给函数参数设置默认值是在函数内部通过判断来给参数默认值
function foo(enable) {
  enable = enable === undefined ? true : enable
  console.log(enable)
}

// 现在只需要在形参的后面设置一个 = 就可以设置默认值
// 这个默认值在没有传递实参或者实参是undefined的时候使用
// 如果传递多个参数,带默认值的参数放在最后,不然可能没法工作
function foo(bar, enable = true) {
  console.log(bar, enable) // 111 true
}
foo(111)

剩余参数

// 以前接收未知个数的参数都通过 arguments, arguments 是给类数组
function foo() {
  console.log(arguments)
}

// ES2015 中新增了 ... 接收剩余参数
// 形参以数组的形式接收从当前位置开始往后所有的实参
// 只能出现在形参的最后一个,且只可以使用一次
function foo(first, ...args) {
  console.log(args)
}
foo(1, 2, 3, 4)

展开数组

const arr = ['foo', 'bar', 'baz']
console.log.apply(console, arr) // es2015 前
console.log(...arr) // es2015 后
// foo bar baz

箭头函数

  • 简短易读
const inc = n => n + 1
console.log(inc(100))
  • 不会改变this指向
const foo = {
  name: 'tom',
  sayName: function () {
    console.log(this.name)
  },
  sayName1: () => {
    console.log(this.name)
  },
  sayNameAsync: function () {
    setTimeout(() => {
      console.log(this.name)
    })
  }
}
foo.sayName() // tom
foo.sayName1() // undefined
foo.sayNameAsync() // tom

对象的扩展

对象字面量的增强

const bar = '456'
const obj = {
  foo: 123,
  // 传统写法必须 : 后面跟上变量
  // bar: bar
  // es2015 这样的写法和上边等价
  bar,
  // 传统的方法后边用 : 跟一个 function
  // method1: function(){},
  // 现在省略 : 直接 (){} 和上边也是等价的
  // 但是背后其实就是普通的 function,这里的this也会指向当前对象
  method1() { },
  // 之前表达式不能直接写在对象的属性里,需要obj[]的方式来声明
  // 现在可以直接在变量中用 [] 来声明属性
  [1 + 1]: 456,
}
// 之前表达式不能直接写在对象的属性里,需要obj[]的方式来声明
obj[Math.random()] = 123

对象扩展方法

  • Object.assign() 将多个源对象中的属性复制到一个目标对象中,如果有相同的属性,源对象中的属性会覆盖目标对象中的属性。后边的对象会覆盖前边的对象
const source1 = {
  a: 123,
  b: 123
}
const source2 = {
  b: 789,
  d: 789
}

const target = {
  a: 456,
  c: 456
}
const result = Object.assign(target, source1, source2)
console.log(target)  // { a: 123, c: 456, b: 789, d: 789 }
console.log(result === target) // true
  • Object.is() 判断两个值是否相等
console.log(0 == false)  //true
console.log(0 === false) // false
console.log(+0 === -0) // true
console.log(+0 === -0) // true
console.log(NaN === NaN) // false

console.log(Object.is(+0 === -0)) // false
console.log(Object.is(NaN, NaN))  // true

Proxy 代理对象

如果想要监视对象中的属性读写,可以使用es5中的 Object.defineProperty,为对象添加属性,这样的话就可以捕获到对象当中属性的读写过程。这种方法应用的非常广泛,vue3.0以前的版本就是使用的这种方法实现的数据响应,从而实现的双向数据绑定。

es2015 中全新设计了一个叫Proxy 的类型,专门用来为对象设置访问代理器的。通过Proxy 可以轻松的监视到对象的读写过程。相比于 Object.defineProperty, 它的功能更为强大,使用起来也更为方便。

const person = {
  name: 'aaa',
  age: 20
}
// 创建一个Proxy 的实例,第一个参数为要操作的对象
// 第二个参数也是一个对象 - 代理的处理对象
// 通过 get 方法监视属性的访问,通过 set 方法监视设置属性的过程
const personProxy = new Proxy(person, {
  // get方法接收的参数分别为:目标对象, 访问的属性名
  // 返回值作为外部访问这个属性的结果
  get(target, property) {
    // console.log(target, property)  // { name: 'aaa', age: 20 } name
    // return 'zhangsan'
    // 判断target中是否有当前属性,有就返回,没有返回 undefined 或默认值
    return property in target ? target[property] : 'default'
  },
  // set 方法参数:目标对象,设置的属性,设置的值
  set(target, property, value) {
    // console.log(target, property, value) // { name: 'aaa', age: 20 } gender true
    if (property === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError(`${value} is not an int`)
      }
    }
    target[property] = value
  }
})
console.log(personProxy.name) // aaa
console.log(personProxy.xxx) // default

personProxy.gender = true
// personProxy.age = '100' // TypeError: 100 is not an int
personProxy.age = 100
console.log(person) // { name: 'aaa', age: 100, gender: true }

Proxy vs. Object.defineProperty

image.png
  • defineProperty只能监视属性的读写,Proxy 能够监视到更多对象操作,eg:delete操作,对对象当中方法的调用等。
const person = {
  name: 'aaa',
  age: 20
}

const personProxy = new Proxy(person, {
  // 外部对这个proxy对象进行delete操作的时候执行
  // 参数:代理目标对象,要删除的属性名
  deleteProperty(target, property){
    console.log(target, property) // { name: 'aaa', age: 20 } age
    delete target[property]
  }
})
delete personProxy.age
console.log(person) // { name: 'aaa' }
  • Proxy 更好的支持数组对象的监视
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)
console.log(list)  // [ 100 ]
  • Proxy 是以非侵入的方式监管了对象的读写

Reflect

Reflect 是es2015中提供的统一的对象操作API。Reflect 属于一个静态类,不能通过 new 方法实例一个对象,只能调用静态类中的静态方法,eg: Reflect.get().

Reflect 内部封装了一系列对对象的底层操作,提供了14个方法操作对象,其中一个已经废弃了。剩下的13个和Proxy中的方法是对应的。Reflect成员方法就是Proxy处理对象方法内部的默认实现。

const obj = {
  foo:'123',
  bar:'456'
}
const proxy = new Proxy(obj,{
  // 没有添加处理对象的方法 等同于 将方法原封不动的交给 Reflect 执行
  get(target, property){
    return Reflect.get(target,property)
  }
})

为什么要有Reflect对象

提供一套统一的用于操作对象的API。

以往操作对象时可能使用Object对象上的方法,也可能使用delete 或 in 这样的操作符,这些对于新手来说实在是太乱了,没有什么规律。Reflect 就是将对象的操作方式统一。

const obj = {
  name: 'aaa',
  age: 18
}

// 操作对象方法不统一
console.log('age' 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))
  1. Reflect.get(target, name, receiver)
    查找并返回target对象的name属性,没有返回undefined.
const obj = {
  name: 'aaa',
  age: 18
}
console.log(Reflect.get(obj, 'name')) // aaa

如果name属性部署了读取函数(getter),则读取函数的this绑定receiver。

var myObject = {
  foo: 1,
  bar: 2,
  get baz() {
    return this.foo + this.bar;
  },
};
var myReceiverObject = {
  foo: 4,
  bar: 4,
};

Reflect.get(myObject, 'baz', myReceiverObject) // 8
  1. Reflect.set(target, name, value, receiver)
const obj = {
  name: 'aaa',
  age: 18
}
Reflect.set(obj, 'gender', 'man')
console.log(obj) // { name: 'aaa', age: 18, gender: 'man' }
  1. Reflect.has(obj, name)
    对应 name in obj 里面的 in 运算符。
    代表 obj 对象里是否有 name 属性。

  2. Reflect.deleteProperty(obj, name)
    等同于 delete obj[name],用于删除对象的属性

  3. Reflect.construct(target, args)
    等同于 new Gouzao(...args)

function Person(name){
  this.name = name
}

// new
const person1 = new Person('zhangsan')

// Reflect.construct
const person2 = Reflect.construct(Person, ['zhangsan'])
  1. Reflect.getPrototypeOf(obj)
    用于读取对象的proto属性,对应Object.getPrototypeOf(obj)
const myObj = new FancyThing();

// 旧写法
Object.getPrototypeOf(myObj) === FancyThing.prototype;

// 新写法
Reflect.getPrototypeOf(myObj) === FancyThing.prototype;
  1. Reflect.setPrototypeOf(obj, newProto)
    用于设置目标对象的原型(prototype),对应 Object.setPrototypeOf(obj, newProto)
const myObj = {};

// 旧写法
Object.setPrototypeOf(myObj, Array.prototype);

// 新写法
Reflect.setPrototypeOf(myObj, Array.prototype);

myObj.length // 0
  1. Reflect.apply()

  2. Reflect.defineProperty(target, propertyKey, attribute)
    基本等同于Object.defineProperty,用来为对象定义属性.

10.Reflect.getOwnPropertyDescriptor(target, propertyKey)
Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象

  1. Reflect.isExtensible (target)
    Reflect.isExtensible方法对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展。

12.Reflect.preventExtensions(target)
Reflect.preventExtensions对应Object.preventExtensions方法,用于让一个对象变为不可扩展

  1. Reflect.ownKeys(target)
    返回对象的所有属性

Promise

class 类

// 通过定义一个函数,来定义一个类型
function Person(name){
  this.name = name
}
Person.prototype.say = function(){
  console.log(`hi, my name is ${this.name}`)
}

// es2015 使用 class 关键词声明一个类型
class Person {
  // constructor 是当前类型的构造函数
  constructor(name) {
    this.name = name
  }
  say() {
    console.log(`hi, my name is ${this.name}`)
  }
}
const p = new Person('tom')
p.say()

静态方法

在类型当中的方法一般分为实例方法和静态方法。实例方法是通过这个类型的实例去调用,静态方法是直接使用这个类型去调用。

使用函数定义类型时,静态方法直接在函数内部定义就可以。而ES2015中新增添加静态成员的 static 关键词。使用static定义的静态方法中如果有 this,那它指向这个类型,而不是实例。

class Person {
  constructor(name) {
    this.name = name
  }
  //  使用 static 关键词定义静态方法
  static create(name) {
    return new Person(name)
  }
}
// 直接使用类型来调用静态方法
Person.create('tom')

继承 extends

继承是面向对象中非常重要的特性,通过这个特性可以抽象出来相似类型之间重复的地方。在es2015之前大多使用原型的方式实现继承,在es2015中使用extends实现继承。

class Person {
  constructor(name) {
    this.name = name
  }
  say() {
    console.log(`hi, my name is ${this.name}`)
  }
}
// 使用 extends 继承父类
// Student 继承自 Person,Student中就有Person中所有的成员
class Student extends Person {
  constructor(name,number){
    // super 对象始终指向父类,调用它就是调用了父类的构造函数
    super(name)
    this.number = number
  }
  hello(){
    // 可以使用 super 对象访问父类的成员
    super.say()
    console.log(`my school number is ${this.number}`)
  }
}
const s = new Student('jack', '100')
s.hello()
// hi, my name is jack
// my school number is 100

Set 数据结构

es2015中提供了一个叫做Set的全新的数据结构,可以理解为集合。它与传统数组类似,但是Set中的成员不能重复。

// Set 是一个类型,通过创建它的实例就可以存放不重复的数据
const s = new Set()

// 通过 add方法往集合中添加数据,返回集合本身,所以还可以链式调用
// 如果向集合中添加一个已经存在的值,则这个添加会被忽略
s.add(1).add(2).add(3).add(2)
console.log(s) // Set(3) { 1, 2, 3 }

// 想要遍历集合中的数据可以使用集合的forEach方法
s.forEach(v => console.log(v)) // 1 2 3
// 遍历集合中的数据也可以使用es2015中的 for(...of...)
for (let i of s) {
  console.log(i)
}
//  1 2 3

// 通过集合的 size 属性获取集合的长度,相当于数组中的length
console.log(s.size) // 3

// has 方法判断集合中是否存在某个值
console.log(s.has(2))

// delete 方法删除集合中的数据,删除成功返回 true
console.log(s.delete(1))

// clear 清除数组中的数据
s.clear()
console.log(s) // Set(0) {}

// Set 最常用的是给数组去重
const arr = [1, 2, 1, 3, 4, 2]
// Set 的实例接受一个数组,数组里的值作为这个实例的初始值,重复的值会被忽略
// 使用 Array.from() 方法或者 扩展运算符 将集合再次转换为数组
// result = Array.from(new Set(arr))
const result = [...new Set(arr)]
console.log(result) // [ 1, 2, 3, 4 ]

Map 数据结构

Map 数据结构与对象类似,都是键值对集合,但是对象结构中的键只能是字符串,存放复杂数据时会有问题,Map 和对象唯一的区别就是可以以任意类型作为键。

// 使用 new 创建一个 Map 的实例
const m = new Map()

const tom = { name: 'tom' }
const jack = { name: 'jack' }

// 可以使用实例的 set 方法为这个Map实例设置键值对
m.set(tom, 90)
m.set(jack, 95)
console.log(m) // Map(2) { { name: 'tom' } => 90, { name: 'jack' } => 95 }
// get 方法获取某个属性的值
console.log(m.get(tom)) // 90
// has 方法判断是否存在某个属性
m.has(tom)
// delete 方法删除某个属性
m.delete(tom)
// clear  方法清空Map实例中的属性
// m.clear()

// forEach 方法遍历这个实例对象
m.forEach((value, key) => {
  console.log(value, key) // 95 { name: 'jack' }
})

for (let i of m) {
  console.log(i) // [ { name: 'jack' }, 95 ]
}

Symbol

在es2015之前,对象的属性名都是字符串,而字符串是有可能重复的,重复就会产生冲突。
eg:如果 a.js 和 b.js 同时引用shared.js文件中的cache对象,a文件给cache添加了属性foo,b文件也添加了属性foo,就造成了冲突。

以往的解决方式是约定,例如a文件使用'a_'开头命名,b文件使用'b_'命名。但是约定的方式只是规避了问题,并没有解决问题,如果有人不遵守约定,问题还是会出现。

// shared.js ==========================
const cache = {}

// a.js ==============================
cache['foo'] = Math.random()
cache['a_foo'] = Math.random()
// b.js ==============================
cache['foo'] = '123'
cache['b_foo'] = '123'

console.log(cache) // { foo: '123' }

es2015为了解决这种问题,提出了一种全新的数据类型 Symbol,表示一个独一无二的值。

使用Symbol函数就能创建一个Symbol类型的数据,而且使用typeof打印出来的结果就是 symbol,说明 symbol 就是一个数据类型。这个数据类型最大的特点就是独一无二,通过 Symbol 函数创建的每一个值都是唯一的,永远不会重复。

考虑到开发过程中的调试,Symbol 函数允许传入一个字符串作为这个值的描述文本,对于多次使用 Symbol 的情况,从控制台可以区分出是哪个 Symbol。

从es2015开始可以对象可以使用 Symbol 类型的值作为属性名,所以现在对象的属性名可以为两种类型,String 和 Symbol。

// 使用Symbol函数就能创建一个Symbol类型的数据
const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // symbol
console.log(Symbol() === Symbol()) // false

console.log(Symbol('foo')) // Symbol(foo)
console.log(Symbol('bar')) // Symbol(bar)


const obj = {
  [Symbol()]: '789'
}
obj[Symbol()] = '123'
obj[Symbol()] = '456'

console.log(obj) // { [Symbol()]: '789', [Symbol()]: '123', [Symbol()]: '456' }

可以利用 Symbol 模拟实现对象的私有成员。以前定义私有成员都是靠约定,例如约定''开头就是私有成员,外部不可以访问''开头的成员。现在可以直接使用 Symbol:

// 模拟实现对象私有成员
// a.js =======================
// 使用Symbol 创建私有成员的属性名
// 在对象内部可以使用创建属性时的 Symbol 拿到对应的属性成员
const name = Symbol()
const person = {
  [name]:'aaa',
  say(){
    console.log(this[name])
  }
}

// b.js =======================
// 在外部无法创建完全相同的 Symbol,所以无法直接访问这个 Symbol成员的属性,只能调用普通名词的成员
person.say() /// aaa

Symbol最主要的作用就是为对象添加独一无二的属性名。

补充

  • Symbol 函数创建的值是唯一的,即使传了一样的参数也是唯一的。
console.log(Symbol('foo') === Symbol('foo')) // false
  • 如果想在全局复用一个相同的 Symbol 值,可以使用全局变量的方式实现,或者也可以使用 Symbol.for() 方法实现。

Symbol.for() 方法传递一个字符串,传递相同的字符串就会返回相同的 Symbol 值。

这个方法内部维护了一个全局的注册表,为字符串和 Symbol 值提供了一个一一对应的关系。需要注意的是,内部维护的是字符串和 Symbol 值对应的关系,所以如果 for 方法传的不是字符串类型,会转换成字符串类型。

const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
console.log(Symbol.for(true) === Symbol.for(true))
  • Symbol 中内置了很多 Symbol 常量,用来作为内部方法的标识,这些标识可以让js对象实现一些内置的接口。
console.log(Symbol.iterator) // Symbol(Symbol.iterator)
console.log(Symbol.hasInstance) // Symbol(Symbol.hasInstance)

例如定义一个obj对象,调用对象的 toString() 方法,结果默认就是 [object Object], 我们把这样的字符串叫做对象的 toString 标签,如果想要自定义对象的toString 标签,我们可以在对象中添加特定的成员来标识。考虑到如果使用字符串去添加这种标识符,就有可能跟内部成员产生重复,所以ECMAScript 要求我们使用 Symbol 值实现这样的接口。

给对象添加一个 Symbol.toStringTag 这样的属性,让它等于 ‘XObject’,此时toString 方法打印出的就是 XObject。

const obj = {
  [Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString()) // [object XObject]
  • 对象中定义的 Symbol 属性使用for in循环是无法拿到的,通过 Object.keys() 方法也获取不到,使用 JSON.stringify(obj) 序列化对象,Symbol 也会被忽略。这些特性都使 Symbol 属性的值特别适合作为私有属性。想要获取 Symbol 属性名可以使用 **Object.getOwnPropertySymbols() **方法,只能获取对象中所有的 Symbol 类型的属性名

for...of 循环

在 ECMAScript 中遍历数组有很多的方法

  • for 循环,适合遍历普通的数组
  • for...in 循环,适合遍历键值对
  • 一些对象的遍历方法,eg:forEach

这些遍历方式都有一定的局限性,所以 es2015 引入了全新的 for...of 循环。这种方式以后会作为遍历所有数据结构的统一方式。

  • for...of 循环可以使用 break 终止遍历
  • 伪数组,Set实例,Map实例也可以使用 for...of 遍历
  • 无法遍历普通对象

Iterable

ES中能够表示有结构的数据类型越来越多,Object, Array, Ser, Map... 为了给各种各样的数据结构提供统一的遍历方式,es2015提供了 Iterable 接口。实现 Iterable 接口就是 for...of 的前提

// 实现可迭代接口(Iterable)
const obj = {
  store: [1, 2, 3],
  [Symbol.iterator]: function () {
    let i = 0
    const self = this
    return {
      next: function () {
        return {
          value: self.store[i],
          done: i++ >= self.store.length
        }
      }
    }
  }
}

for (let item of obj) {
  console.log(item)
}

Generator

function* foo() {
  console.log('111')
  yield 100
  console.log('222')
  yield 200
}
const result = foo()
console.log(result.next()) // 111 { value: 100, done: false }
console.log(result.next()) // 222 { value: 200, done: false }

ES Modules

ES2016

ES2016 新增了两个方法。

  • Array.prototype.includes()
    ES2016之前查找数组中是否有某个元素使用的是 indexOf 方法,这个方法不存在时返回 -1, 存在返回它的位置,但是它不能用于查找是否存在NaN。而includes 方法弥补了这个缺点,可以用于查找是否有 NaN,返回结果为 true 或 false。

  • 指数运算符 **
    ES2016之前想要进行指数运算使用的是Math.pow(3, 2)来计算得到10,ES2016新增了指数运算符 ** ,console.log(3 ** 2) 可以得到9.

ES2017

  • Object.values() 获得对象所有的值,对应 Object.keys() 方法可以获得对象所有的键。
  • Object.entries() 方法可以获得对象的键值对的数组
const obj = { a: 1, b: 2 }
console.log(Object.entries(obj))  // [ [ 'a', 1 ], [ 'b', 2 ] ]
for (const [key, value] of Object.entries(obj)) {
  console.log(key, value)
  // a 1
  // b 2
}
console.log(new Map(Object.entries(obj))) // Map(2) { 'a' => 1, 'b' => 2 }
  • Object.getOwnPropertyDescriptors() 获取对象中属性的完整描述信息

  • String.prototype.padStart(num, str) / String.prototype.padEnd 用指定字符串填充原字符串开头或结尾,知道达到指定位数

console.log('wl'.padEnd(10, '----------')) // wl--------
console.log('102'.padStart(4, '0')) // 0102
  • 可以在函数参数中添加尾逗号
function(
  a,
  b,
) {

}
  • Async / Await

你可能感兴趣的:(学习笔记-ECMAScript新特性)