最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • ES6的学习笔记十四(Module的语法)

    正文概述 掘金(Get-sunshine)   2020-12-30   337

    Module

    1. 概述

    JavaScript一直没有模块体系,无法将一个大程序拆分成相互依赖的小文件,再使用简单的方法拼装起来。
    ES6之前,社区制定了一些模块加载方案,主要有CommonJS和AMD两种。CommonJS用于服务器,AMD用于浏览器。
    ES6在语言标准层面上实现了模块功能,可以取代CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。
    ES6模块化的设计思想:尽量静态化,使得编译时就能确定模块的依赖关系,以及输入输出的变量。
    CommonJS和AMD的设计思想:在运行时确定模块的依赖关系和输入输出的变量。

    // CommonJS模块其实就是对象,输入时必须查找对象属性。
    let {stat,exists,readfile} = require('fs');
    // 相当于
    let _fs = require('fs');
    let stat = _fs.stat;
    let exists = _fs.exists;
    let readfile = _fs.readfile;
    // 此处代码实质:整体加载fs模块(加载fs的所有方法),生成一个对象(_fs),然后从对象上面读取方法。
    // 这种加载称为运行时加载,只有运行时才能的到这个对象,导致完全没办法在编译时做静态优化。
    

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

    // ES6模块
    import {stat,exists,readFile} from 'fs';
    // 此处代码实质:从fs模块加载3个方法,其他方法不加载。这种加载称为编译时加载或者静态加载,即ES6可以在编译时就完成模块加载,效率要比CommonJS模块的加载方式高。但是,这也导致了没法引用ES6模块本身,因为它不是对象。
    

    2.严格模式

    ES6的模块自动采用严格模式,不管是否使用‘use strict’关键字。

    3.export命令

    模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
    一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果希望外部能够读取模块内部的变量,就必须使用export关键字输出该变量。

    // profile.js
    export var firstName = 'Nick';
    export var secondName = 'Tony';
    export var year = 1999;
    
    // 另一种写法,与前一种是等价的,应该优先考虑这种写法,这样很清晰的看出在脚本末尾输出哪些变量。
    var firstName = 'Tony';
    var secondName = 'Nick';
    var year = 1999;
    export {firstName,secondName,year};
    

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

    // 对外输出multiply函数。
    export function multiply(x,y) {
        return x*y
    }
    

    可以使用as关键字对输出的变量重命名。

    function f1(){}
    function f2(){}
    export {
        f1 as f3,
        f2 as f4,
        f2 as foo
    }
    // f2就可以使用不同的名字输出两次。
    

    export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

    // 报错
    export 1;
    // 报错
    var m =1;
    export m;
    // 两种写法都报错,因为没有提供对外的接口。两种写法都直接输出1。1只是一个值,不是接口
    // 写法一
    export var m = 1;
    // 写法二
    var m = 1;
    export {m};
    // 写法三
    var n = 1;
    export {n as m};
    
    // function和class的输出也遵守这样的规则。
    

    export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。而CommonJS模块输出的是值的缓存,不存在动态更新。

    export var foo ='bar'
    setTimeout(() => foo = 'baz',500)
    // 代码输出的是变量foo,值为bar,500毫秒后变成baz。
    

    export命令可以出现在模块的任何位置,只要是模块顶层就可以。import命令也是如此。因为处于条件代码块中,就没法做静态优化了(静态优化是什么呢?)

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

    4.import 命令

    export命令定义模块对外接口,import命令加载模块。
    import 命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,与被导入模块对外接口的名称相同。
    可以使用as重命名。

    // main.js 
    import {firstName,secondName,year} from './profile.js';
    
    import {secondName as newName} from './profile.js';
    

    import命令输入的变量都是只读的,因为它的本质是输入接口,故,不允许在加载模块的脚本里改写接口。

    import {a} from './profile.js'
    a = {} // 报错
    

    但如果a是一个对象的话,改写a的属性是允许的。
    但是这样的写法很难查错,建议输入的变量,都当做完全只读,不要轻易改变它的属性。

    import {a} from './profile.js'
    a.name = 'hello'
    console.log(a.name)
    

    from关键字后指定模块文件位置,可以是相对路径,可以是绝对路径。若不带路径,只是一个模块名,那么必须有配置文件,告诉JS引擎该模块的位置。
    import命令具有提升效果,会提升到整个模块的头部,首先执行。

    console.log(a)
    import {a} from './profile.js'
    // 代码不会报错,import的执行会早于a。
    // 本质:import命令是编译阶段执行的,在代码运行之前。
    

    import是静态执行(静态执行是什么呢?),所以不能使用表达式和变量。(即只有在运行时才能得到结果的语法结构。)

    // 报错 使用了表达式
    import { 'f' + 'oo' } from 'my_module';
    
    // 报错 使用了变量
    let module = 'my_module';
    import { foo } from module;
    
    // 报错 使用了if语句
    if (x === 1) {
      import { foo } from 'module1';
    } else {
      import { foo } from 'module2';
    }
    

    import语句会执行所加载的模块。

    import 'lodash'; // 仅仅执行lodash模块,但是不输入任何值。
    
    // 多次重复执行同一句import语句,只会执行一次。
    import 'lodash';
    import 'lodash';
    
    // import语句是Singleton模式。
    import {foo} from 'my_module';
    import {bar} from 'my_module';
    // 等同于
    import {foo,bar} from 'my_module';
    // foo和bar是在两个语句中加载,但是它们对应的是同一个my_module模块。
    

    5.模块的整体加载

    除了指定加载某个输出,也可以使用星号*,指定一个对象,使所有输出值都加载在这个对象上面。(整体加载。)

    // circle.js  输出
    export function area(radius){
    	return Math.PI*radius*radius;
    }
    export function circumference(radius){
    	return 2*Math.PI*radius;	
    }
    // main.js 引入(逐一指定要加载的方法。)
    import {area,circumference} from './circle.js';
    // 整体加载的写法
    import * as circle from './circle.js';
    // 注意:模块整体加载所在的那个对象,是可以静态分析的,所以不允许运行时改变。
    circle.foo = {}; // 报错:object is not extensible
    circle.area = {}; // 报错:circle.area is read only
    

    6.export default 命令

    从前面的代码可以看出,使用import命令的时候,用户需要知道所加载的变量名或者函数名,否则是无法加载的。
    为了提供方便,让用户不用了解变量名或者函数名。就可以使用export default 命令,为模块指定默认的输出(也就是没有名字的输出??)。

    // default.js
    export default function (){
    	console.log('function');
    }
    // 模块默认输出的是一个函数。
    

    其他模块加载该模块时,import命令可以为此匿名函数指定任意名字。且这时的import命令后面不需要跟大括号。

    // main.js
    import aName from './default.js';
    aName(); // function
    

    export default命令可以用在非匿名函数前。此时函数的名字在模块外是无效的,加载的时候,与匿名函数一样。

    // default.js
    export default function foo() { 
        console.log("object")
    }
    // 或者写成
    function foo(){
    	console.log("object")
    }
    export default foo 
    

    export default命令用于指定模块的默认输出,一个模块只能有一个默认输出,export default命令只能使用一次。所以,import命令后才不用加大括号,因为只可能唯一对应export default命令。
    本质上,export default命令就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。

    // module.js
    function mul(x, y) {
        return x * y
    }
    export { mul as default }
    // 等同于
    export default mul
    
    // main.js
    import {default as foo} from './profile.js'
    // 等同于
    import foo from './profile.js'
    

    因为export default命令输出的是一个叫做default的变量,所以它后面不能跟变量声明语句。

    // ok
    export var a = 'hello'
    // ok 
    var a = 'hello'
    export a
    
    // no
    export default var a ='hello'
    // export default a 的含义是将变量a的值赋给变量default,所以最后一句代码会报错。
    

    也可以直接将一个值写在export default后面。

    // ok
    export default 222;
    // error
    export 23;
    

    可以在一条import语句中同时输入默认方法和其他接口。

    import def,{each,forEach} from './profile.js'
    // 对应的export模块
    export default function (){}
    export function each (){}
    export {each as forEach} // 暴露出forEach接口,默认指向each接口,故forEach和each指向同一个方法。
    

    export default可以用于输出类。

    export default class{
        constructor(x,y){
            this.x = x
            this.y = y
        }
        pri(){
            console.log(this.x,this.x)
        }
    }
    

    7.export和import的复合写法

    如果在一个模块之中,先输入后输出同一个模块,import语句可以与export写在一起。

    export {foo,bar} from './profile.js'
    // 可以理解为
    import {foo,bar} from './profile.js'
    export {foo,bar}
    

    export和import语句结合在一起,写在一行。但是foo和bar实际上并没有被导入到当前模块中,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar。
    模块的接口改名和整体输出,也可以使用这种写法。

    // 接口改名
    export {foo as myFoo} from './profile.js'
    // 整体输出
    export * from './profile.js'
    // 默认接口的写法
    export {default} from './profile.js'
    
    // 具名接口改为默认接口的写法
    export {es6 as default} from './profile.js'
    // 等同于
    import {es6} from './profile.js'
    export default es6
    
    // 同样,默认接口也可改为具名接口。
    export {default as es6} from './profile.js'
    

    在ES2020之前,有一种import语句,没有对应的复合写法。

    import * as someIdentifier from './profile.js'
    
    // 在ES2020之后补上了这个写法。
    export * as someIdentifier from './profile.js'
    // 即
    import * as someIdentifier from './profile.js'
    export {someIdentifier}
    

    8.模块的继承

    模块之间可以继承。 circleplus模块继承circle模块。

    // circleplus.js
    export * from './circle.js'
    export var e = 2.71828182846
    export default function (x){
        return Math.exp(x)
    }
    // export *:输出circle模块的所有属性和方法。此命令忽略circle模块的default方法。
    // 之后,输出了e变量和默认方法。
    
    // 可以将circle的属性和方法改名后再输出。
    export {area as circleArea} from './circle.js'
    
    // 加载circleplus.js模块的写法。
    import * as math from './circleplus.js'
    import exp from 'circleplus.js' // 将circleplus模块的默认方法加载为exp方法
    console.log(exp(math.e))
    

    9.跨模块常量

    const声明的常量只在当前代码块有效,如果想设置跨模块(跨多个文件)的常量,或者说一个值被多个模块共享,采用以下的写法。

    // constants.js
    export const A = 1
    export const B = 3
    export const C = 4
    // test1.js
    import * as constants from './constants.js'
    console.log(constants.A)
    console.log(constants.B)
    console.log(constants.C)
    // test2.js
    import {A,B} from './constants.js'
    console.log(A)
    console.log(B)
    

    建立一个专门的constants目录,将各种常量存放在不同的文件里面,保存在这个目录下面。

    // constants/db.js
    export const db = {
    	url:'XXX',
        admin_username:'admin',
        admin_password:'password'
    }
    // constants/users.js
    export const users = ['root','admin','staff','coo','ceo','chief']
    
    // 将这些文件输出的常量合并在index.js里面
    export {db} from './db.js'
    export {users} from './users.js'
    
    // 使用的时候,直接从index.js中取
    import {db,users} from './constants/index.js'
    

    10.import()

    简介

    import命令会被JS引擎静态解析,先于模块内的其他语句执行。 下面的代码会报错:

    // 报错:Unexpected identifier
    if(x === 2 ){
    	import A from './circle.js'
    }
    // 引擎处理import语句是在编译时,不会去分析或执行if语句,故import语句放在if代码块中毫无意义,会报句法错误,而不是执行时错误。
    // 故,import和export语句只能在模块顶层,不能在代码块之中。(例如:if语句,函数)
    // 这样的设计,有利于编译器提高效率,但无法导致在运行时加载模块。故,条件加载就无法实现。
    // 因此,import命令无法取代require的动态加载功能。(require是运行时加载模块。)
    // const path ='./'+fileName
    // const myModule = require(path)
    

    ES2020引入了import()函数,支持动态加载模块。

    import(specifier)
    
    // specifier:指定要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,主要是后者为动态加载。
    // import()函数返回一个Promise对象。
    // 原例子
    const main = document.querySelector('main');
    
    import(`./section-modules/${someVariable}.js`)
      .then(module => {
        module.loadPageInto(main);
      })
      .catch(err => {
        main.textContent = err.message;
      });
      // import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。是运行时执行。
      // import()函数与所加载的模块没有静态连接关系,这点与import语句不同。
      // import()函数类似Node的require方法,但前者是异步加载,后者是同步加载。
    

    适用场合

    1.按需加载

    在需要的时候,再加载某个模块。

    button.addEventListener('click', event => {
      import('./dialogBox.js')
      .then(dialogBox => {
        dialogBox.open();
      })
      .catch(error => {
        /* Error handling */
      })
    });
    // import()方法放在click事件监听函数中,点击了按钮之后,才会加载。
    

    2.条件加载

    放在if代码块中,根据不同的情况,加载不同的模块。

    if(condition){
    	import('moduleA').then(...);
    }else{
    	import('moduleB').then(...);
    }
    // 满足条件,加载模块A,否则加载模块B
    

    3.动态的模块路径

    import允许模块路径动态生成。

    import(f()).then(); // 根据函数f的返回结果,加载不同的模块。
    

    注意点

    import()加载模块成功以后,此模块作为一个对象,作为then方法的参数。
    使用对象解构赋值的语法,获取输出接口。

    import('./myModule.js').then(({export1,export2}) = > {// dosomething})
    // 如果对象有default输出接口,可以使用参数直接获得。
    import('./myModule.js').then((myDefault) => {// dosomething})
    // 可以使用具名输入的形式。
    import('./myModule.js').then(({default:theDefault}) => {//dosomething})
    

    可以同时加载多个模块。

    Promise.all([import('./module1.js'),import('./module2.js'),import('./module3.js')])
    .then(([module1,module2,module3]) => {});
    

    import用在async函数中。

    async function main() {
      const myModule = await import('./myModule.js');
      const {export1, export2} = await import('./myModule.js');
      const [module1, module2, module3] =
        await Promise.all([
          import('./module1.js'),
          import('./module2.js'),
          import('./module3.js'),
        ]);
    }
    main();
    

    起源地下载网 » ES6的学习笔记十四(Module的语法)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元