ES6新特性

499 阅读13分钟

ECMAScript

概述

ECMAScript,即ES,是JavaScript的标准,JavaScript为ECMAscript的实现。

ECMAScript2015为es的代表版本,比起上个版本更新了很多内容,ES6为ES2015后新标准的统称。ES6相比起ES5.1的变化可分为四大类:

  • 解决原有语法上的一些问题和不足,如let和const关键字;

  • 对原有语法的增强,如解构、展开以及参数默认值等;

  • 全新的对象、方法以及功能,如Promise、Proxy等;

  • 全新的数据类型和数据结构,如Symbol、Map等。

let 与块级作用域

作用域:变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

在ES6之前,只有两种作用域:全局作用域和函数作用域。

随着版本的更新,出现了let和const关键字,用其定义变量会形成一个块级作用域

// 全局作用域和函数作用域
var n = 1;  // 定义在全局作用域中
function fn() {
    var n = 1;  // 定义在函数之中,只能在这个函数中访问该变量
}

//---------------------------------------------------------------------

// 块级作用域
if (flag) {
    let n = 1  // 定义在if代码块之中
}

console.log(n)  //  在if外面打印if内部定义的变量会报变量is not defined

在定义变量时,var关键字会将变量进行提升,而let则会产生一个暂时性死区:

// var
console.log(a)
var a = 1
// 这样不会报错,只会打印一个undefined

//---------------------------------------------------------------------
console.log(b)
let b = 1
// 此时会报一个引用错误,b is not defined

在es6之前通过循环给dom元素添加事件时,也会因为作用域的问题产生一些“预期之外”的问题:

var lis = document.getElementsByClassName('li')

for(var i = 0; i < lis.length; i ++) {
    lis[i].onClick =  function() {
        console.log(i)
    }
}

在成功给li添加上点击事件后,无论点击哪个li打印出的数值总是为lis.length - 1,与我们预期的效果并不一样。这也是因为用var定义计数器i之后,i是被定义在全局之中,当循环结束之时,i的数值已经变成了lis.length - 1,再去点击li的时候打印的结果当然就是同一个数值。在es6之前若解决此问题需要用到闭包:

var lis = document.getElementsByClassName('li')

for(var i = 0; i < lis.length; i ++) {
    lis[i].onClick =  (function (count) {
            return function() {
            console.log(count)
    	}
    })(i) // 这样应该没写错
}

在let关键字出现之后,只要用let定义计数器即可解决这样因为作用域引起的问题:

let lis = document.getElementsByClassName('li')
for(let i = 0; i < lis.length; i ++) {
    lis[i].onClick =  function() {
        console.log(i)
    }
}

const

const关键字基本上和let相同,只是const定义的是一个常量,定义之后不能修改:

const a = 4
a = 5

此时会报一个类型错误Assignment to constant variable

当然,这里的修改指的是修改该成员的内存地址,对于引用数据类型来说,仅修改其内部的数据是没有问题的:

const obj = {}
obj.name = '我要学习'

这种情况是没有问题的,但是如果将obj指向另一个内存地址,还是会报错

const obj = {}
obj = []
// 控制台:Uncaught TypeError: Assignment to constant variable.

解构

数组解构

在es6之前,当我们想要提取数组中的元素时,会用索引来进行取值

const arr = [0, 1, 2]
const n1 = arr[0]
const n2 = arr[1]
const n3 = arr[2]

// 打印n1,n2,n3为:0 1 2

在es6更新后,我们可以通过数组的解构来进行取值

const arr = [0, 1, 2]
const [n1, n2, n3] = arr

// 打印n1,n2,n3为:0 1 2

当然,还有以下情况:

  1. 有不想要取的值,用空位替代
const arr = [0, 1, 2]
const [ , , n1] = arr

// 打印n1为: 2
  1. 数组长度大于取值的长度
const arr = [0, 1, 2]
const [ ,n1] = arr

//  打印n1为: 1
  1. 数组长度小于取值长度
const arr = [0]
const [n1, n2] = arr

// 打印n1,n2为: 0 undefined

对象解构

与数组解构时用变量对位相应元素不同,对象的解构是通过属性名来匹配的,若没有匹配到相应的属性名,则会变成undefined。同时,还可以在解构的时候给变量起别名

