ES6新特性总结

422 阅读16分钟

ES6新特性总结

ps: 借鉴阮一峰老师的ECMAScript 6 入门;传送门,MDN文档;传送门

let、const、var关键字

三个关键字的比较:

varletConst
变量提升××
全局变量××
重复声明××
重新赋值×
只声明不初始化×
暂时死区×
块作用域×

什么是暂时性死区?

let 和 const 声明的变量不存在变量提升,其作用域都是块级作用域,凡是在声明变量之前使用变量就会报错,所以,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)

const关键字你不知道的事:

1、对象属性修改和数组元素变化不会触发 const 错误

原因: const 实际上是将 const 变量存储的那个指针固定,因此如果声明的是引用数据类型,只是不能修改指针,但是可以修改指针指向的内存数据

2、声明对象类型优先使用 const ,非对象类型声明优先选择 let

变量的解构赋值

数组的解构

ES5中,为了从数组中获取特定数据并赋值给变量,我们经常会进行如下的操作:

const arr = [100, 200, 300]
const a = arr[0]
const b = arr[1]
const c = arr[2]
console.log(a) // 100
console.log(b) // 200
console.log(c) // 300

ES6为数组添加了解构功能,以简化获取数组中数据的过程。数组解构采用数组字面量的语法形式,即等号左边的变量通过在数组中的位置去取得等号右边的数组字面量相同位置的值。

例子:

const arr = [100, 200, 300]
const [a, b, c] = arr
console.log(a) // 100
console.log(b) // 200
console.log(c) // 300

如果只需要拿到数组指定位置的元素

例子:

const arr = [100, 200, 300]
const [a] = arr
console.log(a) // 100
const [, b] = arr
console.log(b) // 200
const [, , c] = arr
console.log(c) // 300

其中,我们还可以使用扩展运算符的方式(...)结构出从当前位置到最后位置从而合并成一个新数组

例子:

const arr = [100, 200, 300]
const [a, ...arr1] = arr
console.log(a) // 100
console.log(arr1) // [200, 300]

当解构左边比实际数组的长度还多的时候,多余的变量默认值为undefind

例子:

const arr = [100, 200, 300]
const [a, b, c, d] = arr
console.log(d) // undefind
// 我们可以给d设置默认值
const [a, b, c, d = 400] = arr
console.log(d) // 400

对象的解构

对象的解构和数组解构不同的是,数组解构是根据数组的下标顺序,解构出相对应下标的元素,

而对象是没有下标这种规律性的,需要根据对象中的属性名解构。

其他特点和数组的解构都是相同的。

例子:

const obj = { name: 'weision', age: 18 }
const { name } = obj
console.log(name) // weision

// 利用这个特性,可以结构出console中的log方法,帮助我们在开发中写少量的代码
const { log } = console
log(name) // weision

模版字符串

传统的 JavaScript 语言,输出模板通常是这样写的(下面使用了 jQuery 的方法)。

$('#result').append(
  'There are <b>' + basket.count + '</b> ' +
  'items in your basket, ' +
  '<em>' + basket.onSale +
  '</em> are on sale!'
);

上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。

$('#result').append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

