es6模块

模块化主要是帮助我们更好的组织代码, 模块允许我们将相关的变量和函数放在一个模块中。 在ES6模块化之前,JS语言并没有模块的概念,只有函数作用域和全局作用域非常容易发生命名冲突。 之前的RequireJS, SeaJS, AMD, UMD,CMD在一定层面上都是为了解决JS模块化的问题。ES6模块取其精华:

  • 它提供了简洁的语法
  • 以及异步的, 可配置的模块加载

什么是模块

模块是自动运行在严格模式下并且没有办法退出运行的JavaScript代码

  1. 在模块的顶部this的值是undefined
    2.其模块不支持html风格的代码注释
  2. 除非用default关键字,否则不能用这个语法导出匿名函数或类

任何未限制导出的变量、函数或类都是模块私有的,无法从模块外部访问

为什么要使用模块

目前最普遍的JS运行平台便是浏览器,在浏览器中,所有的代码都运行在同一个全局上下文中, 这使得你即使更改应用中的很小一部分, 你也要担心可能会产生的命名冲突。

传统的JS应用被分离在多个文件夹中,并且在构建的时候连接在一起,这稍显笨重。所以人们开始将每个文件内的代码都包在一个自执行函数中: (function(){ ... })(); 。 这种方法创建了一个本地作用域,于是最初的模块化的概念产生了, 之后的CommonJS和AMD系统中所称的模块, 也是由此实现的。

创建模块

一个JS模块就是一个对其他模块暴露一些内部的属性、方法的文件。 这里仅讨论浏览器中的ES2016模块系统。

每个模块都有自己的上下文

和传统的JS不同,在使用模块时,你不必担心污染全局作用域。恰恰相反,你需要把所有你需要用到的东西从其他模块中导入进来,这样会使得模块之间的依赖关系更为清晰

导入导出

可以使用ES6的新关键字 importexports 来导入或导出模块中的对象。 模块可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值等。

默认导出

每一个模块都支持导出一个不具名的变量,这为默认导出:

// helloWord.js
export default function(){
    console.log('111');
}

// main.js
import hello from './helloWord';
import anotherHello from './helloWord';

hello(); // 111
anotherHello(); //111
console.log(hello === anotherHello); //true

等价的CommonJS语法:

// helloWord.js
module.exports = function(){
    console.log('111');
}

//main.js
var hello = require('./helloWord');
var anotherHello = require('./helloWord');

hello(); // 111
anotherHello(); //111
console.log(hello === anotherHello); //true

任何JS值都是可以被默认导出的:

// helloWord.js
export default 3.14
//export default { foo: 'bar' };
//export default 'hello word';

// main.js
import hello from './helloWord';

console.log(hello); // 3.14

这里如果把注释放开,同样输出3.14, 当有多条export default语句,只会输出第一条export default的值

// helloWord.js
export default 3.14
export default { foo: 'bar' };
export default 'hello word';

// main.js
import hello from './helloWord';

console.log(hello); // 3.14
具名导入

除了默认导出外, ES6的模块系统还支持导出任意数量个具名的变量:

const PI = 3.14
const value = 42;
export function hello(){
    console.log('2111');
}
export {PI,value}

// 等同于CommonJS语法

// var PI = 3.14;
// var value = 42;
// module.exports.hello = function(){
//     console.log('2111');
// }
// module.exports.PI = PI;
// module.exports.value = value;

导入的时候:

import {PI,value,hello} from './helloWord';

console.log(PI,value,hello());
image.png

导入的时候可以使用as关键字来重命名导入的变量:

import {PI as PI2,value as val,hello as helloWord} from './helloWord';

console.log(PI2,val,helloWord());

结果是一样的

导入所有

最简单的,在一条命令中导入一个模块中所有变量的方法, 是使用*标记。 这样一来,被导入模块中所有导出的变量都会变成它的属性, 默认导出的变量则会被置于default属性中。

// helloWord.js
const PI = 3.14
const value = 42;
export function hello(){
    console.log('2111');
}
export {PI,value}

// main.js
import * as Hello from './helloWord';

console.log(Hello);
image.png

注意一点
import * as foo fromimport foo from的区别。 后者仅仅会导入默认导出的变量,而前者则会在一个对象中导入所有,如:

// helloWord.js
const PI = 3.14
const value = 42;
const foo = {
    'a':'123'
}
export function hello(){
    console.log('2111');
}
export {PI,value,foo}

// main.js
import * as foo from './helloWord';

console.log(foo);
console.log(foo.foo);
image.png

对比四种模块价值规范

  1. CommonJS
  2. AMD
  3. CMD
  4. ES6模块
1. CommonJS

commonJS是服务器端的模块化规范, node.js 就是参照commonJS规范实现的。commonJS中有一个全局的方法 require()用来加载模块

function myModule(){
  this.hello = function(){
    return "hello"
  }
  this.goodbye = function(){
  return "goodbye"
  }
 }