const obj = {
    name: '好好学习',
    method: '学习'
}
const { name, age, method: m } = obj

// name打印为:'好好学习',age打印为:undefined,m打印为:'学习'

模板字符串

es6之前,字符串都是用双引号和单引号来表示,如'string'或者"string"。如果想要在字符串换行就得使用\n来表示。深者,如果要在字符串中间加入一个变量就得要进行字符串的拼接:

const name = '好好学习'
const str = '我要' + name + '了'

// 打印str为: '我要好好学习了'

如果碰到多个字符串和多个变量拼接,就会更杂乱,而es6的模板字符串极大的减少了这样的操作。

模板字符串用反引号将内容包裹起来,正常情况下可以当普通的来使用,但是在字符串拼接等情况下,可以使用插值表达式来嵌入变量甚至是简单的标准js语句:

const name = '好好学习'
const str = `我要${name}了`

// 打印结果为:'我要好好学习了'

模板字符串标签

在模板字符串中,还可以给它加上标签函数:

function tagFn(string) {
    return string
}
const name = 'cinit'
const toDo = '好好学习'

const res = tagFn`我${name}${toDo}了`
// res打印结果为:['我', '要', '了']

可以看出标签函数将模板字符串以变量的位置对其进行了分割,同时,还可以在tagFn的形参处加上对应的变量,取到对应的值:

function tagFn(string, name, toDo) {
    return string[0] + name + string[1] + toDo + string[2]
}
const name = 'cinit'
const toDo = '好好学习'

const res = tagFn`我${name}${toDo}了`
// res打印结果为:'我cinit要好好学习了'

由此,可以在标签函数内对模板字符串中的变量进行加工:

function tagFn(string, name, toDo) {
    name = name === 'cinit''我' : '别人'
    return string[0] + name + string[1] + toDo + string[2]
}
const name = 'cinit'
const toDo = '好好学习'

const res = tagFn`我${name}${toDo}了`
// res打印结果为:'我我要好好学习了'

字符串扩展方法

  • includes

    includes方法可以判断字符串中是否包含目标字符

    const str = '好好学习'
    console.log(str.includes('好好'))
    // 打印结果为:true
    
  • startsWith

    startsWith可以判断字符串是否以目标字符开头

    const str = '好好学习'
    console.log(str.startsWith('好好'))
    // 打印结果为: true
    
  • endsWith

    endsWith可以判断字符串是否以目标字符结尾

    const str = '好好学习'
    console.log(str.endsWith('学习'))
    // 打印结果为:true
    

函数默认值

对于有些函数,可以给其设置默认值来简化函数的调用。在es6之前,大多是用这种方式实现:

function fn(name) {
    name = name === undefined ? 'cinit' : name
    console.log(name)
}
fn()
fn('wo')

// 函数调用打印结果为:
// 'cinit'
// 'wo'

es6后,代码可以简化为:

function fn(name = 'cinit') {
    console.log(name)
}
fn()
fn('wo')

// 函数调用打印结果为:
// 'cinit'
// 'wo'

剩余操作符

es6之前,对于一个函数内多传入的参数可以在函数内部用arguments来获取到:

function fn() {
    console.log(arguments)
}
fn(1, 2, 2)
// 打印结果为: [1, 2, 2]

这个arguments为伪数组

在es6之后,我们可以通过剩余操作符...来获取到剩余参数:

function fn(...args) {
    console.log(args)
}
fn(1, 2, 2)
//  打印结果为:[1, 2, 2]

经过...的操作,args形成一个数组来接收传入fn的参数。

数组展开

...不仅可以作为剩余操作符使用,还可以用来展开数组

在es6之前,若想同时打印数组中的每一项元素,一般都是通过apply方法传入数组进行打印:

const arr = [1, 2, 3]
console.log.apply(console, arr)

// 打印结果为: 1 2 3

es6之后,可以简化为:

const arr = [1, 2, 3]
console.log(...arr)

// 打印结果为: 1 2 3

...会将数组的每一项依次传入函数之中

箭头函数

