Vue.js源码中大量采用的ES6新特性介绍:模块、let、const

1 关于ES6
      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代码,从而在现有环境中执行。

2 模块
       历史上,JavaScript一直没有模块的概念,这会导致当项目大到一定程度时JavaScript代码将变得难以复用且极难维护。因此,在相当长的一段时间内,对于大型复杂项目来说,JavaScript基本从一开始就被排除在方案之外。JavaScript官网一直缺少对模块的定义,直到ES6出现。

       ES6从官方标准中带来了模块化开发规范。下面主要介绍模块化开发当中最重要的export和import概念。

export
       在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暴露接口的第二种语法,使用大括号指定要暴露的接口
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暴露的接口与其在模块内部对应的值是一种动态绑定的关系,通过接口可以获取模块内部实时的值。

import
       使用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();
export default


       有时候一个模块实际上只对外暴露一个接口,这时候实际上没必要再限定暴露的接口名字,在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';
export/import在Vue.js中的使用
      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'

3 let
       代码中任何一对花括号({和})中的语句集都属于一个块,其中定义的所有变量在代码块外都是不可见的,称之为块级作用域。在ES5及之前的版本中均不存在块级作用域,只有全局作用域和函数作用域。由此带来的问题很多,比如内层变量可能会覆盖外层变量、用于计数的循环变量泄露为全局变量等。以往,开发者往往要通过闭包等方式来模拟块级作用域,非常繁琐。

       let关键词的出现为JavaScript带来了期待已久的块级作用域。

(1)let的使用

        let的作用是声明变量,用法类似于var。与var所不同的是,let声明的变量只在let命令所在的代码块内有效。代码示例如下:

{
let a='ddfe';
var b='didiFamily';
}


        在let中声明的a的代码块之外调用a会报错,用var声明的b可以返回正确的值。这表明let声明的变量只在其所在代码块内有效。

let很适合用来声明循环变量。代码示例如下:
for(let i=0;i<10;i++){
...//i只在for循环体内有效
}
console.log(i);//ReferenceError: i is not defined
let不允许重复声明。在相同作用域内,重复声明同一个变量会报错。代码示例如下:

{
let a='ddfe';
let a='didiFamilyt'//报错
}

{
let a='ddfe';
var a='didiFamilyt'//报错
}


let不存在变量提升。因此,变量需要在声明后才可以使用,否则会报错。代码示例如下:

console.log(foo);//输出undefined
console.log(bar);//报错ReferenceError

var foo='ddfe';
let bar='ddfe';


let存在暂时性死区(temporal deadzone,以下简称TDZ),即指在当前代码块内如果使用了let声明变量,则在该条声明语句之前,该变量不可用。代码示例如下:

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<queue.lenght;i++){
...
}
queue.length=0
}

4 const
(1) const的使用

const用于声明一个变量,一旦声明,常量的值便不能再被更改,进入只读模式。代码示例如下:

const PI=3.141;
PI//3.141
PI=3;
PI//3.141,重新赋值无效,PI值不变


使用const声明后不得再更改的特性,也就是说在声明变量时就必须初始化,不能留到以后再赋值。代码示例如下:

const ddfe;
ddfe='wonderful';//重新赋值无效
ddfe//undefined
const的作用域与let相同,只在声明所在的块级作用域内有效。
const与let一样,同样不允许重复声明,不存在变量提升以及TDZ。
const在声明复合类型的变量时,只能保证变量名指向的地址不变,并不保证该地址的数据不变(因为复合类型的变量名指向数据地址而不指向数据)。因此,如果使用const声明一个对象,并不能保证对象不可更改。示例代码如下:
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


参考:https://blog.csdn.net/bluedandelion/article/details/80625961