ES2015新特性

ECMAScript概述

JavaScript = ECMAScript + 运行环境的API
其中,运行环境包括浏览器和Node环境

ES2015(ES6来泛指ES2015之后的新标准)新特性

解决原有用语法上的不足

let、const与块级作用域

  • let、const声明的变量只在{}包裹的块级作用域内有效
// 将会打印出三次foo;
for(let i = 0; i < 3; i ++) {
  let i = 'foo';
  console.log(i);
}
// 这段代码相当于如下
// 第一步,声明循环变量
let i = 0;
// 第二步,判断循环条件,执行循环体,由于循环体是一个块级作用域,因此与循环变量尽管同名,但在循环体内只有一个i变量;
if(i < 3) {
  let i = 'foo';
  console.log(i);
}
// 第三步,循环变量的值增加
i ++;
  • 不会有作用域提升效果,也就是只有执行到声明语句时,才有这个变量;
  • const用来声明常量值,意味着该变量只读;对于引用类型而言,该常量值为引用地址,只要不更改引用地址就行;

在程序中,不用var,主要用const,再配合let

for of 遍历

作为遍历所有数据结构的统一方式,在Iterator对象调用next方法后返回的结果对象中如果done属性为false,则执行循环体,否则退出循环。

  • 可以在遍历过程中使用break关键字终止循环
  • 可以遍历数组、类数组集合、Set、Map等等,但不能遍历对象,原因在于对象没有实现默认的Iterator接口。
  • 对Map数据结构的遍历时,得到的元素是键值对构成的二元数组,因此可以使用数组解构赋值的方式来使用键值对。

对原有语法的增强

解构赋值

更加简洁地从一个复杂的数据结构中提取值,包括数组、对象这些数据结构。

数组解构

将一个已知数组中的值赋值给变量时,为了提升效率,不再利用索引一个一个赋值,而是一次性赋值。
解构时要根据位置(即数字索引)来赋值。

  • 支持使用…扩展符解构
  • 支持在解构时,为变量提供默认值
const arr = [1, 2, 3];
// 原始做法
const foo = arr[0];
const bar = arr[1];
const baz = arr[2];
// 解构做法
const [foo, bar, baz] = arr; // foo = 1, bar = 2, baz = 3;
// 或者只赋值某一部分
const [, , baz] = arr; // baz = 3; 
// 使用...扩展符解构,...rest这种方式只能用于数组末尾
const [foo, ...rest] = arr; // foo = 1, rest = [2, 3];
// 解构时使用默认值,如果解构赋值时没有得到值,则使用默认值
const [foo, bar, baz, more = 'default'] = arr; // more = 'default'; 
对象解构

使用属性名(索引)来解构赋值。

const obj = { name: 'mm', age: 18 };
const { name: foo = 'ff', age: bar } = obj; // foo = 'mm', bar = 18, 其中foo = 'ff'为默认赋值

模板字符串

使用``来标识字符串

  • 支持在字符串中加入换行符(而不是用\n)
  • 支持插值表达式,不需要再用丑陋的拼接方式
  • 支持标签函数,标签函数用于对模板字符串进行提取,并进行额外加工;
const str = console.log`hello world`; // [ 'hello world' ];
const name = 'tom';
const gender = true;
// 自定义标签函数
// 标签函数接收的第一个参数为,模板字符串被插值表达式分隔而成的字符串数组
// 标签函数接收的其他参数为,模板字符串中插值表达式的值
function myTag(strings, name, gender) {
  console.log(strings, name, gender); // strings = ['hello, ', ' is a ', '.'], name = 'tom', gender = true;
  const sex = gender ? 'man': 'woman';
  return strings[0] + name + strings[1] + sex + strings[2]; // 'hello, tom is a men.';
}
const result = myTag`hello, ${name} is a ${gender}.`;

字符串方法扩展

  • includes(str):判断字符串是否包含str
  • startsWith(str):判断字符串是否以str开头
  • endsWith(str):判断字符串是否以str结尾

函数扩展

参数默认值
  • 带默认值的参数要放在函数形参的末尾
剩余参数

使用扩展符…rest作为形参来接收函数的实参,其中rest为数组,剩余参数…rest要放在形参的末尾

使用扩展符来传递实参
const arr = ['foo', 'baz', 'bar'];
// 通过...arr传递实参
console.log(...arr);
箭头函数

即lambda表达式形式

  • 简化了回调函数的编写
  • 突出了函数的本质(类型到类型的映射关系,适用于函数式编程范式)
  • 不会改变this指向,箭头函数内部没有this机制,因此箭头函数体的this绑定的是函数上下文中的this,即箭头函数定义的词法作用域中的this;在使用保存this指向的场景下,都可以使用箭头函数来简化