es6的箭头函数在一定程度上简化了函数的定义方式,它包含以下特点:

  • =>{}来描述一个函数

    const fn = (num1, num2) => {
        num1 += 1
        num2 += 2
        return num1 + num2
    } 
    
  • 必须要用定义变量的方式来声明函数

    // wrong
    function(num1, num2) => {
        num1 += 1
        num2 += 2
        return num1 + num2
    } 
    // right
    const fn = (num1, num2) => {
        num1 += 1
        num2 += 2
        return num1 + num2
    } 
    
  • 当函数的形参有且仅有一个的时候,可以省略小括号

    const fn = num => {
        num += 1
        return num
    }
    
  • 当函数体仅有一句(return)语句时,可以省略大括号

    const fn = num => num + 1
    
  • 箭头函数内的this指向其定义时的上下文的this指向

对象字面量增强

es6之前,用对象字面量方式声明变量时,可能会遇到以下情况

const name = '好好学习'
const fn = function() {}
const obj = {
    // 属性的键值名一样
    name: name,
    fn: fn,
}
// 给对象添加动态属性名
obj[Math.random()] = 1

在es6之后则可以改成以下方法:

const name = '好好学习'
const fn = function() {}
const obj = {
    // 属性的键值名一样,只用写一次
    name,
    fn,
    // 直接在声明变量时用中括号包裹变量
    [Math.random()]: 1
}

对象扩展方法

Object.assign

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。

语法:

Object.assign(target, ...sources)

参数:

target:目标对象。

sources:源对象。

返回值:

目标对象。

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// 打印结果为: { a: 1, b: 4, c: 5 }

console.log(returnedTarget);
// 打印结果为: { a: 1, b: 4, c: 5 }

如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性。

Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 getter 和 setter。因此,它分配属性,而不仅仅是复制或定义新的属性。如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用Object.getOwnPropertyDescriptor()Object.defineProperty()

String类型和 Symbol 类型的属性都会被拷贝。

在出现错误的情况下,例如,如果属性不可写,会引发TypeError,如果在引发错误之前添加了任何属性,则可以更改target对象。

注意,Object.assign 不会在那些source对象值为 nullundefined 的时候抛出错误。

针对深拷贝,需要使用其他办法,因为 Object.assign()拷贝的是(可枚举)属性值。

假如源值是一个对象的引用,它仅仅会复制其引用值。

const log = console.log;

function test() {
  'use strict';
  let obj1 = { a: 0 , b: { c: 0}};
  let obj2 = Object.assign({}, obj1);
  log(JSON.stringify(obj2));
  // { a: 0, b: { c: 0}}

  obj1.a = 1;
  log(JSON.stringify(obj1));
  // { a: 1, b: { c: 0}}
  log(JSON.stringify(obj2));
  // { a: 0, b: { c: 0}}

  obj2.a = 2;
  log(JSON.stringify(obj1));
  // { a: 1, b: { c: 0}}
  log(JSON.stringify(obj2));
  // { a: 2, b: { c: 0}}

  obj2.b.c = 3;
  log(JSON.stringify(obj1));
  // { a: 1, b: { c: 3}}
  log(JSON.stringify(obj2));
  // { a: 2, b: { c: 3}}

  // Deep Clone
  obj1 = { a: 0 , b: { c: 0}};
  let obj3 = JSON.parse(JSON.stringify(obj1));
  obj1.a = 4;
  obj1.b.c = 4;
  log(JSON.stringify(obj3));
  // { a: 0, b: { c: 0}}
}

test();

Object.is

Object.is() 方法判断两个值是否为同一个值。

语法:

Object.is(value1, value2);

参数:

value1:被比较的第一个值。

value2:被比较的第二个值。

返回值:

一个Boolean类型标示两个参数是否是同一个值。

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

Object.is('foo', 'bar');     // false
Object.is([], []);           // false

var foo = { a: 1 };
var bar = { a: 1 };
Object.is(foo, foo);         // true
Object.is(foo, bar);         // false

Object.is(null, null);       // true

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

Object.is() 方法判断两个值是否为同一个值。如果满足以下条件则两个值相等:

  • 都是 undefined
  • 都是 null
  • 都是 truefalse
  • 都是相同长度的字符串且相同字符按相同顺序排列
  • 都是相同对象(意味着每个对象有同一个引用)
  • 都是数字且
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 或都是非零而且非 NaN 且为同一个值

== 运算不同。 == 运算符在判断相等前对两边的变量(如果它们不是同一类型) 进行强制转换 (这种行为的结果会将 "" == false 判断为 true), 而 Object.is不会强制转换两边的值。

