ECMAScript 6规范总结(长文慎入)

本文介绍了ES6的关键特性,包括语法升级、模块化、类型升级和异步编程。深入探讨了块级作用域、箭头函数、class、模块化系统、Promise和Generator等内容,强调了ES6对JavaScript语言的重大改进,旨在帮助读者掌握ES6的编程思想和最佳实践。
摘要由CSDN通过智能技术生成

闲话

学习ES6的动机起于对其promise标准的好奇,它与jQuery源码中Deferred不同,而且在异步编程中加入了Generator,在后续ES7中更有Async。这勾起我强烈的兴趣了解ES6更多的内容,于是完整的学习了阮一峰老师的《ECMAScript 6入门》

本文不对规范细节做详细说明。希望通过这篇博客,记录自己所理解的es6的语言风格和编程思想。

注:以《ECMAScript 6入门》为蓝本,大量用例出自其中。

ES6介绍

ECMAScript 6(简称ES6)是JavaScript语言的下一代标准,于2015年6月正式发布,也称ECMAScript 2015。

摘自《ECMAScript 6入门》

ECMAScript 3.0(1999年12月)成为通行标准,奠定了JavaScript通行标准,直到今天,我们一开始学,都是在学3.0版本语法。
ECMAScript 4.0草案(2007年10月),对ES3做了彻底升级,各方代表对是否通过产生严重分歧。2008年7月,EMCA开会决定终止开发,将其中涉及现有功能改善的一小部分,发布为ECMAScript 3.1(会后不久改名为ECMAScript 5),将其他激进的设想放在以后的版本,由于会议的气氛,该项目代号起名为Harmony(和谐)。

2009年12月,ECMAScript 5.0正式发布。Harmony项目一分为二,一些较为可行的设想定为JavaScript.next继续开发,后来演变成ES6,一些不是很成熟的设想,被视为JavaScript.next.next,在更远的将来再考虑推出。

2011年6月,ECMAscript 5.1版发布,并且成为ISO国际标准(ISO/IEC 16262:2011)
2015年6月,ECMAScript 6正式通过,成为国际标准。


ES6总览

tips:

  • ES6规范的原则是尽可能完整的向下兼容,除了块级作用域支持外,原有代码几乎不受影响。通过新增API及语法扩展支持。随着规范的普及,完全参照严格模式'use strict'将成为编程最佳实践

  • 不同类别的工具方法挂载在对应的构造函数上,而不是作为全局方法(如isNaN() -> Number.isNaN()),对原有全局方法进行了迁移(原有的还在)。

下面,分 5 点对 ES6 进行全面解读。ES6总览后,为每点的分条详述。

1、语法升级

对基本语法进行了增强,并调整为块级作用域支持。

用更直观的“声明式”思想(解构赋值、...扩展运算符、无 this 上下文困扰的箭头函数、for…of 遍历),对取值、赋值、对象表示、构造函数及继承等的过程进行了大幅简化。

2、模块化

静态化的模块系统支持(默认严格模式编程)。完美的循环依赖处理(commonjs只算半支持),动态的输出值引用。

3、类型升级

Number 新的二/八进制写法、浮点误差、安全数;String RegExp:全面支持32位utf16字符,定义了超简易的模板字符串拼接(并可便捷的自定义模板处理规则);引入基本数据类型Symbol,代表独一无二值,有效防止属性命名冲突;Array数组空位处理方法的修正,提供 for…of 遍历及对名值遍历的API支持;新增数据结构SetMap及弱引用的WeakSetWeakMap,可去重存储value、key-value。

数据结构的增加,使得ES6 for…of遍历不仅仅需要对数组、字符串等带有length属性的类数组生效,还需要能够个性化定制。抽象出Symbol.iterator接口,凡是带有该接口的对象均可被遍历(仅有length属性的类数组不可以),调用该接口。比如数组会调用Array.prototypeSymbol.iterator

Symbol属性的添加也使对象枚举相关的API增加了几个(是否枚举Symbol、原型链、不可枚举属性)

tips:遍历与枚举的不同在于,遍历是对值(value)的,枚举是对键(key)的。遍历的顺序是Symbol.iterator接口定义的(数组是0~n数字顺序);枚举是底层内部定义的(顺序:先数字排序、属性按时间排、Symbol按时间排),未开放权限

4、语言层面

分层的权限

