以下文章来源于鱼头的Web海洋 ,作者陈大鱼头

 
鱼头的Web海洋

一个名为Web的海洋世界

(给前端大全加星标,提升前端技能)

介绍

ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会)在标准ECMA-262中定义的脚本语言规范。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,但实际上后两者是ECMA-262标准的实现和扩展。

历史版本

至发稿日为止有九个ECMA-262版本发表。其历史版本如下:

1.1997年6月:第一版2.1998年6月:修改格式,使其与ISO/IEC16262国际标准一样3.1999年12月:强大的正则表达式,更好的词法作用域链处理,新的控制指令,异常处理,错误定义更加明确,数据输出的格式化及其它改变4.2009年12月:添加严格模式("use strict")。修改了前面版本模糊不清的概念。增加了getters,setters,JSON以及在对象属性上更完整的反射。5.2011年6月:ECMAScript标5.1版形式上完全一致于国际标准ISO/IEC 16262:2011。6.2015年6月:ECMAScript 2015(ES2015),第 6 版,最早被称作是 ECMAScript 6(ES6),添加了类和模块的语法,其他特性包括迭代器,Python风格的生成器和生成器表达式,箭头函数,二进制数据,静态类型数组,集合(maps,sets 和 weak maps),promise,reflection 和 proxies。作为最早的 ECMAScript Harmony 版本,也被叫做ES6 Harmony。7.2016年6月:ECMAScript 2016(ES2016),第 7 版,多个新的概念和语言特性。8.2017年6月:ECMAScript 2017(ES2017),第 8 版,多个新的概念和语言特性。9.2018年6月:ECMAScript 2018 (ES2018),第 9 版,包含了异步循环,生成器,新的正则表达式特性和 rest/spread 语法。10.2019年6月:ECMAScript 2019 (ES2019),第 10 版。

发展标准

TC39(Technical Committee 39)是一个推动JavaScript发展的委员会,它的成语来自各个主流浏览器的代表成语。会议实行多数决,每一项决策只有大部分人同意且没有强烈反对才能去实现。

TC39成员制定着ECMAScript的未来。

每一项新特性最终要进入到ECMAScript规范里,需要经历5个阶段,这5个阶段如下:

Stage 0: Strawperson

只要是TC39成员或者贡献者,都可以提交想法

Stage 1: Proposal

这个阶段确定一个正式的提案

Stage 2: draft

规范的第一个版本,进入此阶段的提案大概率会成为标准

Stage 3: Candidate

进一步完善提案细则

Stage 4: Finished

表示已准备好将其添加到正式的ECMAScript标准中

由于ES6以前的属性诞生年底久远,我们使用也比较普遍,遂不进行说明,ES6之后的语言风格跟ES5以前的差异比较大,所以单独拎出来做个记录。

ES6(ES2015)

Let 和 Const

在ES6以前,JS只有var一种声明方式,但是在ES6之后,就多了letconst这两种方式。用var定义的变量没有块级作用域的概念,而letconst则会有,因为这三个关键字创建是不一样的。

区别如下:

{        let b =     }a // 10b // Uncaught ReferenceError: b is not definedc // c is not definedlet d = 40const e = 50d = 60d // 60e = 70 // VM231:1 Uncaught TypeError: Assignment to constant variable.
变量提升××
全局变量××
重复声明××
重新赋值×
暂时死区×
块作用域×
只声明不初始化×

类(Class)

在ES6之前,如果我们要生成一个实例对象,传统的方法就是写一个构造函数,例子如下:

function Person(name, age) {    this.name = name    this.age = age}Person.prototype.information = function () {    return 'My name is ' + this.name + ', I am ' + this.age}

但是在ES6之后,我们只需要写成以下形式:

class Person {    constructor(name, age) {        this.name = name        this.age = age    }    information() {        return 'My name is ' + this.name + ', I am ' + this.age    }}

箭头函数(Arrow function)

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的thisargumentssuper或 new.target。这些函数表达式更适用于那些本来需要匿名函数的地方,并且它们不能用作构造函数。

在ES6以前,我们写函数一般是:

, , , , , , ]var newList = list.map(function (item) {    return item * item})

但是在ES6里,我们可以:

, , , , , , ]const newList = list.map(item => item * item)

看,是不是简洁了不少

函数参数默认值(Function parameter defaults)

在ES6之前,如果我们写函数需要定义初始值的时候,需要这么写:

function config (data) {    var data = data || 'data is empty'}

这样看起来也没有问题,但是如果参数的布尔值为false时就会出问题,例如我们这样调用config:

config()config('')

那么结果就永远是后面的值

如果我们用函数参数默认值就没有这个问题,写法如下:

const config = (data = 'data is empty') => {}

模板字符串(Template string)

在ES6之前,如果我们要拼接字符串,则需要像这样:

var name = 'kris'var age = 24var info = 'My name is ' + this.name + ', I am ' + this.age

但是在ES6之后,我们只需要写成以下形式:

const name = 'kris'const age = 24const info = `My name is ${name}, I am ${age}`

解构赋值(Destructuring assignment)

我们通过解构赋值, 可以将属性/值从对象/数组中取出,赋值给其他变量。

比如我们需要交换两个变量的值,在ES6之前我们可能需要:

var a = 10var b = 20var temp = aa = bb = temp

但是在ES6里,我们有:

let a = 10let b = [a, b] = [b, a]

是不是方便很多

模块化(Module)

在ES6之前,JS并没有模块化的概念,有的也只是社区定制的类似CommonJS和AMD之类的规则。例如基于CommonJS的NodeJS:

// circle.js// 输出const { PI } = Mathexports.area = (r) => PI * r ** 2exports.circumference = (r) => 2 * PI * r
// index.js// 输入const circle = require('./circle.js')console.log(`半径为 4 的圆的面积是 ${circle.area(4)}`)

在ES6之后我们则可以写成以下形式:

// circle.js// 输出const { PI } = Mathexport const area = (r) => PI * r ** 2export const circumference = (r) => 2 * PI * r
// index.js// 输入import {    area} = './circle.js'console.log(`半径为 4 的圆的面积是: ${area(4)}`)

扩展操作符(Spread operator)

扩展操作符可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。

比如在ES5的时候,我们要对一个数组的元素进行相加,在不使用reduce或者reduceRight的场合,我们需要:

function sum(x, y, z) {    , , ]var total = sum.apply(null, list)

但是如果我们使用扩展操作符,只需要如下:

, , ]const total = sum(...list)

非常的简单,但是要注意的是扩展操作符只能用于可迭代对象

如果是下面的情况,是会报错的:

var obj = {'key1': 'value1'}var array = [...obj] // TypeError: obj is not iterable

对象属性简写(Object attribute shorthand)

在ES6之前,如果我们要将某个变量赋值为同样名称的对象元素,则需要:

var cat = 'Miaow'var dog = 'Woof'var bird = 'Peet peet'
var someObject = {  cat: cat,  dog: dog,  bird: bird}

但是在ES6里我们就方便很多:

let cat = 'Miaow'let dog = 'Woof'let bird = 'Peet peet'
let someObject = {  cat,  dog,  bird}
console.log(someObject)
//{//  cat: "Miaow",//  dog: "Woof",//  bird: "Peet peet"//}

非常方便

Promise

Promise 是ES6提供的一种异步解决方案,比回调函数更加清晰明了。

Promise 翻译过来就是承诺的意思,这个承诺会在未来有一个确切的答复,并且该承诺有三种状态,分别是:

1.等待中(pending)2.完成了 (resolved)3.拒绝了(rejected)

这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,也就是说一旦状态变为 resolved 后,就不能再次改变