=== 运算也不相同。 === 运算符 (也包括 == 运算符) 将数字 -0+0 视为相等 ,而将Number.NaNNaN视为不相等.

Proxy

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

术语:

  • handler:包含捕捉器(trap)的占位符对象,可译为处理器对象。

  • traps:提供属性访问的方法。这类似于操作系统中捕获器的概念。

  • target:被 Proxy 代理虚拟化的对象。它常被作为代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。

语法:

const p = new Proxy(target, handler)

参数:

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

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

方法:

Proxy.revocable():创建一个可撤销的Proxy对象。

handle对象的方法:

handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。

所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。

  • handler.getPrototypeOf()

    Object.getPrototypeOf 方法的捕捉器。

  • handler.setPrototypeOf()

    Object.setPrototypeOf 方法的捕捉器。

  • handler.isExtensible()

    Object.isExtensible 方法的捕捉器。

  • handler.preventExtensions()

    Object.preventExtensions 方法的捕捉器。

  • handler.getOwnPropertyDescriptor()

    Object.getOwnPropertyDescriptor 方法的捕捉器。

  • handler.defineProperty()

    Object.defineProperty 方法的捕捉器。

  • handler.has()

    in 操作符的捕捉器。

  • handler.get()

    属性读取操作的捕捉器。

  • handler.set()

    属性设置操作的捕捉器。

  • handler.deleteProperty()

    delete 操作符的捕捉器。

  • handler.ownKeys()

    Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。

  • handler.apply()

    函数调用操作的捕捉器。

  • handler.construct()

    new 操作符的捕捉器。

示例:

// 基础示例
const p = new Proxy({}, {
    get: function(obj, prop) {
        return prop in obj ? obj[prop] : 37;
    });
p.a = 1;
p.b = undefined;

console.log(p.a, p.b);      // 1, undefined
console.log('c' in p, p.c); // false, 37
// 验证向一个对象的传值
let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }

    // The default behavior to store the value
    obj[prop] = value;

    // 表示成功
    return true;
  }
};

let person = new Proxy({}, validator);

person.age = 100;

console.log(person.age);
// 100

person.age = 'young';
// 抛出异常: Uncaught TypeError: The age is not an integer

person.age = 300;
// 抛出异常: Uncaught RangeError: The age seems invalid

Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

Reflect提供了一整套对对象的操作方法

与大多数全局对象不同Reflect并非一个构造函数,所以不能通过new运算符对其进行调用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的(就像Math对象)。

Reflect 对象提供了以下静态方法,这些方法与proxy handler methods的命名相同.

其中的一些方法与 Object相同, 尽管二者之间存在 某些细微上的差别 .

静态方法:

  • Reflect.apply(target, thisArgument, argumentsList

    对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似。

  • Reflect.construct(target, argumentsList[, newTarget])

    对构造函数进行 new 操作,相当于执行 new target(...args)

  • Reflect.defineProperty(target, propertyKey, attributes)

    Object.defineProperty() 类似。如果设置成功就会返回 true

  • Reflect.deleteProperty(target, propertyKey)

    作为函数的delete操作符,相当于执行 delete target[name]

  • Reflect.get(target, propertyKey[, receiver])

    获取对象身上某个属性的值,类似于 target[name]。

  • Reflect.getOwnPropertyDescriptor(target, propertyKey)

    类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符, 否则返回 undefined.

  • Reflect.getPrototypeOf(target)

    类似于 Object.getPrototypeOf()

  • Reflect.has(target, propertyKey)

    判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。

  • Reflect.isExtensible(target)

    类似于 Object.isExtensible().

  • Reflect.ownKeys(target)

    返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受enumerable影响).

  • Reflect.preventExtensions(target)

    类似于 Object.preventExtensions()。返回一个Boolean

  • Reflect.set(target, propertyKey, value[, receiver])

    将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true

  • Reflect.setPrototypeOf(target, prototype)

    设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回true。

示例:

// 检测一个对象是否存在特定属性
const duck = {
  name: 'Maurice',
  color: 'white',
  greeting: function() {
    console.log(`Quaaaack! My name is ${this.name}`);
  }
}

