美文网首页
1.js模块化

1.js模块化

作者: zwj2024 | 来源:发表于2018-03-05 11:45 被阅读37次

1.1.export、exports、modules.exports 和 require 、import 的一些组合套路和坑:

引入: require / import
导出:export / module.exports / exports

require 用来加载代码,而 exports 和 module.exports 则用来导出代码

1.1.1.nodejs中exports 和 module.exports 的区别

1.module.exports 初始值为一个空对象 {}
2.exports 是指向的 module.exports 的引用
3.require() 返回的是 module.exports 而不是 exports

实例解释上述区别:

test.js:

var a = {name: 1}; 
var b = a;
console.log(a); 
console.log(b);

b.name= 2; 
console.log(a); 
console.log(b);

var b = {name: 3}; 
console.log(a); 
console.log(b);

运行 test.js 结果为:
{ name: 1 } 
{ name: 1 } 
{ name: 2 } 
{ name: 2 } 
{ name: 2 } 
{ name: 3 }

解释
a 是一个对象,b 是对 a 的引用,即 a 和 b 指向同一块内存,所以前两个输出一样。
当对 b 作修改时,即 a 和 b 指向同一块内存地址的内容发生了改变,所以 a 也会体现出来,所以第三四个输出一样。
当 b 被覆盖时,b 指向了一块新的内存,a 还是指向原来的内存,所以最后两个输出不一样。

综上所述
其实,Module.exports才是真正的接口,exports只不过是它的一个辅助工具。最终返回给调用的是Module.exports而不是exports。

所有的exports设置的属性和方法,都赋值给了Module.exports。当然前提就是Module.exports本身不具备任何属性和方法。如果Module.exports已经具备一些属性和方法,那么exports收集来的信息将被忽略。
如果你没有显式的给Module.exports设置任何属性和方法,那么你的模块就是exports设置给Module.exports的属性。

Module.exports模块可以是任何合法的javascript对象:boolean, number, date, JSON, string, function, array等等。
如果你想你的模块是一个特定的类型就用Module.exports。如果你想的模块是一个典型的“实例化对象”就用exports。推荐使用exports导出,除非你打算从原来的“实例化对象”改变成一个类型。

1.1.2.ES6:export default 和 export 区别

1.在一个文件或模块中,export、import可以有多个,export default仅有一个。
2.通过export方式导出,在导入时只能以真实的函数名或者类名导出,且要加{ };export default方式导出,在import导入时],可以给模块起任何变量名,且不需要用大括号包含。
1.export
//a.js
export const str = "blablabla~";
export function log(sth) { 
  return sth;
}
对应的导入方式:

//b.js
import { str, log } from 'a'; //也可以分开写两次,导入的时候带花括号

2.export default
//a.js
const str = "blablabla~";
export default str;
对应的导入方式:

//b.js
import str from 'a'; //导入的时候没有花括号

3.重命名
//a.js
let sex = "boy";
export default sex(sex不能加大括号)

// b.js
import any from "./a.js"
import any12 from "./a.js" 
console.log(any,any12)   // boy,boy

1.1.3.import与require区别
Nodejs 不支持 import 和 export,导入用require, 导出用module.exports (ps: 不知从什么时候开始,es6居然已经支持module.exports了。)

import与export是es6的语法,他们打包的时候都会经过babel转码成require、module.exports

尽管es6兼容以上所有,但需要注意:
在webpack打包的时候,可以在js文件中混用 require 和 export。但是不能混用 import 以及 module.exports所以统一改成 ES6 的方式编写: import 和 export

1.1.4.es6 : import { ... } from '...' 实例

lib.js:

// 
export const a = () => 123
export const b = () => 456
export const c = () => 789

__________________________________________________________

// 
module.exports = {
    a: () => 123,
    b: () => 456,
    c: () => 789,
}

__________________________________________________________

// export 对象导出
//请注意,这里的 { a, b, c } 并不是es6 对 key: value 形式的缩写
//而是只能以这种方式写
const a = () => 123
const b = () => 456
const c = () => 789
export { a, b, c }

__________________________________________________________

main.js:
import { a, b, c } from './lib.js'
console.log(a()) // => 123
console.log(b()) // => 345
console.log(c()) // => 678

1.2.es6 模块

1.2.1.模块概要
将一个大程序拆分成依赖的小文件,再拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至 CSS 都有@import。

在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。

ES6 在语言标准的层面上,实现了模块功能,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块方案。

ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系(编译时加载),以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西(运行时加载)。