const person1 = {
  name: 'tom',
  sayHi: () => {
    setTimeout(function() {
      console.log(this.name); 
    }, 1000)
  }
}
person1.sayHi(); // undefined;

const person2 = {
  name: 'tom',
  // 注意在方法的定义上,不要使用箭头函数,否则内部的this找不到
  sayHi: function() {
    // 使用_this来保存this指向,通过闭包机制访问到this.name;
    let _this = this;
    setTimeout(function() {
      console.log(_this.name); 
    }, 1000)
  }
}
person2.sayHi(); // tom;

const person3 = {
  name: 'tom',
  sayHi: function() {
    setTimeout(() => {
      console.log(this.name); 
    }, 1000)
  }
}
person3.sayHi(); // tom;

对象增强

动态属性名即计算属性名

在对象用字面量方式定义时,可以利用[表达式]的方式去定义动态属性名,称之为计算属性名

对象扩展方法
  • Object.assign(target, source1, source2, …rest):将source1、source2等对象的属性复制到target对象,并返回target对象。同名属性将会被覆盖,覆盖以从右向左的顺序。注意:浅复制
    • 一般常用于更新对象,但不修改源对象的非侵入式修改,适合函数式编程范式。
    • 这也是JavaScript扩展对象的一种方式 Mixin
    • assign方法无法复制源对象的访问器属性,该方法会将访问器属性执行之后,将执行结果作为数据属性复制到目标对象上。
  • Object.is(value1, value2): 比较两个值是否全等。主要是为了补充+0与-0、NaN与NaN的比较。因为在符号===的情况下,与Object.is方法刚好相反的。
    • Object.is(+0, -0); // false
    • Object.is(NaN, NaN); // true
  • Object.keys(obj):返回目标对象obj所有可枚举的属性名组成数组
  • Object.getOwnPropertyNames(obj):返回目标对象obj不考虑可枚举性的属性名组成的数组
  • Object.getOwnPropertySymbols(obj):返回目标对象obj所有Symbol类型的属性名组成的数组

全新的对象及其API

Proxy代理对象

用处

用于拦截程序中对对象的访问、设置等各种行为,使用代理对象对这些行为进行响应。每一种特定的行为都有相应的陷阱来拦截该行为,并指定对应的处理程序来响应该行为。响应程序可以自定义,也可以使用Reflect API来使用Proxy默认的响应行为。
通过Proxy代理之后,对该对象的操作转而需要通过Proxy来进行。

构造
const person = {
  name: 'tom',
  age: 18
}
// new Proxy(target, options)传入代理的目标对象和代理配置对象,配置对象内部定义了各种拦截器
const personProxy = new Proxy(person, {
  // 拦截访问对象属性值的行为,target为要访问的目标对象,property为要访问的属性名
  get (target, property) {
    // 不管访问目标对象的什么属性,一律返回100; 这里就是自定义的响应;
    return 100;
  }
  // 拦截设置对象属性值的行为,增加校验行为
  set (target, property, value) {
    // 可以针对age属性做一下校验
    if(property === 'age') {
      if(!Number.isInteger(value)) {
        throw new TypeError(`${value} is not an Integer!`);
      }
      target[property] = value;
    }
    target[property] = value;
  }
})
// 访问person.name的行为转而变为访问personProxy.name的行为;
const result = peronProxy.name; // 100;
与Object.defineProperty的对比
  • Object.defineProperty只能拦截对属性的读写操作,Proxy可以拦截更多的操作,这取决于Proxy内部的陷阱函数
  • 监视数组的操作
    • Object.defineProperty,如Vue中是通过重写数组的操作方法来拦截
    • Proxy 监视数组的操作更加灵活
  • 非侵入式拦截对象操作
const list = [];
const listProxy = new Proxy(list, {
  set(target, property, value) {
    console.log('set', property, value);
    target[property] = value;
    return true; // 表示写入成功
  }
})
// push操作可以被Proxy的set陷阱拦截,并且property自动更新;但如果是利用Object.defineProperty时,就需要重写数组的push方法了,原来的push方法无法被拦截到;
list.push(66); // set, 0, 66;
list.push(88); // set, 1, 88;
Reflect