Reflect.has(duck, 'color');
// true
Reflect.has(duck, 'haircut');
// false
// 为一个对象添加新属性
Reflect.set(duck, 'eyes', 'black');
// returns "true" if successful
// "duck" now contains the property "eyes: 'black'"

Promise

详见Promise

Class

在面向对象编程中, 一个类(class)定义了一个对象的特征. 类是定义对象属性和方法的模板 。

在ES6之前,可以通过Function来实现类

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

Student.prototype.greet = function () {
    console.log('hi')
}

ES6引入class之后,可以改成下面这种形式:

class Student {
    constructor(name) {
        this.name = name
    }
    
    greet() {
        console.log('hi')
    }
}

类似于Java,这种方式也可以通过staticextends实现静态方法和继承:

class Student {
    constructor(name) {
        this.name = name
    }
    greet() {
        console.log('hi')
    }
}

class LiHua extends Student {
    constructor(name, major) {
        super(name)
        this.major = major
    }
    static study() {
        console.log('study English')
    }
    sayHi() {
        super.greet()
    }
}

Set

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

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

NaNundefined都可以被存储在Set 中, NaN之间被视为相同的值。

set数据结构本身是一个构造函数。

实例属性:

  • size: 返回 Set 对象中的值的个数

实例方法:

  • Set.prototype.add(value)

    Set对象尾部添加一个元素。返回该Set对象。

  • Set.prototype.clear()

    移除Set对象内的所有元素。

  • Set.prototype.delete(value)

    移除Set中与这个值相等的元素,返回Set.prototype.has(value)在这个操作前会返回的值(即如果该元素存在,返回true,否则返回false

  • Set.prototype.has(value):

    在此后会返回false

  • Set.prototype.entries()

    返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值的[value, value]数组。为了使这个方法和Map对象保持相似, 每个值的键和值相等。

  • Set.prototype.forEach(callbackFn[, thisArg])

    按照插入顺序,为Set对象中的每一个值调用一次callBackFn。如果提供了thisArg参数,回调中的this会是这个参数。

  • Set.prototype.has(value)

    返回一个布尔值,表示该值在Set中存在与否。

  • Set.prototype.keys()

    与**values()**方法相同,返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。

  • Set.prototype.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
// 用于数组去重
const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)])
// [2, 3, 4, 5, 6, 7, 32]

Map

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

**ObjectsMaps **

类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。因此(并且也没有其他内建的替代方式了)过去我们一直都把对象当成 Maps 使用。不过 MapsObjects 有一些重要的区别,在下列情况里使用 Map 会是更好的选择:

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

创建一个Map对象

const m = new Map()

Map构造函数属性:

  • Map.length:属性 length 的值为 0 。想要计算一个Map 中的条目数量, 使用 Map.prototype.size.

Map实例属性:

  • Map.prototype.constructor:返回一个函数,它创建了实例的原型。默认是Map函数。

  • Map.prototype.size:返回Map对象的键/值对的数量。

Map实例方法:

  • Map.prototype.clear()

    移除Map对象的所有键/值对 。

  • Map.prototype.delete(key)

    如果 Map 对象中存在该元素,则移除它并返回 true;否则如果该元素不存在则返回 *false*。随后调用 Map.prototype.has(key) 将返回 false

  • Map.prototype.entries()

    返回一个新的 Iterator 对象,它按插入顺序包含了Map对象中每个元素的 [key, value] 数组。

  • Map.prototype.forEach(callbackFn[, thisArg])

    按插入顺序,为 Map对象里的每一键值对调用一次callbackFn函数。如果为forEach提供了thisArg,它将在每次回调中作为this值。

  • Map.prototype.get(key)

    返回键对应的值,如果不存在,则返回undefined。

  • Map.prototype.has(key)

    返回一个布尔值,表示Map实例是否包含键对应的值。

  • Map.prototype.keys()

    返回一个新的 Iterator对象, 它按插入顺序包含了Map对象中每个元素的

  • Map.prototype.set(key, value)

    设置Map对象中键的值。返回该Map对象。

  • Map.prototype.values()

    返回一个新的Iterator对象,它按插入顺序包含了Map对象中每个元素的

// 示例
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...
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"

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

Symbol

symbol 是一种基本数据类型。Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类 。

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

// 示例
const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');

