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暴露的接口就是器本来的名字,不过可以采用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

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


你可能感兴趣的:(ES6)