为了便于理解,我把底层行为分为 规则层(基于对象,被遍历、被枚举、被正则匹配、被new、被转类型等)、属性配置层(基于属性,propertyDescriptor)。

ES6的一大特点是,开放权限。姑且把我所理解的权限分为 5 类:原型链、调用栈、作用域链、对象规则层、属性配置层。

ES6函数严格模式执行时不再对调用栈引用,此时支持尾递归优化。作用域链引用不可开放,这是词法作用域安全性、隔离性的根本。开放了规则层自定义,使得开发者能够自定义一些对细部规则的反应。开放了原型链的访问,使得已有对象也能直接改变原型链引用,使更强大的继承容易做到(通常尽可能不用)。属性配置层到了ES5就比较完善了。

ES6把规则层的部分行为抽象为一系列接口,涉及被正则匹配、被判断instanceof、被for…of遍历、数组是否可展开、构造器的返回对象和stringTag等。出于防止命名冲突的考虑,都使用Symbol值(独一无二),保存在内置的Symbol构造函数的属性上,共11个(很多并不是语言层面的重要规则操作,定位:偏个性化的需求 + 部分重要规则)

Object实例是js里的基础对象,包括函数都是由object衍生而来。它是一种基本的key-value式的数据结构。

  • 1、对象下通过内置Symbol规则属性个性化定义特殊行为时如何反应。
  • 2、每个属性的value,只是属性描述的一部分。Object.getOwnPropertyDescriptor(obj, pro)可获取,设定是否可枚举、可定义、只读、是否为访问器(get、set)。

Proxy和Reflect

ES6新增Proxy数据类型,可以通过new Proxy(obj, handler)生成对象操作的代理,本质是一个拦截层,涉及增删查改属性值、设置原型链、属性配置、遍历枚举、环境绑定、new等等操作(部分内置Symbol不是对对象的主要操作,只是小的个性化补充,就不包含在内了)。

新增Reflect,提供了所有与Proxy对应的语言默认操作方法,一一对应,目前有 14 个。

Reflect有着几乎所有对对象的重要操作,ES6以前跟语言相关的配置操作都在Object上,都迁移了过去,并且对设置型的API都以返回false表示设置失败,而不是抛出错误。以后语言内部相关的方法都将扩充到Reflect,Object上不一定会添加。

5、异步编程

传统的异步使用回调函数,函数以参数形式传入以待调用。复杂情况时,回调函数里可能也有异步逻辑,导致层层嵌套。而且还需要手动catch错误。

ES6推出了promise标准。既能把每层的逻辑解耦分开,又有自动的机制catch错误。通过then串联起来要执行的逻辑。

ES6支持Generator函数。它是语言层面的支持,用同步的方式来顺序书写异步代码,以yield暂停。相较promise有着更直观的控制流管理,“半协程”的实现,使得在yield进程的切换中仍然保留着调用栈,使得内部定义的 try…catch 总能捕捉到内部的错误,是完全意义上的同步式写法。虽然在promise的源码中利用词法作用域的特点也能解决。

但Generator只相当于一个状态机,声明式的定义了流程,还需要封装一个co模块函数才能实现支持异步逻辑的自动流程处理。

ES7提供了Async函数,是Generator的语法糖,调用时等同于被co函数加载执行的Generator函数。到此,异步编程算是得到了最佳实践。


语法升级

核心:用一目了然的方式,简化表达。定义ES6推荐的最佳编程实践。

1、作用域

ES6支持了块级作用域,{} 部分包裹的代码块具有独立的作用域,如if、for。新增let(变量) const(常量)定义变量,必须先定义后使用,不会变量提升,更不容易出错,填var的坑。

{
    let a = 5;
    const b = 4;  // 不能重新赋值或改变引用,但能改变引用对象内的属性
    a = 3;  // 3
    b = 3;  // error
}
console.log(a)  // error

// 自执行函数 作用有 2 点:1.防止全局污染; 2.构造闭包保存变量状态
// 在只需 第1点 时,可以 { 代码 } 替代

函数声明可以在块级内声明 { }不再报错,只在块级作用域中变量提升。

if (true)
    function a() {
   }  // error

// 正确版本,不能省略{}
if (true) {
    function a() {
   }
}
console.log(a);  // error


2、取值、赋值、对象表达

对象简写