静态类,封装一系列的对对象的底层操作API,是Proxy对象拦截行为的默认响应,每一个API都与Proxy中的陷阱函数相对应。我们在定义Proxy中的陷阱函数时,可以先自定义好需要的逻辑,然后使用Reflect的API将默认响应返回。
Reflect的意义在于,统一了针对对象的各种操作,不管是利用Object的静态方法还是实例方法,还是一些操作符如in, has等,都可以通过Reflect API找到对应的方法;Reflect API可以不和Proxy一起使用,也可以单独使用,目的是取代之前定义的对象操作行为,统一经过Reflect API来操作。

class类

静态成员

使用static关键字声明静态成员,内部的this指向类本身(构造函数)

继承

使用extends关键字实现类型继承

  • 在子类构造函数中需要先调用super()函数初始化this值,才能在之后的代码中使用this。super引用父类构造函数,是构造函数借用。

Iterator对象

用于提供统一的迭代数据结构的接口,是for of 实现遍历的前提。也就是说,只有数据结构实现了Iterator接口,才能够被for of方式来遍历。
数据结构如果实现了[Symbol.iterator]属性(属性值为函数),该函数返回一个Iterator对象。Iterator对象的next()方法用来获取数据结构中的下一个元素。这也是for of 方式实现遍历的原理。

为对象实现Iterator接口

只要实现了Iterator接口,就可以使用for of 遍历;

const obj {
  store: ['foo', 'bar', 'baz'],
  // 实现iterable接口,用于返回Iterator对象
  [Symbol.iterator]: function() {
    const self = this; // 保存对象的引用;
    let index = 0;
    // 实现Iterator对象,用于迭代对象内部的值
    return {
      next: function() {
        const result = {
          value: self.store[index],
          done: index >= self.store.length
        }
        index ++;
        return result;
      }
    }
  }
}
迭代器模式管理数据结构的遍历

Iterator对象是为了实现迭代器模式而创建。迭代器模式是为了将遍历代码与数据结构解耦,统一使用迭代器来进行遍历。这样如果数据结构频繁变化时,可以只修改迭代器即可,遍历代码改动很小。也就是说,迭代器模式将数据结构与遍历代码搭建了一个统一的桥梁

Generator生成器

意义

提供更好的异步编程写法

yield关键字
使用generator实现Iterator
const obj {
  store: ['foo', 'bar', 'baz'],
  // 实现iterable接口,用于返回Iterator对象,这里使用了生成器函数
  [Symbol.iterator]: function* () {
      for(const item of this.store) {
        yield item;
      }
  }
}
redux-saga使用generator写法

全新的数据类型

Set数据结构

数据集合,其中的数据不重复。

构造

const set = new Set();
操作方法(均为实例方法)
  • add(item):增加一个元素item到Set
  • forEach((item, index) => {}):遍历Set中的元素
  • size属性:返回Set中元素的数量
  • has(item):判断Set中item是否存在
  • delete(item):删除Set中item元素
  • clear():清理Set所有元素
WeakSet

Map数据结构(对象的增强或补充)

key-value数据集合,Map与对象的区别是,key可以是任意数据类型,而对象的key只能是字符串(其他数据类型会被隐式转换为字符串类型);

构造
const map = new Map();
操作方法(均为实例方法)
  • get(key)
  • set(key, value)
  • forEach((value, key) => {})
  • 其他类似于对象的方法,如keys()、values()、entries()等
WeakMap

弱引用集合。

  • WeakMap的键必须是一个对象,使用非对象的键会报错
  • 集合中保存的是这些对象的弱引用,如果在弱引用之外的不存在其他的强引用,则垃圾回收机制会将弱引用对象收回,同时从WeakMap中删除对应的键值对。
  • 最大的用于是保存DOM元素。当DOM元素从页面移除时(外部强引用不存在了),WeakMap内部对应的DOM元素也会被垃圾回收机制自动清除。

Symbol类型

意义
  • 创建独一无二的值const s = Symbol(); // 不能使用new创建,而是直接用Symbol(),每次使用Symbol()创建出来的值都不相同。
    • 创建私有属性
    • 避免第三方库的命名冲突
创建
  • Symbol(str)传入可选的str字符串,用于描述这个Symbol类型的值。描述文本有利于开发调试,让开发者知道该值的相关信息。
  • Symbol的描述文本存储在内部的[[ Descriptor ]]属性,只有调用Symbol的toString()方法才可以读取到这个内部属性。使用console.log()时隐式调用了toString()方法。
const s1 = Symbol('name');
console.log(s1); // "Symbol('name')"
类型判断

可以使用typeof操作符来判断一个值是否是Symbol类型