// 普通字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is
 not legal.`

console.log(`string text line 1
string text line 2`);

// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

字符串的扩展方法

这里介绍三个比较常用的查找字符串的方法

includes()startaWith()endsWith()

分别为

  • 是否包含某个字符串
  • 是否以某个字符串开头
  • 是否以某个字符串结尾

他们返回的都是 Boolean 类型

例子:

const message = 'Error: foo is not defind.'

console.log(message.includes(‘foo’)) // true 
console.log(message.startaWith(‘Error’)) // true 
console.log(message.endsWith(‘.’)) // true 

箭头函数

在 MDN 上的定义:箭头函数表达式的语法比函数表达式更简洁,并且没有自己的thisargumentssupernew.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。

// 普通函数
function inc (number) {
	return number + 1
}
console.log(inc(1)) // 2

// 箭头函数
const inc = (n) => {
    return n + 1
}
console.log(inc(1)) // 2

// 最简方式
const inc = n => n + 1
console.log(inc(1)) // 2

箭头函数不会改变 this 指向

例子:

// 普通函数
const person = {
  name: 'weision',
  sayHi: function () {
    console.log(`hi, my name is ${this.name}`)
  }
}
person.sayHi() // hi, my name is weision

// 箭头函数
const person = {
  name: 'weision',
  sayHi: () => {
    console.log(`hi, my name is ${this.name}`)
  }
}
person.sayHi() // hi, my name is undefined

对象的新增方法

1. Object.assign()

基础用法

Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

如果只有一个参数,Object.assign()会直接返回该参数。

const obj = {a: 1};
Object.assign(obj) === obj // true

如果该参数不是对象,则会先转成对象,然后返回。

typeof Object.assign(2) // "object"

注意点

1.浅拷贝

Object.assign()方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

上面代码中,源对象obj1a属性的值是一个对象,Object.assign()拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。

2.数组的处理

Object.assign()可以用来处理数组,但是会把数组视为对象。

Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]

上面代码中,Object.assign()把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1

2.Object.is()

ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。

ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

Object.is('foo', 'foo') // true

Object.is({}, {}) // false

不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

+0 === -0 //true
NaN === NaN // false

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

Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

语法

const p = new Proxy(target, handler)

参数

target

要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler

一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

handler两个重要的对象的方法

  1. handler.get() 用于拦截对象的读取属性操作。

    var p = new Proxy(target, {
      get: function(target, property, receiver) {
      }
    });
    
  2. handler.set() 用于设置属性值操作的捕获器。

    const p = new Proxy(target, {
      set: function(target, property, value, receiver) {
      }
    });
    

参数解读

target: 目标对象。

property: 将被设置的属性名或 Symbol。

value: 新属性值。

receiver: 最初被调用的对象。通常是 proxy 本身,但 handler 的 set 方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是 proxy 本身)。

比如:

假设有一段代码执行 `obj.name = "jen"``obj` 不是一个 proxy,且自身不含 `name` 属性,但是它的原型链上有一个 proxy,那么,那个 proxy 的 `set()` 处理器会被调用,而此时,`obj` 会作为 receiver 参数传进来。

Proxy 对比 Object.defineProperty()

优势1:Proxy 可以监视读写以外的操作

const person = {
  name: 'weision',
  age: 18
}

const personProxy = new Proxy(person, {
  deleteProperty (target, property) {
    console.log('delete', property)
    delete target[property] 
  }
})

delete personProxy.age
console.log(person)

优势2: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)
listProxy.push(100)

优势3:Proxy 不需要侵入对象

const person = {}

Object.defineProperty(person, 'name', {
  get () {
    console.log('name 被访问')
    return person._name
  },
  set (value) {
    console.log('name 被设置')
    person._name = value
  }
})
Object.defineProperty(person, 'age', {
  get () {
    console.log('age 被访问')
    return person._age
  },
  set (value) {
    console.log('age 被设置')
    person._age = value
  }
})

person.name = 'jack'

console.log(person.name)

// Proxy 方式更为合理
const person2 = {
  name: 'zce',
  age: 20
}

const personProxy = new Proxy(person2, {
  get (target, property) {
    console.log('get', property)
    return target[property]
  },
  set (target, property, value) {
    console.log('set', property, value)
    target[property] = value
  }
})

personProxy.name = 'jack'

console.log(personProxy.name)
console.log(personProxy.name)

Promise

ps: 借鉴MDN上promise概念描述,加上自己的语境和案例记录学习promise的成果,MDN传送门

描述

一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled): 意味着操作成功完成。
  • 已拒绝(rejected): 意味着操作失败。

待定状态的 Promise 对象要么会通过一个值被兑现(fulfilled),要么会通过一个原因(错误)被拒绝(rejected)。当这些情况之一发生时,我们用 promise 的 then 方法排列起来的相关处理程序就会被调用。如果 promise 在一个相应的处理程序被绑定时就已经被兑现或被拒绝了,那么这个处理程序就会被调用,因此在完成异步操作和绑定处理方法之间不会存在竞争状态。

解决什么问题

promise主要是用来解决以下问题的:

  • 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象
  • promise可以支持多个并发的请求,获取并发请求中的数据

// 回调地狱,只是示例,不能运行

$.get('/url1', function (data1) {
  $.get('/url2', data1, function (data2) {
    $.get('/url3', data2, function (data3) {
      $.get('/url4', data3, function (data4) {
        $.get('/url5', data4, function (data5) {
          $.get('/url6', data5, function (data6) {
            $.get('/url7', data6, function (data7) {
              // 略微夸张了一点点
            })
          })
        })
      })
    })
  })
})

以上回调地狱代码示例,在远古时期(promise诞生前)经常遇到,这让开发者很难维护和读解代码。

Promise 基本示例

const promise = new Promise(function (resolve, reject) {
  // 这里用于“兑现”承诺

  // resolve(100) // 承诺达成

  reject(new Error('promise rejected')) // 承诺失败
})

promise.then(function (value) {
  // 即便没有异步操作,then 方法中传入的回调仍然会被放入队列,等待下一轮执行
  console.log('resolved', value)
}, function (error) {
  console.log('rejected', error)
})

console.log('end')

Promise 方式的 AJAX

function ajax (url) {
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

ajax('/api/foo.json').then(function (res) {
  console.log(res)
}, function (error) {
  console.log(error)
})

Promise 常见误区

function ajax (url) {
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

// 嵌套使用 Promise 是最常见的误区
// ajax('/api/urls.json').then(function (urls) {
//   ajax(urls.users).then(function (users) {
//     ajax(urls.users).then(function (users) {
//       ajax(urls.users).then(function (users) {
//         ajax(urls.users).then(function (users) {

//         })
//       })
//     })
//   })
// })

Promise 链式调用

我们可以用 promise.then()promise.catch()promise.finally() 这些方法将进一步的操作与一个变为已敲定状态的 promise 关联起来。这些方法还会返回一个新生成的 promise 对象,这个对象可以被非强制性的用来做链式调用,就像这样:

以下所有示例中的ajax方法都在上面Promise常见误区中


ajax('/api/users.json')
  .then(function (value) {
    console.log(1111)
    return ajax('/api/urls.json')
  }) // => Promise
  .then(function (value) {
    console.log(2222)
    console.log(value)
    return ajax('/api/urls.json')
  }) // => Promise
  .then(function (value) {
    console.log(3333)
    return ajax('/api/urls.json')
  }) // => Promise
  .then(function (value) {
    console.log(4444)
    return 'foo'
  }) // => Promise
	.catch(function (value) {
    console.log('error', value)
  })

Promise 静态方法

  • Promise.resolve(value)

    • 返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果您不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。
    Promise.resolve('foo')
      .then(function (value) {
        console.log(value) // foo
      })
    
  • Promise.reject(reason)

    • 返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法
    // Promise.reject 传入任何值,都会作为这个 Promise 失败的理由
    
    Promise.reject('anything')
      .catch(function (error) {
        console.log(error)
      })
    
    Promise.reject(new Error('rejected'))
      .catch(function (error) {
        console.log(error)
      })
    
  • Promise.all(iterable)

    • 这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。
    // 需要等待所有promise结束才会返回对应结果。
    // 只有所有的promise都成功才能走.then()
    // 否则走catch
    var promise = Promise.all([
      ajax('/api/users.json'),
      ajax('/api/posts.json')
    ])
    
    promise.then(function (values) {
      console.log(values)
    }).catch(function (error) {
      console.log(error
    }
    
  • Promise.any(iterable)

    • 接收一个Promise对象的集合,当其中的一个 promise 成功,就返回那个成功的promise的值。
    // 只要有一个promise成功,立刻返回该成功的promise的值。
    // 否则走catch
    
    var promise = Promise.all([
      ajax('/api/users.json'),
      ajax('/api/posts.json')
    ])
    
    promise.then(function (values) {
      console.log(values)
    }).catch(function (error) {
      console.log(error)
    }
    
  • Promise.race(iterable)

    • 当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
    // 实现超时控制
    // 如果 reauest 请求未得到响应,500毫秒内就触发定时器,返回reject
    const request = ajax('/api/posts.json')
    const timeout = new Promise((resolve, reject) => {
      setTimeout(() => reject(new Error('timeout')), 500)
    })
    
    Promise.race([
      request,
      timeout
    ])
    .then(value => {
      console.log(value)
    })
    .catch(error => {
      console.log(error)
    })
    
    

class 类

类声明

定义类的一种方法是使用类声明。要声明一个类,你可以使用带有class关键字的类名(这里是Person)。

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

提升

函数声明类声明之间的一个重要区别在于, 函数声明会提升,类声明不会。你首先需要声明你的类,然后再访问它,否则类似以下的代码将抛出ReferenceError

let p = new Rectangle(); // ReferenceError

class Rectangle {}

构造函数

constructor 方法是类的构造函数,是一个默认方法,通过 new 命令创建对象实例时,自动调用该方法。一个类必须有 constructor 方法,如果没有显式定义,一个默认的 consructor 方法会被默认添加。所以即使你没有添加构造函数,也是会有一个默认的构造函数的。一般 constructor 方法返回实例对象 this ,但是也可以指定 constructor 方法返回一个全新的对象,让返回的实例对象不是该类的实例。

原型方法

class Person {
  constructor (name) {
    this.name = name
  }

  say () {
    console.log(`hi, my name is ${this.name}`)
  }
}

const p = new Person('tom')
p.say() // hi, my name is tom

静态方法

static 关键字用来定义一个类的一个静态方法。调用静态方法不需要实例化该类,但不能通过一个类实例调用静态方法。静态方法通常用于为一个应用程序创建工具函数。

class Person {
  constructor (name) {
    this.name = name
  }

  say () {
    console.log(`hi, my name is ${this.name}`)
  }

  static create (name) {
    return new Person(name)
  }
}

const tom = Person.create('tom')
tom.say()

继承 extends

extends 关键字在 类声明类表达式 中用于创建一个类作为另一个类的一个子类。

class Person {
  constructor (name) {
    this.name = name
  }

  say () {
    console.log(`hi, my name is ${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}`)
  }
}

const s = new Student('jack', '100')
s.hello() // hi, my name is jack; my school number is 100

Set

简述

Set对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即 Set 中的元素是唯一的。

声明

new Set([iterable]); 其中iterable是一个可迭代对象,其中的所有元素都会被加入到 Set 中。null被视作 undefined。也可以不传入[iterable],通过其add方法来添加元素。

方法

  • **add(value):**在Set对象尾部添加一个元素。返回该Set对象。
  • **clear():**移除Set对象内的所有元素。
  • **delete(value):**移除Set中与这个值相等的元素,返回Set.prototype.has(value)在这个操作前会返回的值(即如果该元素存在,返回true,否则返回false)。Set.prototype.has(value)在此后会返回false
  • **entries():**返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值的[value, value]数组。为了使这个方法和Map对象保持相似, 每个值的键和值相等。
  • **forEach(callbackFn, [thisArg]):**按照插入顺序,为Set对象中的每一个值调用一次callBackFn。如果提供了thisArg参数,回调中的this会是这个参数。
  • **has(value):**返回一个布尔值,表示该值在Set中存在与否。
  • **values():**返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。
  • **keys():values()**方法相同,返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。

示例

let mySet = new Set();

mySet.add(1); // Set [ 1 ]
mySet.add(5); // Set [ 1, 5 ]
mySet.add(5); // Set [ 1, 5 ]
mySet.add("some text"); // Set [ 1, 5, "some text" ]
let o = {a: 1, b: 2};
mySet.add(o);

mySet.add({a: 1, b: 2}); // o 指向的是不同的对象,所以没问题

mySet.has(1); // true
mySet.has(3); // false
mySet.has(5);              // true
mySet.has(Math.sqrt(25));  // true
mySet.has("Some Text".toLowerCase()); // true
mySet.has(o); // true

mySet.size; // 5

mySet.delete(5);  // true,  从set中移除5
mySet.has(5);     // false, 5已经被移除

mySet.size; // 4, 刚刚移除一个值

console.log(mySet);
// logs Set(4) [ 1, "some text", {…}, {…} ] in Firefox
// logs Set(4) { 1, "some text", {…}, {…} } in Chrome

// 迭代整个set

for (let item of mySet) console.log(item);
// 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}

for (let item of mySet.keys()) console.log(item);
// 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}

for (let item of mySet.values()) console.log(item);
// 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}

for (let [key, value] of mySet.entries()) console.log(key);
// 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
//(键与值相等)

// 使用 Array.from 转换Set为Array
var myArr = Array.from(mySet); // [1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}]

// 如果在HTML文档中工作,也可以:
mySet.add(document.body);
mySet.has(document.querySelector("body")); // true

// Set 和 Array互换
mySet2 = new Set([1, 2, 3, 4]);
mySet2.size;               // 4
[...mySet2];               // [1,2,3,4]

// 用forEach迭代
mySet.forEach(function(value) {
  console.log(value);
});

应用场景:数组去重

利用set数据结构唯一特性,对数组去重

const arr = [1, 2, 1, 3, 4, 1]
// const result = Array.from(new Set(arr))
const result = [...new Set(arr)]
console.log(result) // [1, 2, 3, 4]

总结

与Array相比:

  • Set中存储的元素是唯一的,而Array中可以存储重复的元素。
  • Set中遍历元素的方式:Set通过for…of…,而Array通过for…in…。
  • Set是集合,不能像数组用下标取值。

Map

Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。

描述

一个Map对象在迭代时会根据对象中元素的插入顺序来进行 — 一个 for...of循环在每次迭代后会返回一个形式为[key,value]的数组。

Objects 和 maps 的比较

MapObject
意外的键Map 默认情况不包含任何键。只包含显式插入的键。一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。注意: 虽然 ES5 开始可以用 Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。
键的类型一个 Map的键可以是任意值,包括函数、对象或任意基本类型。一个Object 的键必须是一个 String或是 Symbol
键的顺序Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。一个 Object 的键是无序的注意:自ECMAScript 2015规范以来,对象确实保留了字符串和Symbol键的创建顺序; 因此,在只有字符串键的对象上进行迭代将按插入顺序产生键。
SizeMap 的键值对个数可以轻易地通过 size 属性获取Object 的键值对个数只能手动计算
迭代Mapiterable的,所以可以直接被迭代。迭代一个Object需要以某种方式获取它的键然后才能迭代。
性能在频繁增删键值对的场景下表现更好。在频繁添加和删除键值对的场景下未作出优化。

示例

let myMap = new Map();

let keyObj = {};
let keyFunc = function() {};
let keyString = 'a string';

// 添加键
myMap.set(keyString, "和键'a string'关联的值");
myMap.set(keyObj, "和键keyObj关联的值");
myMap.set(keyFunc, "和键keyFunc关联的值");

myMap.size; // 3

// 读取值
myMap.get(keyString);    // "和键'a string'关联的值"
myMap.get(keyObj);       // "和键keyObj关联的值"
myMap.get(keyFunc);      // "和键keyFunc关联的值"

myMap.get('a string');   // "和键'a string'关联的值"
                         // 因为keyString === 'a string'
myMap.get({});           // undefined, 因为keyObj !== {}
myMap.get(function() {}); // undefined, 因为keyFunc !== function () {}

使用 for..of 方法迭代 Map

let myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
for (let [key, value] of myMap) {
  console.log(key + " = " + value);
}
// 将会显示两个log。一个是"0 = zero"另一个是"1 = one"

for (let key of myMap.keys()) {
  console.log(key);
}
// 将会显示两个log。 一个是 "0" 另一个是 "1"

for (let value of myMap.values()) {
  console.log(value);
}
// 将会显示两个log。 一个是 "zero" 另一个是 "one"

for (let [key, value] of myMap.entries()) {
  console.log(key + " = " + value);
}
// 将会显示两个log。 一个是 "0 = zero" 另一个是 "1 = one"

使用 forEach() 方法迭代 Map

myMap.forEach(function(value, key) {
  console.log(key + " = " + value);
})
// 将会显示两个logs。 一个是 "0 = zero" 另一个是 "1 = one"

Symbol

symbol 是一种基本数据类型 。Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。

每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。

描述

直接使用Symbol()创建新的symbol类型,并用一个可选的字符串作为其描述。

var sym1 = Symbol();
var sym2 = Symbol('foo');
var sym3 = Symbol('foo');

上面的代码创建了三个新的symbol类型。 注意,Symbol("foo") 不会强制将字符串 “foo” 转换成symbol类型。它每次都会创建一个新的 symbol类型:

Symbol("foo") === Symbol("foo"); // false

下面带有new 运算符的语法将抛出 TypeError错误:

var sym = new Symbol(); // TypeError

示例

// 两个 Symbol 永远不会相等
console.log(Symbol() === Symbol()) // false

// 使用 Symbol 为对象添加用不重复的键
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj) // {Symbol(): "123", Symbol(): "456"}

// 也可以在计算属性名中使用
const obj = {
  [Symbol()]: 123
}
console.log(obj) // {Symbol(): 123}

场景

扩展对象,属性名冲突问题

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

// a.js =========================================
cache['foo'] = Math.random()

// b.js =========================================
cache['foo'] = '123'

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

上例中,cache 是一个全局对象,在a.js中设置属性foo的值,但又不小心在其他模块文件b.js中又设置一次属性foo,就会覆盖掉a.js中的foo

解决方案可以分别在a,b文件中设置属性名,a_foob_foo。但是这种约定不适用于多人协同开发,很好的解决方案就是使用Symbol

使用Symbol解决上述场景

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

// a.js ======================================
const foo = Symbol()
cache[foo] = Math.random()

// b.js =========================================
const foo = Symbol()
cache[foo] = '123'

console.log(cache)