1、ES5/ES6和babelnode
ECMAScript5,即ES5,是ECMAScript的第五次修订,于2009年完成标准化,如今的浏览器已经至关于彻底实现了这个标准。
ECMAScript6,即ES6,也称ES2015,是ECMAScript的第六次修订,于2015年完成,而且运用的范围逐渐开始扩大,由于其相对于ES5更加简洁,提升了开发速率,开发者也都在陆续进行使用,可是因为ES6还存在一些支持的问题,因此通常即便是使用ES6开发的工程,也须要使用Babel进行转换。
Babel是一个普遍使用的ES6转码器,能够将ES6代码转为ES5代码,从而在现有环境执行。这一过程叫作“源码到源码”编译, 也被称为转换编译。es6
通常来讲Babel做为依赖包被引入ES6工程中,此处再也不介绍以cli方式使用的ES6,若是你须要以编程的方式来使用 Babel,可使用 babel-core 这个包。babel-core 的做用是把 js 代码分析成 ast ,方便各个插件分析语法进行相应的处理。有些新语法在低版本 js 中是不存在的,如箭头函数,rest 参数,函数默认值等,这种语言层面的不兼容只能经过将代码转为 ast,分析其语法后再转为低版本 js。babel的使用过程以下:npm
$ npm install babel-core
var babel = require("babel-core");
字符串形式的 JavaScript 代码能够直接使用 babel.transform 来编译。
编程
若是是文件的话,可使用异步 api:json
或者是同步 api:api
或者在development环境下可使用bable-node和bable-register的方式配置,过程以下:数组
在Node.js工程package.json包中添加以下依赖:浏览器
2. 配置dev脚本服务器
接下来罗列一下ES6的语法要点参考备用。babel
这两个的用途与var相似,都是用来声明变量的,但在实际运用中他俩都有各自的特殊用途。
首先来看下面这个例子:
使用var 两次输出都是obama,这是由于ES5只有全局做用域和函数做用域,没有块级做用域,这带来不少不合理的场景。第一种场景就是你如今看到的内层变量覆盖外层变量。而let则实际上为JavaScript新增了块级做用域。用它所声明的变量,只在let命令所在的代码块内有效。
另一个var带来的不合理场景就是用来计数的循环变量泄露为全局变量,看下面的例子:
上面代码中,变量i是var声明的,在全局范围内都有效。因此每一次循环,新的i值都会覆盖旧值,致使最后输出的是最后一轮的i的值。而使用let则不会出现这个问题。
const也用来声明变量,可是声明的是常量。一旦声明,常量的值就不能改变。
ES6 容许按照必定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
解构不成功时变量赋值为undefined
存在不彻底解构的状况以下:
解构赋值容许指定默认值。ES6 内部使用严格相等运算符(===),判断一个位置是否有值。因此,只有当一个数组成员严格等于undefined,默认值才会生效。
以下是解构赋值的应用实例:
以下两种函数的定义方法在解构赋值时具有不一样的返回值:
模板字符串(template string)是加强版的字符串,用反引号(`)标识。它能够看成普通字符串使用,也能够用来定义多行字符串,或者在字符串中嵌入变量。
若是在模板字符串中须要使用反引号,则前面要用反斜杠转义。
let greeting = `\`Yo\` World!`;
大括号内部能够放入任意的 JavaScript 表达式,能够进行运算,以及引用对象属性。若是大括号中的值不是字符串,将按照通常的规则转为字符串。好比,大括号中是一个对象,将默认调用对象的toString方法。
模板字符串它能够紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。
模板字符串前面有一个标识名tag,它是一个函数。整个表达式的返回值,就是tag函数处理模板字符串后的返回值。函数tag依次会接收到多个参数。
tag函数的第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。tag函数的其余参数,都是模板字符串各个变量被替换后的值。因为本例中,模板字符串含有两个变量,所以tag会接受到value1和value2两个参数。也就是说,tag函数实际上如下面的形式调用。
tag(['Hello ', ' world ', ''], 15, 50)
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不须要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
arguments对象不是数组,而是一个相似数组的对象。因此为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法均可以使用。
rest函数的实现也是基于扩展运算符,扩展运算符(spread)是三个点(...)。它比如 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
扩展运算符提供了复制数组的简便写法。
扩展运算符提供了数组合并的新写法。
ES6 容许使用“箭头”(=>)定义函数。
若是箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,而且使用return语句返回。若是箭头函数不须要参数或须要多个参数,就使用一个圆括号表明参数部分。
因为大括号被解释为代码块,因此若是箭头函数直接返回一个对象,必须在对象外面加上括号,不然会报错。
箭头函数能够与变量解构结合使用。
箭头函数的一个用处是简化回调函数。
箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不能够看成构造函数,也就是说,不可使用new命令,不然会抛出一个错误。
(3)不可使用arguments对象,该对象在函数体内不存在。若是要用,能够用 rest 参数代替。
(4)不可使用yield命令,所以箭头函数不能用做 Generator 函数。
上面四点中,第一点尤为值得注意。this对象的指向是可变的,可是在箭头函数中,它是固定的。
上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到 100 毫秒后。若是是普通函数,执行时this应该指向全局对象window,这时应该输出21。可是,箭头函数致使this老是指向函数定义生效时所在的对象(本例是{id: 42}),因此输出的是42。
ES6 容许直接写入变量和函数,做为对象的属性和方法。这样的书写更加简洁。
ES6 容许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。下面是另外一个例子。
函数的name属性,返回函数名。对象方法也是函数,所以也有name属性。
在对象的继承、原型和构造函数上ES6提供了更接近传统语言的写法,引入了Class(类)这个概念。新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。
上面代码首先用class定义了一个“类”,能够看到里面有一个constructor方法,这就是构造方法,而this关键字则表明实例对象。简单地说,constructor内定义的方法和属性是实例对象本身的,而constructor外定义的方法和属性则是全部实例对象能够共享的。
Class之间能够经过extends关键字实现继承,这比ES5的经过修改原型链实现继承,要清晰和方便不少。上面定义了一个Cat类,该类经过extends关键字,继承了Animal类的全部属性和方法。
super关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,不然新建实例时会报错。这是由于子类没有本身的this对象,而是继承父类的this对象,而后对其进行加工。若是不调用super方法,子类就得不到this对象。
ES6的继承机制,实质是先创造父类的实例对象this(因此必须先调用super方法),而后再用子类的构造函数修改this。
ES6 一共有 5 种方法能够遍历对象的属性。
(1)for...in
for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
(2)Object.keys(obj)
Object.keys返回一个数组,包括对象自身的(不含继承的)全部可枚举属性(不含 Symbol 属性)的键名。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的全部属性(不含 Symbol 属性,可是包括不可枚举属性)的键名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的全部 Symbol 属性的键名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys返回一个数组,包含对象自身的全部键名,无论键名是 Symbol 或字符串,也无论是否可枚举。
以上的 5 种方法遍历对象的键名,都遵照一样的属性遍历的次序规则。
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
Symbol 值经过Symbol函数生成。这就是说,对象的属性名如今能够有两种类型,一种是原来就有的字符串,另外一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,能够保证不会与其余属性名产生冲突。
因为每个 Symbol 值都是不相等的,这意味着 Symbol 值能够做为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的状况很是有用,能防止某一个键被不当心改写或覆盖(Symbol 值做为对象属性名时,不能用点运算符,由于点运算符后面老是字符串,因此不会读取mySymbol做为标识名所指代的那个值,致使a的属性名其实是一个字符串,而不是一个 Symbol 值)。
Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的全部用做属性名的 Symbol 值。
Symbol能够用于实现单例模式:
上面代码中,能够保证global[FOO_KEY]不会被无心间覆盖,但仍是能够被改写。
ES6 提供了新的数据结构 Set。它相似于数组,可是成员的值都是惟一的,没有重复的值。Set 自己是一个构造函数,用来生成 Set 数据结构。
Set 函数能够接受一个数组(或者具备 iterable 接口的其余数据结构)做为参数,用来初始化。
Set的遍历顺序就是插入顺序。这个特性有时很是有用,好比使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。
keys方法、values方法、entries方法返回的都是遍历器对象,因为 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),因此keys方法和values方法的行为彻底一致。
ES6 提供了 Map 数据结构。它相似于对象,也是键值对的集合,可是“键”的范围不限于字符串,各类类型的值(包括对象)均可以看成键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。若是你须要“键值对”的数据结构,Map 比 Object 更合适。
与其余数据结构的互相转换
(1)Map 转为数组
前面已经提过,Map 转为数组最方便的方法,就是使用扩展运算符(...)。
(2)数组 转为 Map
将数组传入 Map 构造函数,就能够转为 Map。
(3)Map 转为对象
若是全部 Map 的键都是字符串,它能够转为对象。
(4)对象转为 Map
(5)Map 转为 JSON
Map 转为 JSON 要区分两种状况。一种状况是,Map 的键名都是字符串,这时能够选择转为对象 JSON。
另外一种状况是,Map 的键名有非字符串,这时能够选择转为数组 JSON。
(6)JSON 转为 Map
JSON 转为 Map,正常状况下,全部键名都是字符串。
ES6 模块不是对象,而是经过export命令显式指定输出的代码,再经过import命令输入。
上面代码的实质是从fs模块加载 3 个方法,其余方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 能够在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。固然,这也致使了无法引用 ES6 模块自己,由于它不是对象。
因为 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,好比引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
除了静态加载带来的各类好处,ES6 模块还有如下好处。
ES6 的模块自动采用严格模式,无论你有没有在模块头部加上"use strict";。严格模式主要有如下限制。
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其余模块提供的功能。一个模块就是一个独立的文件。该文件内部的全部变量,外部没法获取。若是你但愿外部可以读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。
上面代码是profile.js文件,保存了用户信息。ES6 将其视为一个模块,里面用export命令对外部输出了三个变量。export的写法,除了像上面这样,还有另一种。
export命令除了输出变量,还能够输出函数或类(class)。一般状况下,export输出的变量就是原本的名字,可是可使用as关键字重命名。
export语句输出的接口,与其对应的值是动态绑定关系,即经过该接口,能够取到模块内部实时的值。
export命令能够出如今模块的任何位置,只要处于模块顶层就能够。若是处于块级做用域内,就会报错,下一节的import命令也是如此。这是由于处于条件代码块之中,就无法作静态优化了,违背了 ES6 模块的设计初衷。
使用export命令定义了模块的对外接口之后,其余 JS 文件就能够经过import命令加载这个模块。
上面代码的import命令,用于加载profile.js文件,并从中输入变量。import命令接受一对大括号,里面指定要从其余模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。若是想为输入的变量从新取一个名字,import命令要使用as关键字,将输入的变量重命名。
import { lastName as surname } from './profile.js';
import命令具备提高效果,会提高到整个模块的头部,首先执行。
使用import命令的时候,用户须要知道所要加载的变量名或函数名,不然没法加载。可是,用户确定但愿快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。
上面代码是一个模块文件export-default.js,它的默认输出是一个函数。其余模块加载该模块时,import命令能够为该匿名函数指定任意名字。
上面代码的import命令,能够用任意名称指向export-default.js输出的方法,这时就不须要知道原模块输出的函数名。须要注意的是,这时import命令后面,不使用大括号。
export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,所以export default命令只能使用一次。因此,import命令后面才不用加大括号,由于只可能惟一对应export default命令。本质上,export default就是输出一个叫作default的变量或方法,而后系统容许你为它取任意名字。
若是在一个模块之中,先输入后输出同一个模块,import语句能够与export语句写在一块儿。
引擎处理import语句是在编译时,这时不会去分析或执行if语句,因此import语句放在if代码块之中毫无心义,所以会报句法错误,而不是执行时错误。也就是说,import和export命令只能在模块的顶层,不能在代码块之中(好比,在if代码块之中,或在函数之中)。
这样的设计,当然有利于编译器提升效率,但也致使没法在运行时加载模块。在语法上,条件加载就不可能实现。若是import命令要取代 Node 的require方法,这就造成了一个障碍。由于require是运行时加载模块,import命令没法取代require的动态加载功能。
上面的语句就是动态加载,require到底加载哪个模块,只有运行时才知道。import语句作不到这一点。
import()加载模块成功之后,这个模块会做为一个对象,看成then方法的参数。所以,可使用对象解构赋值的语法,获取输出接口。
上面代码中,export1和export2都是myModule.js的输出接口,能够解构得到。若是模块有default输出接口,能够用参数直接得到。
如上就是ES6中比较容易识别出的关键点,实际上在两天半的ES6使用中也确实见到了如上的用法,感谢阮一峰老师的博客,先总结到这里,留待后补?