美文网首页ES6
8. Module语法

8. Module语法

作者: 羊烊羴 | 来源:发表于2018-01-11 15:19 被阅读0次

    javascript语法之前一直存在一个问题就是没有一个模块加载方案,即使在CSS中都存在@import来让我们可以加载其它模块的代码,这对于我们开发大型项目是很不友好的,很容易导致我们需要去重复写一些重复的代码,而在在ES6中有一个比较大的变动就是引入了模块加载方案,而且实现的相当简单

    在ES6中我们通过export命令显式指定输出代码,再通过import来对代码进行引入

    //a.js
    let a=1,
        b=2;
    export {a,b}
    
    //b.js
    import {a,b} from './a'
    

    以上就是ES6中基本的Module语法,如果我们之前有使用或读过Common.js/AMD/CMD,那么对这个语法应该是比较容易接受的,但是这两者之间还是有区别的

    ES6模块的设计思想,是尽量的静态化,是的编译时就能确定模块的依赖关系,以及输出和输入的变量,而在Common中,都是在运行时才能确定这些东西的

    let { stat, exists, readFile } = require('fs');
    

    在Common中模块就是对象,输入时必须查找对象属性,Common中我们加载一个模块时的实质是整体加载了该模块,生成一个对象,然后再从这个对象上读取方法,这种加载称为运行时加载,因为只有运行时才能得到这个对象

    在ES6中,模块不是对象,而是通过export命令显式指定输出的代码,再通过import对象输入

    import {stat,exists,readFile} from 'fs'
    

    上面的代码实质上是从fs模块加载三个方法,其它方法不加载,这种加载称为编译时加载/静态加载,也就是说ES6可以在编译时就完成模块加载,这样就提高了加载时的效率,但是同时也无法引用ES6模块本身,因为它不是对象

    ES6中还有一个特别需要注意的与Common的区别不同在于,Common中输出的值是缓存的值,模块内部的变化不会动态更新到外部引用了该模块的模块,影响已经而ES6中模块输出的值是动态更新的,我们在模块内的更改会影响到外部引用了该模块的模块。栗子:

    //a.js
    var cpunter = 3;
    function incCounter(){
      counter++;
    }
    module.exports={
      counter,
      incCounter
    }
    
    //b.js
    var mod=require('./a');
    
    console.log(mod.counter);//3
    mod.incCounter();
    console.log(mod.conunter);//3
    

    以上是在CommonJS规则下运行的结果,那么将该代码放在ES6的规则下

    //a.js
    var cpunter = 3;
    function incCounter(){
      counter++;
    }
    export {cpunter incCounter };
    
    //b.js
    import {cpunter incCounter} from './a'
    
    console.log(cpunter)//3
    incCounter()
    console.log(cpunter)//4
    

    其实也很好理解,之前我们也说过,在Common规则下模块接收的是一个对象,该对象会被缓存,我们是从该对象上获取某个值,而ES6模块化的规则则是在JS引擎对脚本进行静态分析的时候,遇到加载命令import就会生成一个制度引用,等到脚本真正执行时再根据这个只读引用去加载模块内的值,可以理解为是对模块内变量/函数的堆地址的引用,所以可以动态的获取变量的变化

    在Common中我们如果要动态获取原始类型的值,一般这么写

    var counter=3;
    function incCounter(){
      counter++;
    }
    module.exports={
      get counter(){
        return counter
      },
      incCounter:incCounter
    }
    

    我们将counter作为一个取值函数对外暴露,这样在强迫每次重新获取counter的值

    在ES6中接收的其他模块变量,由于只是一个”符号链接“的关系,所以这个变量是只读的,允许我们对它进行添加属性的操作,对它进行重新赋值操作会报错,因为重新赋值相当于在我们引用的模块中创建新的属性,这是不被允许的

    使用ES6模块需要注意,它会自动采用严格模式,不管我们是否添加了"use strict",所以需要我们注意一些问题

    1. arguments不会自动反映函数参数的变化
    2. 不能使用argument.callee
    3. 不能使用argument.caller
    4. 禁止this指向全局,唔,需要注意,在非严格模式下,如果this指向undefined那么会指向window,但是在严格模式中该指向会指向undefined
    5. 函数参数不能有同名属性

    export命令

    模块功能主要由两个命令构成:export和import,export用于规定模块的对外接口,import用于接收其他模块提供的功能

    一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取,如果我们希望某个变量能被外部使用,那么必须使用exprot输出该变量

    //1.
    export let a=1;
    export let b=2;
    
    //2.
    var a=1,
        b=2;
    export {a,b}
    

    上面是两种输出代码的方式,更加推荐下面的一种,可读性更高

    export命令除了输出变量,还可以输出函数或类(class)

    export function a() {
        //...
    }
    

    一般情况我们对外输出的就是函数/变量本来的名字,但也可以通过关键字as对接口重新命名

    function a() { }
    function b() { }
    
    export {
        a as get,
        a as getName,
        b as set
    }
    

    如上,在我们使用了as对a函数分别命名了get和getName后,a函数可以用这两个不同的名字在其它引用的模块中输出两次

    需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应的关系,也就是说我们要向外暴露的必须是一个对象或者是一个定义的变量/函数

    var a=1;
    export a;//错误
    
    export 1;//错误
    
    function b() {};
    export b;//错误
    //以上的写法都是直接输出值而不是接口,正确的写法如下
    var a=1;
    export{a}//正确
    
    export var a=1;
    //函数同理
    

    export命令可以出现在模块内的任何位置,只要保证是顶层作用域就可以,如果处于块级作用域内会报错,import命令也是如此

    function foo(){
      export default 'bar'  // SyntaxError
    }
    

    import命令

    js文件可以通过import命令来接收使用了export命令向外暴露了接口的模块的变量/函数

    import {first,last} from './a'
    console.log(first+'|'+last)
    

    import命令接受一对大括号,里面指定要从其它模块导入的变量名,大括号里面的变量名,必须与导入模块的对外暴露的接口的名称相同

    如果想要使用一个新名字,那么在import命令中使用关键字as来为导入的变量/函数重新命名

    import { last as child } from './a'
    

    import后面的from用来指定模块文件的位置,可以是相对路径或者是绝对路径。文件后缀可以省略,如果只是模块名,不带有路径那么必须配置文件中告诉Javascript引擎该模块的位置

    import {myMethod} from 'util'; //这里util是模块的文件名,由于不带有路径,必须通过配置告诉引擎怎么读取到这个模块
    

    注意,import命令具有提升效果,会提升整个模块的头部,首先执行

    foo()
    
    import { foo } from './a'
    

    上面的写法是完全可行的,因为在与解析时会执行import命令,早于foo的执行

    同时由于import在预解析阶段就开始执行所以不能使用表达式和变量,表达式和变量都是会在运行时才能得到结果,所以会报错

    // 报错
    import { 'f' + 'oo' } from 'my_module';
    
    // 报错
    let module = 'my_module';
    import { foo } from module;
    
    // 报错
    if (x === 1) {
      import { foo } from 'module1';
    } else {
      import { foo } from 'module2';
    }
    

    多次重复同一句import语句只会执行一次,而不会执行多次

    import { foo } from './a'
    import { bar } from './a'
    //等同于
    import { foo,bar } from './a'
    

    模块的整体加载

    除了指定加载某个输出值,还可以使用整体加载,也就是使用通配符(*)制定一个对象,所有输出值都加载在这个对象上面

    import { foo,bar } from './a'
    foo()
    bar()
    //等同于
    import * as fn from './a'
    fn.foo()
    fn.bar()
    

    注意,同样,也是静态生成的,所以不允许在运行时改变,以下写法会报错

    fn.foo=function(){...}
    

    export default命令

    也许之前我们没有看过ES6的语法而是直接使用Vue等框架配合webpack做模块化开发的话,我们会发现其实我们最常使用的是我们现在介绍的这个命令,这个命令可以说是ES6设计中比较贴心的,在上面的写法中我们必须知道所加载的变量名或函数名,否则无法加载,但是这需要我们去阅读我们要使用的模块的文档,浪费我们大量的时间,为了为用户提供方便,让我们不用阅读文档就能加载模块,就要用到export default

    该命令用于为模块指定默认输出,其它模块在加载该模块时可以为该模块指定任意的名字,并且不需要包裹在大括号内

    //a.js
    export default function(){
      console.log('foo')
    }
    //b.js
    import foo from './a'
    foo()
    

    以上代码a.js中默认输出的是一个函数,在b.js中使用foo来接收a.js模块对外暴露的函数

    当然,export default可以对外暴露命名函数,匿名函数,变量,对象,但是在向外暴露时,函数/对象/变量本身的名字会被忽略,按照匿名函数/对象/变量来向外暴露

    //a.js
    function foo (){
      ...
    }
    export default foo;//这个样子也是可以的
    
    //b.js
    import fn from './a'
    fn()
    

    注意,一个模块只允许有一个export default命令所以import后才不需要加大括号,因为只有可能对应一个方法

    其实本质上export default就是输出一个叫做dafault的变量或方法,然后系统允许你为它取任意名字,但是同时需要注意,因为在export default输出的其实是一个叫做default的变量,所以它后面不能跟变量声明语句,这一点和export是相反的

    //正确
    var a=1;
    export default a;
    //错误
    export default var a=1;
    

    上面代码中,export default a的含义是将变量a的值赋值给变量defaul,所以,最后一种写法会报错

    同样的,因为export default的本质是将该命令后面的值赋值给default,default就是我们设置的对外的接口,所以可以将一个值直接写在export default之后

    export default 42; //正确
    
    expotr 42;//报错
    

    在向外暴露接口时我们可以同时设定export default和export接口

    //a.js
    var function foo(){...}
    var  a=1;
    export { foo a };
    export default{
      b:2,
      bar:function(){
        console.log("success")
      }
    }
    
    //b.js
    import { foo a } from './a';
    import fn from './a'
    

    export与import的复合写法

    唔,这种写法主要是用来实现我们在一个模块中先导入一个模块,然后再将该模块暴露出去,这个时候我们需要将import语法和export语法进行混写

    import { foo,bar } form './a';
    export { foo as fn ,bar };
    //以上写法等同于
    export { foo as fn,bar } from './a'
    

    整体输出我们导入的模块采用以下写法

    export * from './a'
    //这里需要注意export * 命令会忽略a模块中的default方法
    

    默认接口写法如下

    export { default } from 'foo';
    

    跨模块常量

    我们都知道在ES6中规定了const声明的常量只在当前代码块优先,如果想设置跨模块常量,可以采用如下写法

    //a.js
    export const A=1;
    export const B=3;
    //b.js
    import { A,B } from './a'
    //或者
    import * as constants from './a'
    
    console.log(A)
    console.log(B)
    

    相关文章

      网友评论

        本文标题:8. Module语法

        本文链接:https://www.haomeiwen.com/subject/jlxtoxtx.html