ECMAScript6(以下简称ES6)是JavaScript语言的最新一代标准,发布于2015年6月,因为ECMA委员会决定从ES6起每年更新一次标准,因此ES6被改名为ES2015,后面的标准将按照发布的年份命名,比如ES2016、ES2017。
ES6引入了很多新特性,这些特性解决了JavaScript长久以来别开发者所诟病的很多缺点。同时,ES6也给JavaScript的语言带来了重大变革,它使JavaScript变得更加强大、更富有表现力。尽管属于重大升级,但ES6仍然秉持了最大化兼容已有代码的设计理念,因此采用ES5及之前的标准编写的JavaScript代码在ES6环境下将继续正常运行。
由于发布日期尚短,当前主流的浏览器对ES6还没有全面支持,不过好消息是支持程度正在逐步提高,目前在各大浏览器的最新版本中ES6的大部分特性都已经实现。而Node.js对ES6的支持性还要优于浏览器,通过node可以体验更多ES6特性。也可以使用Babel等JavaScript预编译器将ES6代码转化WieES5代码,从而在现有环境中执行。
历史上,JavaScript一直没有模块的概念,这会导致当项目大到一定程度时JavaScript代码将变得难以复用且极难维护。因此,在相当长的一段时间内,对于大型复杂项目来说,JavaScript基本从一开始就被排除在方案之外。JavaScript官网一直缺少对模块的定义,直到ES6出现。
ES6从官方标准中带来了模块化开发规范。下面主要介绍模块化开发当中最重要的export和import概念。
在ES6中,一个文件就是一个模板,一个模板内的所有变量,对于外部来说都是无法获取的,除非使用关键词export对外暴露接口,暴露的各接口通过名字来进行区分。如以下代码,lib.js模块通过sqrt、square、diag向外界暴露三个接口。
//---------lib.js-----------
/*
暴露三个接口给外界
*/
export const sqrt=Math.sqrt;
export function square(x){
retrun x*x;
}
export function diag(x,y){
return sqrt(Square(x)+Square(y));
export也可以采用下面的方式暴露接口:
//---------lib.js-----------
const sqrt=Math.sqrt;
function square(x){
retrun x*x;
}
function diag(x,y){
return sqrt(Square(x)+Square(y));
//通过export暴露接口的第二种语法,使用大括号指定要暴露的接口
export{sqrt,square,dig};
通常情况下,export暴露的接口就是器本来的名字,不过可以采用as语法进行别名export,这种导出方式可以将一个接口通过n个名字对外暴露。示例代码如下:
//---------lib.js-----------
const sqrt=Math.sqrt;
export{sqrt as sq1,sqrt as sq2};
注:在ES6规范中,如果b模块从a模块中导入一个原始值后,在a模块中修改了这个原始值,那么b模块中的值也会与a模块中最新的值保持同步,即export暴露的接口与其在模块内部对应的值是一种动态绑定的关系,通过接口可以获取模块内部实时的值。
使用export命令对外暴露接口以后,其他JavaScript文件就可以通过import命令加载这个模块(文件)。在main.js模块中就可以通过import引入上一节中lib.js暴露的接口,代码示例如下:
//---------main.js-----------
/*
通过import语法从lib模块中导入所需要的接口,大括号中的接口名必须在lib.js模块中,通过export关键词导出
*/
import {square,diag} from './lib';
console.log(square(11)); //121
console.log(diag(4,3));//5
import也可以采用as语法对引入的变量重命名,代码示例如下:
//---------lib.js-----------
export var myVar1="var1";
//---------main.js-----------
import{myVar1 as myCustomVar1} from './lib';
console.log(myCustomVar1);
import会执行加载的模块,因此有空import的语法。代码示例如下:
//只加载执行模块,不引用任何接口
import 'lib'
import 还可以整体加载模块,达到命名空间的效果。代码示例如下:
//---------lib.js-----------
export var myVar1=...;
export var myVar2=...;
export const MY_CONST=...;
export function myFunc(){
...
}
export function* myGeneratorFunc(){
...
}
export class MyClass{
...
}
//---------main.js-----------
import * as lib from './lib';
console.log(lib.myVar1);
console.log(lib.myVar2);
new lib.MyClass();
有时候一个模块实际上只对外暴露一个接口,这时候实际上没必要再限定暴露的接口名字,在ES6中可以使用export default语法让模块调用者自定义要导入的接口的名字。代码示例如下:
//---------myFunc.js-----------
export default function(){}
//---------main1.js-----------
/*
注意:myFunc不能包含在{}里;myFunc可以替换为任意喜欢的名字
*/
import myFunc from 'myFunc';
myFunc();
本质上,export default就是输出一个名为default的变量或方法,然后系统允许我们进行重命名。代码示例如下:
//lib.js
function add(x,y){
return x*y;
}
export {add as default};
//等同于
//export default add;
//main.js
import {default as myAdd} from 'lib';
//等同于
//import myAdd form 'lib';
使用export default命令,可以直观地引入模块,比如引用jquery模块可以这样写:
import $ form 'jquery';
Vue.js采用export/import进行模块化开发,文件通过export暴露接口,通过import引用其他文件的内容。Vue.js的源码结构优雅、层次严谨,而这一切都离不开export和import。
举例来说,Vue.js的入口文件是/src/index.js,该文件开头如下:
import Vue from './instance/vue'
import installGlobalAPI from './global-api'
import {inBrowser, devtools} from './util/index'
import config from './config'
index.js从其他4个文件引用了必要的变量和方法。进入引用的第一个文件/src/instance/vue.js中,可以看到,其采用了export default命令:
export default Vue
也有不采用export default方式暴露接口的,比如在/src/filters/array-filters.js中采用以下语法对外暴露了三个函数方法:
export function limitBy(...){},
export function limitBy(...){},
export function limitBy(...){}
在/src/filters/index.js中引入上述三个函数:
import{orderBy,filterBy,limitBy} from './array-filters'
代码中任何一对花括号({和})中的语句集都属于一个块,其中定义的所有变量在代码块外都是不可见的,称之为块级作用域。在ES5及之前的版本中均不存在块级作用域,只有全局作用域和函数作用域。由此带来的问题很多,比如内层变量可能会覆盖外层变量、用于计数的循环变量泄露为全局变量等。以往,开发者往往要通过闭包等方式来模拟块级作用域,非常繁琐。
let关键词的出现为JavaScript带来了期待已久的块级作用域。
(1)let的使用
let的作用是声明变量,用法类似于var。与var所不同的是,let声明的变量只在let命令所在的代码块内有效。代码示例如下:
{
let a='ddfe';
var b='didiFamily';
}
在let中声明的a的代码块之外调用a会报错,用var声明的b可以返回正确的值。这表明let声明的变量只在其所在代码块内有效。
for(let i=0;i<10;i++){
...//i只在for循环体内有效
}
console.log(i);//ReferenceError: i is not defined
{
let a='ddfe';
let a='didiFamilyt'//报错
}
{
let a='ddfe';
var a='didiFamilyt'//报错
}
console.log(foo);//输出undefined
console.log(bar);//报错ReferenceError
var foo='ddfe';
let bar='ddfe';
if(true){
//TDZ开始
tmp='ddfe';//ReferenceError
console.log(tmp);//ReferenceError
let tmp;//TDZ结束
console.log(tmp);//undefined
tmp=123;
console.log(tmp);//123
}
(2)let在Vue.js中的使用
由于使用let具有严谨、不易发生错误同时含有块级作用域等诸多优点,在Vue.js中运用let命令的情况比较多,比如:
//在src/config.js文件中保存了全局配置信息
let delimiters=['{{','}}']
let unsafeDelimiters=['{{{','}}}']
再比如:在src/batcher.js文件中,runBatcherQueue函数中的for循环变量也是采用let声明的:
function runBatcherQueue(queue){
for(let i=0;i
(1) const的使用
const PI=3.141;
PI//3.141
PI=3;
PI//3.141,重新赋值无效,PI值不变
const ddfe;
ddfe='wonderful';//重新赋值无效
ddfe//undefined
const ddfe={};
ddfe.age=4;
ddfe.age//4
ddfe={};// TypeError: "foo" is read-only
由上例可以看到,使用const声明一个对象ddfe后,对象内的属性仍然可以修改。但不可改变ddfe指向的对象的地址。若想达到对象本身不可变的效果,应当使用Object.freeze方法。
(2)const在Vue.js中的使用
在Vue.js中对于一些常用的变量都采用了const方式声明。比如在src/transition/transition.js中,对transition的动画类型的声明便采用了const命令。代码示例如下:
const TYPE_TRANSITION='transition'
const TYPE_ANIMATION='animation'
再比如,很多正则检测用的变量,也都使用const声明,因为这些都是一经声明就不再更改的常量。代码示例如下:
// src/compiler/compile.js
const bindRE=/^v-bind:|^:/
// src/filters/index.js
const digitsRE=/(\d{3})(?=\d)/g