const s1 = Symbol('symbol test');
console.log(typeof s1); // 'symbol'
对象私有属性的创建
  • 对象的属性名现在可以接受string类型和Symbol类型了
  • 通过Symbol类型可以创建对象的私有属性了,因为外部无法找到相同的Symbol类型值。
    const name = Symbol(); // name变量中保存了新创建的Symbol类型值,但name变量不暴露给外部即可实现私有属性
    const obj = {
      [name]: 'foo',
      say() {
        console.log(this.[name]);
      }
    }
    
Symbol共享体系

在代码中查找所创建的Symbol值。全局Symbol注册表是一个类似全局作用域的共享体系,同样需要注意避免冲突。

Symbol.for(str):
  • 用于查找描述为str的Symbol值并返回该Symbol类型的值。如果没找到,则使用str参数创建一个Symbol值并返回。
  • 要求传入str是字符串类型,如果不是则隐式转换为字符串类型。
  • 为了查找Symbol值,其内部维护了一个描述文本与值的全局Symbol注册表,我们可以通过描述文本找到对应的Symbol值。
Symbol.keyFor(symbol):
  • 用于查找symbol对应的描述文本。与Symbol.for()方法相反。
  • symbol参数要求是一个Symbol值。
  • 如果查找不到,则返回undefined。
Symbol与类型转换
  • Symbol类型无法转换为字符串类型与数字类型,调用toString()只是返回该值的字符串描述而已。如果Symbol类型与字符串作拼接或参与数学运算都会报错。
  • Symbol可以参与逻辑运算,因为Symbol等价布尔值为true
Symbol公共属性来暴露一些JavaScript内部逻辑

Symbol内置了一些常量属性来暴露JavaScript的内部逻辑,同时也指定了这些操作的接口。不同的数据类型对这些方法实现情况不同,有的时候需要自定义。如对象类型并没有[Symbol.iterator]接口。开发者可以根据情况对这些接口进行改写,这一方式也被称为元编程。

Symbol.iterator // 返回迭代器接口的方法
Symbol.hasInstance // 使用instanceof操作符时调用的方法
Symbol.match // 在调用String.prototype.match()方法时调用的方法,用于比较字符串是否匹配子串
Symbol.replace // 在调用String.prototype.replace()方法时调用的方法,用于替换字符串的子串
Symbol.search // 在调用String.prototype.search()方法时调用的方法,用于查询字符串的子串
Symbol.split // 在调用String.prototype.split()方法时调用的方法,用于分隔字符串
Symbol.isConcatSpreadable // 布尔值,用于表示当传递一个集体到Array.prototype.concat()方法的参数时,是否应该将集合内的元素规整到同一层级
Symbol.toPrimitive // 一个返回对象对应的原始类型的值的方法,即有时候特定的操作会触发将对象转换为原始类型的值。
Symbol.toStringTag // 一个在调用Object.prototype.toString()方法时使用的字符串,用于创建对象描述,对象描述指的是[object object]、[object array]之类的。如果定义[Symbol.toStringTag]: value, 则返回的对象描述为[object value]
Symbol.unscopables // 一个定义了一些不可被with语句引用的对象属性名称的对象集合
获取对象内部的Symbol类型属性名

使用Object.getOwnPropertySymbols(obj); // 传入目标对象,返回该对象的Symbol类型的属性名来获取目标对象的Symbol类型的属性名;
使用传统的for in循环或者Object.keys(obj)只能获取到字符串类型的属性名;
同时,使用JSON.stringify(obj)也不会将Symbol类型属性序列化;

ES Modules

ES2016

Array.prototype.includes

鉴于Array.prototype.indexOf(NaN)无法判断数组中的NaN元素,因此扩展出了includes方法

指数运算符

原来:Math.pow(2, 10); // 2^10
现在:2 ** 10;

ES2017

Async/Await写法

Object的扩展方法

Object.values(obj)

返回目标对象obj属性值组成数组

Object.entries(obj)

返回目标对象obj键值对组成的数组,其中键值对也是一个二元数组。

Object.getOwnPropertyDescriptors(obj)

返回目标对象obj上所有属性以及对应的属性描述符对象,其结构如下:

{
  属性名1: 属性描述符对象,
  属性名2: 属性描述符对象,
  ...
}

字符串填充

为给定的字符串在开始处(padStart)或结束处(padEnd)填充指定的字符串,直到填充的字符数达到了指定的字符数。

String.prototype.padStart(num, str)

在给定字符串的开始处填充str(可能会反复填充str)直到填充后的字符串长度达到num,返回填充后的字符串;

String.prototype.padEnd(num, str)

在给定字符串的结尾处填充str(可能会反复填充str)直到填充后的字符串长度达到num,返回填充后的字符串;

函数最后一个形式参数后添加逗号

function func(arg1, arg2, arg3,) {
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章