console.log(typeof symbol1);
// expected output: "symbol"

console.log(symbol2 === 42);
// expected output: false

console.log(symbol3.toString());
// expected output: "Symbol(foo)"

console.log(Symbol('foo') === Symbol('foo'));
// expected output: false


typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
typeof Symbol.iterator === 'symbol'

Symbol.iterator

一个返回一个对象默认迭代器的方法。

Symbol.hasInstance

一个确定一个构造器对象识别的对象是否为它的实例的方法。

Symbol.isConcatSpreadable

一个布尔值,表明一个对象是否应该flattened为它的数组元素。

Symbol.unscopables

拥有和继承属性名的一个对象的值被排除在与环境绑定的相关对象外。

Symbol.species

一个用于创建派生对象的构造器函数。

Symbol.toPrimitive

一个将对象转化为基本数据类型的方法。

Symbol.toStringTag

用于对象的默认描述的字符串值。

方法:

Symbol.for(key)

使用给定的key搜索现有的symbol,如果找到则返回该symbol。否则将使用给定的key在全局symbol注册表中创建一个新的symbol。

Symbol.prototype.toString()

返回包含Symbol描述符的字符串。 覆盖Object.prototype.toString() 方法。

Symbol.prototype.valueOf()

返回 Symbol 对象的初始值.。覆盖 Object.prototype.valueOf() 方法。

Tips:

  • Symbols 在 for...in 迭代中不可枚举。另外,Object.getOwnPropertyNames() 不会返回 symbol 对象的属性,但是能使用 Object.getOwnPropertySymbols() 得到它们。

  • 当使用 JSON.stringify() 时,以 symbol 值作为键的属性会被完全忽略。

for...of

for...of语句在可迭代对象(包括 ArrayMapSetStringTypedArrayargumentsHTMLCollection对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句 。

// 示例 迭代Array
const array1 = ['a', 'b', 'c'];

for (const element of array1) {
  console.log(element);
}

// expected output: "a"
// expected output: "b"
// expected output: "c"

相比起数组的forEachfor...of可以在迭代数组的时候由break, throw continue return终止。在这些情况下,迭代器关闭。

迭代器接口

迭代协议

作为 ECMAScript 2015 的一组补充规范,迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。

迭代协议具体分为两个协议:可迭代协议迭代器协议

可迭代协议

可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of 结构中,哪些值可以被遍历到。一些内置类型同时是内置可迭代对象,并且有默认的迭代行为,比如 Array 或者 Map,而其他内置类型则不是(比如 Object))。

要成为可迭代对象, 一个对象必须实现 Symbol.iterator 方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为Symbol.iterator 的属性。它是一个无参数的函数,其返回值为一个符合迭代器协议的对象。

当一个对象需要被迭代的时候(比如被置入一个 for...of 循环时),首先,会不带参数调用它的 Symbol.iterator 方法,然后使用此方法返回的迭代器获得要迭代的值。

值得注意的是调用此零个参数函数时,它将作为对可迭代对象的方法进行调用。 因此,在函数内部,this关键字可用于访问可迭代对象的属性,以决定在迭代过程中提供什么。

此函数可以是普通函数,也可以是生成器函数,以便在调用时返回迭代器对象。 在此生成器函数的内部,可以使用yield提供每个条目。

迭代器协议

迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。

只有实现了一个拥有以下语义的 next() 方法,一个对象才能成为迭代器:

属性
next一个无参数函数,返回一个应当拥有以下两个属性的对象:done(boolean)如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。value迭代器返回的任何 JavaScript 值。done 为 true 时可省略。next() 方法必须返回一个对象,该对象应当有两个属性: donevalue,如果返回了一个非对象值(比如 falseundefined),则会抛出一个 TypeError 异常("iterator.next() returned a non-object value")。
// 迭代器示例
function makeIterator(array) {
    let nextIndex = 0;
    return {
       next: function () {
           return nextIndex < array.length ? {
               value: array[nextIndex++],
               done: false
           } : {
               done: true
           };
       }
    };
}

let it = makeIterator(['哟', '呀']);

console.log(it.next().value); // '哟'
console.log(it.next().value); // '呀'
console.log(it.next().done);  // true

以下是class中的迭代器:

class SimpleClass {
  constructor(data) {
    this.data = data
  }