let b ='check';
let obj = {
    a: 1,
    b,  // 等同 b: b ,即 b: 'check'

    c(x, y) { return x+y },  // 等同 c: function(){}

    get d() { return 2; }, // 设置 d 的 get 取值器函数
    [Symbol('foo')]() {
  return true},  // 设置 [Symbol('foo')] 的函数值
    * e(x) { yield x; }  // 设置 e 的Generator函数值

function test(x, y) {
   
    return {x, y};  // {x: x, y: y}
}

一步到位 的赋值方法 —— 解构赋值 + 默认值,直观、高效。

let [a, [b, c]] = [3, 'str'];
// a=3, b='s', c='t'   数组型赋值:要求右侧值有Symbol.iterator接口,如数组、字符串

let {a, b=4, c=4, d: _d=5} = {a: 2, b: 3};  // 等同 let {a: a, b: b=4, c: c=4, d: _d=5} = {a: 2, b: 3};
// a=2, b=3, c=4(默认值), _d=5(默认值)

[x, y] = [y, x]  // 交换赋值


/* ---- 优化示例 ---- */

// ES3,遇上一个 fun(args) 的API,args有7个可选属性接口,看得出么,一脸懵逼(゚Д゚≡゚Д゚)
function sb(args) {
   
    return args.a + args.b * args.c;
}

// ES6
function sb({a, b, c}) {
   
    return a + b * c;  // 无参数时出错
}
sb({a:1, b:2, c:3});  // 7

// 不传参数时默认为 {a:0, b:0, c:0}
function sb({a, b, c} = {a:0, b:0, c:0}) {
   
    return a + b * c;
}
sb();  // 7
sb({a:2});  // error, 不使用默认值,但b、c为undefined

// 属性默认值
// 无值参数取默认值{},无a、b、c参数,默认取0
function sb({a=0, b=0, c=0} = {}) {
    // 等同 {a: a=0, b: b=0, c: c=0} = {}
    return a + b * c;
}
sb();  // 7

引入“…”扩展运算符数组环境(函数参数算数组环境),用于赋值(左侧=)为rest参数(只可用于尾参数),用于取值则为扩展值(=右侧,需Symbol.iterator接口支持)

/* 赋值,rest 参数 */
let [a ,b, ...c] = [1, 2, 3, 4, 5];
// a=1, b=2, c=[3, 4, 5]
let [a ,b, ...[c, d]] = [1, 2, 3];
// a=1, b=2, c=3, d=undefined

function t(a, ...arr) {
   }
t(1,2,3) -> a=1, arr[1, 2]

/* 取值 */
let a = [...[1, 2], 3, ...'str'];
// [1, 2, 3, 's', 't', 'r']

/* 两者结合 —— 解决平常厌恶的只能apply传入相同参数的问题 */
function test(...args) {
     // 赋值
    return function _test() {
   
        return fun(...args);  // 取值,等同 fun.apply(this, args)
    }
}

ES7提案 引入“…”扩展运算符对象环境。用于赋值(左侧=)为rest参数(只可用于尾参数),用于取值则为扩展值(=右侧,只扩展自身的可枚举属性,等同Object.key(obj))

/* 赋值,rest 参数 */
let {a ,b, ...re} = {a:1, b:2, c:3, d:4, e:5};
// a=1, b=2, re={c:3, d:4, e:5]
let {...{x, y}} = {x:1, y:2};
// x=1, y=2

/* 取值 */
let a = {...[1, 2], gg:3, ...{x:4, y:5}};
// {'0':1, '1':2, gg:3, x:4, y:5}

ES7提案 ‘::’简化bindapplycall

foo::bar;
// 等同于
bar.bind(foo);

foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);

::console.log  // 等同于 console::console.log


3、箭头函数

只相当于一个简单的 { } 块级代码段(选择性使用)。没有普通函数的能力:独立的 this 上下文(这有时候是坑的来源。箭头函数 bind 也无效)、arguments 参数、对调用栈的访问。不能用作Generator状态机。

let a = (x) => x+2;
// '=>' 左侧的参数若是一个,可简写为 let a = x => x+2;
// '=>' 右侧 x+2 是 {return x+2;} 的简写,{}中包含函数中所有代码

// 若返回对象,可({})返回。(x) => {return {id: x};} 可简写为 x => ({id: x})

var obj = {
    a: 1,
    b: function() {
   
        setTimeout( () => {
            this.a++;  // this 为 b 函数内 this,可以更简单的绑定环境
        }, 0);
    }
};

// 便捷易懂的管道
let f = (x=0) => (y=0<
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值