不是说js不需要编译吗,那‘ES6模块是编译时加载’是什么意思
JS的“编译”可能指三种流程:
transform:把ES6+的JS转译成es5或以下引擎能运行的代码。
bundle:合并依赖的模块。
uglify:压缩丑化,减小体积。
ES6模块是编译时加载:模块之间的依赖关系,在运行之前(即编译时)就通过静态分析来确定好了的。所以有时候说编译时可以做“静态优化”,运行时加载因为只有运行时才能得到这个对象,导致完全没办法做“静态优化”。

CommonJS 和 AMD 模块与es6 模块区别
CommonJS 模块运行时加载。let _fs = require('fs');整体加载CommonJS 模块(即加载模块的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。即CommonJS 模块就是对象。
ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。import { stat, exists, readFile } from 'fs';ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。还有成为浏览器和服务器通用的模块解决方案(将来)。

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
-变量必须声明后再使用
-禁止this指向全局对象
尤其需要注意this的限制。ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。

1.2.2.export和import
模块功能主要由两个命令构成:export和import。
export命令用于输出模块的对外接口,import命令用于输入其他模块来提供的功能。

export命令规定的是对外的接口格式:

// 报错
var m = 1;
export m;

// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};

// 报错
function f() {}
export f;

// 正确
export function f() {};

// 正确
function f() {}
export {f};

最后,export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,import命令也是如此。export语句放在函数之中,条件代码块之中,结果报错。

import命令接受一对大括号,里面指定从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块对外接口的名称相同。import { lastName as surname } from './profile.js';如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。

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

import {a} from './xxx.js'

a = {}; // Syntax Error : 'a' is read-only;

但是,如果a是一个对象,改写a的属性是允许的。a的属性可以成功改写,并且其他模块也可以读到改写后的值。但一般都当作完全只读,轻易不要改变它的属性。

import {a} from './xxx.js'

a.foo = 'hello'; // 合法操作

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

foo();

import { foo } from 'my_module';
//import命令是编译阶段执行的,在代码运行之前。

由于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语句,那么只会执行一次

import { foo } from 'my_module';
import { bar } from 'my_module';

1.2.3.export default 命令
export default命令,为模块指定默认输出。其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。需要注意的是,这时import命令后面,不使用大括号。

export default function foo() {
  console.log('foo');
}

// 或者写成

function foo() {
  console.log('foo');
}

export default foo;
上面代码中,foo函数的函数名foo,在模块外部是无效的。加载的时候,视同匿名函数加载。

export与export default区别

// 第一组
export default function crc32() { 

}
import crc32 from 'crc32'; 

// 第二组
export function crc32() {

};

import {crc32} from 'crc32'; 

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

function add(x, y) {
  return x * y;
}
export {add as default};
import { default as foo } from 'modules';

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

// 正确
export var a = 1;

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

// 错误
export default var a = 1;
//export default a的含义是将变量a的值赋给变量default。

// 正确
export default 42;

// 报错
export 42;

如果想在一条import语句中,同时输入默认方法和其他接口

import _, { each, each as forEach } from 'lodash';

export default function (obj) {
 
}
export function each(obj, iterator, context) {
 
}
export { each as forEach };

export default也可以用来输出类。

// MyClass.js
export default class { ... }

// main.js
import MyClass from 'MyClass';
let o = new MyClass();

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

export { foo, bar } from 'my_module';

// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };

foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,当前模块不能直接使用foo和bar。

// 接口改名
export { foo as myFoo } from 'my_module';

// 整体输出
export * from 'my_module';

//默认接口
export { default } from 'foo';

1.2.5.模块的继承

// circleplus.js

export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}

// main.js

import * as math from 'circleplus';
import exp from 'circleplus';
console.log(exp(math.e));

export * 命令会忽略circle模块的default方法。
import exp表示,将circleplus模块的默认方法加载为exp方法。

1.2.6.常量跨模块
const声明的常量只在当前代码块有效。
一个常量也可以被多个模块共享。如:

// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;

// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3

// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3

如果要使用的常量非常多,可以建一个专门的constants目录,将各种常量写在不同的文件里面,保存在该目录下。

// constants/db.js
export const db = {
  url: 'http://my.couchdbserver.local:5984',
  admin_username: 'admin',
  admin_password: 'admin password'
};

// constants/user.js
export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];

然后,将这些文件输出的常量,合并在index.js里面。

// constants/index.js
export {db} from './db';
export {users} from './users';

使用的时候,直接加载index.js就可以了。

// script.js
import {db, users} from './index';

相关文章

网友评论

      本文标题:1.js模块化

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