module.exports = myModule

其实module变量代表当前模块
这样就可以在其他模块中使用这个模块

var myModule = require('myModule');

var myModuleInstance = new myModule();
myModuleInstance.hello();
myModuleInstance.goodbye();

关于commonJS的更多,见CommonJS规范

2. AMD

commonJS定义模块的方式和引入模块的方式还是比较简单的,但不适合浏览器端, 因为commonJS是同步加载的。 而AMD是异步加载的,模块的加载不影响它后面语句的运行。 所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。 这个用require.js实现AMD规范的模块化, 用require.config()指定引用路径等,
通过define()来定义模块, 用requier加载模块

首先我们需要引入require.js文件和一个入口文件main.js. main.js中配置require.config()并规定项目中用到的基础模块。

/** 网页中引入require.js及main.js **/


/** main.js 入口文件/主模块 **/
// 首先用config()指定各模块路径和引用名
require.config({
  baseUrl: "js/lib",
  paths: {
    "jquery": "jquery.min",  //实际路径为js/lib/jquery.min.js
    "underscore": "underscore.min",
  }
});
// 执行基本操作
require(["jquery","underscore"],function($,_){
  // some code here
});

引用模块的时候,我们将模块名放在[]中作为 require() 的第一参数; 如果我们定义的模块本身也依赖其他模块, 那就需要将他们放在[]中作为define的第一参数

// 定义math.js模块
define(function () {
    var basicNum = 0;
    var add = function (x, y) {
        return x + y;
    };
    return {
        add: add,
        basicNum :basicNum
    };
});
// 定义一个依赖underscore.js的模块
define(['underscore'],function(_){
  var classify = function(list){
    _.countBy(list,function(num){
      return num > 30 ? 'old' : 'young';
    })
  };
  return {
    classify :classify
  };
})

// 引用模块,将模块放在[]内
require(['jquery', 'math'],function($, math){
  var sum = math.add(10,20);
  $("#sum").html(sum);
});

define的第一个参数是依赖的模块, 必须是一个数组。 通过return来暴露接口

通过 require() 来加载模块, 模块的名字默认为模块加载器请求的指定脚本的名字

require(['main'],function(main){
    alert(main.foo());
})

require.js就是根据AMD规范来实现的, 优点是:

  1. 实现js文件的异步加载, 避免网页失去响应
  2. 管理模块之间的依赖性,便于代码的编写和维护
3. CMD

CMD也是异步模块定义
CMD与AMD的区别:
CMD相当于按需加载, 定义一个模块的时候不需要立即制定依赖模块,在需要的时候require就可以了,比较方便。
而AMD则相反,定义模块的时候需要制定依赖模块, 并以形参方式引入回调函数中。

// CMD 按需加载
        define(function(require,exports,module){
            var a = require('./helloWord');
            a.hello(); 

            var b = require('./counter');
            console.log(b.foo); 
            // 2111
            // aaa
        })

        // AMD 定义模块的时候需要制定依赖
        define(['./helloWord','./counter'],function(a,b){
            a.hello();
            console.log(b.foo);
        });
image.png
ES6

ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单, 完全可以取代现有的CommonJS和AMD规范, 成为浏览器和服务器通用的模块解决方案。

ES6模块主要有两个功能: exportimport

export用于对外输出本模块(一个文件可以理解为一个模块)变量的接口

import用于在一个模块中加载另一个含有exoport接口的模块

参考:
https://segmentfault.com/a/1190000010058955
https://juejin.im/post/5aaa37c8f265da23945f365c

https://segmentfault.com/a/1190000004100661
http://es6.ruanyifeng.com/#docs/module

深入系列:
https://github.com/mqyqingfeng/Blog/issues/108
https://zhuanlan.zhihu.com/p/33843378?group_id=947910338939686912

面试题

题目一:ES6与commonJS模块的差异

1. commonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用。

  • commonJS模块一旦输出一个值,模块内部的变化就影响不到这个值。
  • ES6模块如果使用import从一个模块加载变量,那些变量不会被缓存,而是成为一个指向被加载模块的引用,原始值变了,import加载的值也会跟着变。需要开发者自己保证,真正取值的时候能够取到值。

**2. commonJS 模块是运行时加载, ES6模块是编辑时输出接口

  • 运行时加载: commonJS模块就是对象,即在输入时是加载整个模块,生成一个对象,然后再从整个对象上读取方法,这种加载称为”运行时加载“。 commonJS脚本代码在require的时候,就会全部执行。一旦出现某个模板被”循环加载“,就只能输出已经执行的部分,还未执行的部分不会输出。

  • 编译时加载: ES6模块不是对象,而是通过export命令显式指定输出的代码, import时指定加载某个输出值,而不是加载整个模块,这种加载称为”编译时加载“

你可能感兴趣的:(es6模块)