  [Symbol.iterator]() {
    // Use a new index for each iterator. This makes multiple
    // iterations over the iterable safe for non-trivial cases,
    // such as use of break or nested looping over the same iterable.
    let index = 0;

    return {
      next: () => {
        if (index < this.data.length) {
          return {value: this.data[index++], done: false}
        } else {
          return {done: true}
        }
      }
    }
  }
}

const simple = new SimpleClass([1,2,3,4,5])

for (const val of simple) {
  console.log(val)   //'1' '2' '3' '4' '5'
}

Generator

生成器对象是由一个 generator function 返回的,并且它符合可迭代协议和迭代器协议。

// 示例
function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

let g = gen();
// "Generator { }"

Tips

生成器对象既是迭代器也是可迭代对象,因为其符合可迭代协议和迭代器协议:

let aGeneratorObject = function* (){
    yield 1;
    yield 2;
    yield 3;
}();

typeof aGeneratorObject.next;
// 返回"function", 因为有一个next方法,所以这是一个迭代器

typeof aGeneratorObject[Symbol.iterator];
// 返回"function", 因为有一个Symbol.iterator方法,所以这是一个可迭代对象

aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// 返回true, 因为Symbol.iterator方法返回自身(即迭代器),所以这是一个格式良好的可迭代对象

[...aGeneratorObject];
// 返回[1, 2, 3]

console.log(Symbol.iterator in aGeneratorObject)
// 返回true, 因为Symbol.iterator方法是aGeneratorObject的一个属性

由上,我们也可以使用for...of来对生成器对象进行迭代:

function* fibonacci() { // 一个生成器函数
    let [prev, curr] = [0, 1];
    for (;;) { // while (true) {
        [prev, curr] = [curr, prev + curr];
        yield curr;
    }
}

for (let n of fibonacci()) {
     console.log(n);
    // 当n大于1000时跳出循环
    if (n >= 1000)
        break;
}

ES2016

相比于ES2015,ES2016只做了小版本的更新,比如数组的includes方法和指数运算符**

Array.prototype.includes

includes 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

// 示例
const array1 = [1, 2, 3];

console.log(array1.includes(2));
// expected output: true

const pets = ['cat', 'dog', 'bat'];

console.log(pets.includes('cat'));
// expected output: true

console.log(pets.includes('at'));
// expected output: false

相比起Array.prototype.indexOfincludes可以查询到数组中的NaN,也就是说includes可以判断数组中是否包含NaN

[1, 2, NaN].includes(NaN); // true

includes还可以传入第二个参数:fromIndex,它会将formIndex作为开始查找的索引往后查询目标值:

// 如果 fromIndex 大于等于数组的长度,则会返回 false,且该数组不会被搜索。
[1, 2, 3].includes(3, 3);  // false

// 如果 fromIndex 为负值,计算出的索引将作为开始搜索searchElement的位置。如果计算出的索引小于 0,则整个数组都会被搜索。
[1, 2, 3].includes(3, -1); // true

指数运算符

如同+-*/等常用运算符一样,求幂运算也有了专门的运算符:**。相比起Math.pow(),指数运算符更为简便:

2 ** 10  // 1024

ES2017

Object.values

Object.values方法返回一个给定对象自身的所有可枚举属性值的数组。

// 示例
const obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]

Object.entries

Object.entries方法返回一个给定对象自身可枚举属性的键值对数组。

// 示例
const obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]

由此,可以通过这样的方式使用for...of来迭代一个对象:

const object1 = {
  a: 'somestring',
  b: 42
};

for (const [key, value] of Object.entries(object1)) {
  console.log(`${key}: ${value}`);
}

// "a: somestring"
// "b: 42"

还可以将Object转换成一个Map对象:

const obj = { foo: "bar", baz: 42 };
const map = new Map(Object.entries(obj));
console.log(map); // Map { foo: "bar", baz: 42 }

String.prototype.padStart/Sting.prototype.padEnd

padStart/padEnd 方法用另一个字符串填充当前字符串,以便产生的字符串达到给定的长度。 padStart是从左侧开始填充,padEnd从右侧开始填充。

const str1 = '5';

console.log(str1.padStart(2, '0')); // "05"
console.log(str1.padEnd(2, '0')); // "50"