new Promise((resolve, reject) => {  resolve('success')  // 无效  reject('reject')})

当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的

new Promise((resolve, reject) => {  console.log('new Promise')  resolve('success')})console.log('finifsh')// new Promise -> finifsh

Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装

Promise.resolve()  .then(res => {    console.log(res) // => 1    return 2 // 包装成 Promise.resolve(2)  })  .then(res => {    console.log(res) // => 2  })

当然了,Promise 也很好地解决了回调地狱的问题,例如:

ajax(url, () => {    // 处理逻辑    ajax(url1, () => {        // 处理逻辑        ajax(url2, () => {            // 处理逻辑        })    })})

可以改写成:

ajax(url)  .then(res => {      console.log(res)      return ajax(url1)  }).then(res => {      console.log(res)      return ajax(url2)  }).then(res => console.log(res))

for...of

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

例子如下:

const array1 = ['a', 'b', 'c'];
for (const element of array1) {      console.log(element)}
// "a"// "b"// "c"
 

Symbol

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

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

例子如下:

);const symbol3 = Symbol('foo');
console.log(typeof symbol1); // "symbol"console.log(symbol3.toString()); // "Symbol(foo)"console.log(Symbol('foo') === Symbol('foo')); // false

迭代器(Iterator)/ 生成器(Generator)

迭代器(Iterator)是一种迭代的机制,为各种不同的数据结构提供统一的访问机制。任何数据结构只要内部有 Iterator 接口,就可以完成依次迭代操作。

一旦创建,迭代器对象可以通过重复调用next()显式地迭代,从而获取该对象每一级的值,直到迭代完,返回{ value: undefined, done: true }

虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。生成器函数使用 function*语法编写。最初调用时,生成器函数不执行任何代码,而是返回一种称为Generator的迭代器。通过调用生成器的下一个方法消耗值时,Generator函数将执行,直到遇到yield关键字。

可以根据需要多次调用该函数,并且每次都返回一个新的Generator,但每个Generator只能迭代一次。

所以我们可以有以下例子:

function* makeRangeIterator(start = , end = Infinity, step = ) {    ,,)a.next() // {value: 1, done: false}a.next() // {value: 3, done: false}a.next() // {value: 5, done: false}a.next() // {value: 7, done: false}a.next() // {value: 9, done: false}a.next() // {value: undefined, done: true}

Set/WeakSet

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

所以我们可以通过Set实现数组去重

,,,,,,,,,,,,,,,,,,]console.log([...new Set(numbers)]) // [2, 3, 4, 5, 6, 7, 32]

WeakSet 结构与 Set 类似,但区别有以下两点:

WeakSet 对象中只能存放对象引用, 不能存放值, 而 Set 对象都可以。•WeakSet 对象中存储的对象值都是被弱引用的, 如果没有其他的变量或属性引用这个对象值, 则这个对象值会被当成垃圾回收掉. 正因为这样, WeakSet 对象是无法被枚举的, 没有办法拿到它包含的所有元素。

所以代码如下:

var ws = new WeakSet()var obj = {}var foo = {}
ws.add(window)ws.add(obj)
ws.has(window) // truews.has(foo)    // false, 对象 foo 并没有被添加进 ws 中
ws.delete(window) // 从集合中删除 window 对象ws.has(window)    // false, window 对象已经被删除了
ws.clear() // 清空整个 WeakSet 对象

Map/WeakMap

Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。

例子如下,我们甚至可以使用NaN来作为键值:

var myMap = new Map();myMap.set(NaN, "not a number");
myMap.get(NaN); // "not a number"
var otherNaN = Number("foo");myMap.get(otherNaN); // "not a number"

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

Map的区别与SetWeakSet的区别相似,具体代码如下:

var wm1 = new WeakMap(),    wm2 = new WeakMap(),    wm3 = new WeakMap();var o1 = {},    o2 = function(){},    o3 = window;
wm1.);wm1.set(o2, "azerty");wm2.set(o1, o2); // value可以是任意值,包括一个对象wm2.set(o3, undefined);wm2.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个WeakMap对象wm1.get(o2); // "azerty"wm2.get(o2); // undefined,wm2中没有o2这个键wm2.get(o3); // undefined,值就是undefined
wm1.has(o2); // truewm2.has(o2); // falsewm2.has(o3); // true (即使值是undefined)
wm3.);wm3.get(o1); // 37wm3.clear();wm3.get(o1); // undefined,wm3已被清空wm1.has(o1);   // truewm1.delete(o1);wm1.has(o1);   // false

Proxy/Reflect

Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

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

ProxyReflect是非常完美的配合,例子如下:

const observe = (data, callback) => {      return new Proxy(data, {            get(target, key) {                return Reflect.get(target, key)            },            set(target, key, value, proxy) {                  callback(key, value);                  target[key] = value;                    return Reflect.set(target, key, value, proxy)            }      })}
const FooBar = { open: false };const FooBarObserver = observe(FooBar, (property, value) => {  property === 'open' && value           ? console.log('FooBar is open!!!')           : console.log('keep waiting');});console.log(FooBarObserver.open) // falseFooBarObserver.open = true // FooBar is open!!!

当然也不是什么都可以被代理的,如果对象带有configurable: false 跟writable: false 属性,则代理失效。

Regex对象的扩展

正则新增符号

i 修饰符

  // i 修饰符  /[a-z]/i.test('\u212A') // false  /[a-z]/iu.test('\u212A') // true

•y修饰符

  // y修饰符  var s = 'aaa_aa_a';  var r1 = /a+/g;  var r2 = /a+/y;
  r1.exec(s) // ["aaa"]  r2.exec(s) // ["aaa"]
  r1.exec(s) // ["aa"]  r2.exec(s) // null

•String.prototype.flags

  // 查看RegExp构造函数的修饰符  var regex = new RegExp('xyz', 'i')  regex.flags // 'i'

•unicode模式

  var s = '
04-30 23:49