在开发以及面试中,总是会遇到有关模块化相关的问题,始终不是很明白,不得要领,例如以下问题,回答起来也是模棱两可,希望通过这篇文章,能够让大家了解十之一二,首先抛出问题:
创新互联致力于网站建设,网站制作设计,营销网页按需策划,外贸网站建设,企业网站建设,微信小程序,网站SEO优化,网站设计制作案例丰富,是成都做网站公司和建站公司,欢迎咨询。
module.exports/exports
或者 export/export default
;require
奇怪的是也可以使用 import
??它们之间有何区别呢?于是有了菜鸟解惑的搜喽过程。。。。。。
模块化规范:即为 JavaScript 提供一种模块编写、模块依赖和模块运行的方案。
其实最原始的 JavaScript 文件加载方式,就是Script 标签,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在 window 对象中,不同模块的接口调用都是一个作用域中,一些复杂的框架,会使用命名空间的概念来组织这些模块的接口。
缺点:
默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到 标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。
如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。这显然是很不好的体验,所以浏览器 允许脚本异步加载
。
标签添加
defer
或 async
属性,脚本就会 异步加载 。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。
defer
:要等到整个页面在内存中正常渲染结束,才会执行;多个脚本时,按顺序执行
async
:一旦下载完,渲染引擎就会中断渲染,执行这个脚本再继续渲染。多个脚本时,不能保证按执行顺序
总结一句话: defer
是“ 渲染完再执行 ”, async
是“ 下载完就执行 ”。
require
方法 同步加载 所依赖的模块,通过 exports
或 module.exports
导出需要暴露的数据。在 Node.js 中,创建一个模块非常简单, 一个文件就是一个模块
- // module.js 模块
- var name = "Echoyya";
- // todo something...
- exports.name = name
使用require函数 加载模块(即被依赖模块的 module.exports对象)。
require('express')
和 require('./node_modules/express')
加载两次,也不会重复加载,尽管两次参数不同,解析到的文件却是同一个。exports.属性 = 值
exports.方法 = 函数
var exports = module.exports;
exports对象 是 module.exports对象的引用
,不能改变指向,只能添加属性和方法,若直接改变exports 的指向,等于切断了 exports 与 module.exports 的联系,返回空对象另外的用法:
- // singleobjct.js
- function Hello() {
- var name;
- this.setName = function (thyName) {
- name = thyName;
- };
- this.sayHello = function () {
- console.log('Hello ' + name);
- };
- }
- exports.Hello = Hello;
此时获取 Hello 对象 require('./singleobject').Hello
,略显冗余,可以用下面方法简化。
- // hello.js
- function Hello() {
- var name;
- this.setName = function(thyName) {
- name = thyName;
- };
- this.sayHello = function() {
- console.log('Hello ' + name);
- };
- }
- module.exports = Hello;
就可以直接获得这个对象:
- // gethello.js
- var Hello = require('./hello');
- hello = new Hello();
- hello.setName('Yu');
- hello.sayHello();
采用 异步方式
加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。 推崇依赖前置
require.js 是目前 AMD 规范最热门的一个实现
AMD 也采用 require
语句加载模块,但是不同于 CommonJS,它要求两个参数: require([module], callback);
[module]:是一个数组,成员就是要加载的模块
callback:加载成功之后的回调函数;
- require(['math'], function (math) {
- math.add(2, 3);
- });
模块必须采用 define()
函数来定义。
- // math.js
- define(function (){
- var add = function (x,y){
- return x+y;
- };
- return {
- add: add
- };
- });
- // test.js
- define(['myLib'], function(myLib){
- function foo(){
- myLib.doSomething();
- }
- return {
- foo : foo
- };
- });
- // main.js
- require(['math'], function (math){
- alert(math.add(1,1));
- });
理论上 require.js 加载的模块,必须是按照 AMD 规范 用 define() 函数定义的模块。但实际上,虽然已经有一部分流行的函数库(比如 jQuery )符合 AMD 规范,更多的库并不符合。那么require.js 如何能够加载非规范的模块呢?
这样的模块在用 require() 加载之前,要先用 require.config() 方法,定义它们的一些特征。
例如,underscore 和 backbone 这两个库,都没有采用 AMD 规范编写。如果要加载的话,必须先定义它们的特征。
- require.config({
- shim: {
- 'underscore': {
- exports: '_'
- },
- 'backbone': {
- deps: ['underscore', 'jquery'],
- exports: 'Backbone'
- }
- }
- });
require.config()接受一个配置对象,这个对象有一个 shim
属性,专门用来配置不兼容的模块。每个模块要定义:
exports :输出的变量名,表示这个模块外部调用时的名称;
deps: 数组,表示该模块的依赖性。
如jQuery 的插件还可以这样定义:
- shim: {
- 'jquery.scroll': {
- deps: ['jquery'],
- exports: 'jQuery.fn.scroll'
- }
- }
推崇依赖前置
,也就是提前执行(预执行),在模块使用之前就已经执行完毕。推崇就近依赖
。1. factory 是函数时有三个参数,function(require, exports, module):
- // 定义 a.js 模块
- define(function(require, exports, module) {
- var $ = require('jquery.js')
- exports.price= 200;
- });
- // b.js 加载模块
- const a = require('./a.js')
2. factory 为对象、字符串时,表示模块的接口就是该对象、字符串。比如可以定义一个 JSON 数据模块:
- define({"foo": "bar"});
3. 通过字符串模板定义模块:
- define('I am a template.My name is {{name}}.');
AMD 是 提前执行 ,CMD 是 延迟执行 。
AMD 是 依赖前置 ,CMD 是 依赖就近 。
- // AMD
- define(['./a', './b'], function(a, b) { // 在定义模块时 就要声明其依赖的模块
- a.doSomething()
- // ....
- b.doSomething()
- // ....
- })
- // CMD
- define(function(require, exports, module) {
- var a = require('./a')
- a.doSomething()
- // ...
- var b = require('./b') // 可以在用到某个模块时 再去require
- b.doSomething()
- // ...
- })
- (function (window, factory) {
- if (typeof exports === 'object') {
- module.exports = factory();
- } else if (typeof define === 'function' && define.amd) {
- define([],factory);
- } else {
- window.eventUtil = factory();
- }
- })(this, function () {
- return {};
- });
ES6 模块的设计思想,是尽量的静态化,使得 编译时
就能确定模块的依赖关系,以及输入和输出的变量。
ES6 中, import 引用模块,使用 export 导出模块。通过 babel 项目将还未被宿主环境(各浏览器、Node.js)直接支持的 ES6 模块 编译为 ES5 的 CommonJS。因此 Babel 实际上是将不被支持的 import/export 翻译成目前已被支持的 require/exports 。
- // 导入
- import Vue from 'vue'
- import App from './App'
- // 导出
- function v1() { ... }
- function v2() { ... }
- export {
- v1 as streamV1,
- v2 as streamV2,
- v2 as streamLatestVersion
- };
- export function multiply() {...};
- export var year = 2018;
- export default ...
CommonJS | AMD | CMD | ES6 | |
---|---|---|---|---|
引用模块 | require | require | require | import |
暴露接口 | module.exports || exports | define函数返回值 return | exports | export |
加载方式 | 运行时加载, 同步 加载 |
并行加载,提前执行, 异步 加载 |
并行加载,按需执行, 异步 加载 |
编译时加载, 异步 加载 |
实现模块规范 | NodeJS | RequireJS | SeaJS | 原生JS |
适用 | 服务器 | 浏览器 | 浏览器 | 服务器/浏览器 |
说了这么多,还是要回到文章一开始提到的问题,"require"与"import"两种引入模块方式,到底有神马区别,大致可以分为以下几个方面(可能总结的也不是很全面):
require/exports
的用法只有以下三种简单的写法:
- const fs = require('fs')
- exports.fs = fs
- module.exports = fs
import/export
的写法就多种多样:
- import fs from 'fs'
- import {default as fs} from 'fs'
- import * as fs from 'fs'
- import {readFile} from 'fs'
- import {readFile as read} from 'fs'
- import fs, {readFile} from 'fs'
- export default fs
- export const fs
- export function readFile
- export {readFile, read}
- export * from 'fs'
require
输入的变量,基本类型数据是赋值,引用类型为浅拷贝,可修改
import
输入的变量都是只读的,如果输入 a 是一个对象,允许改写对象属性。
- import {a} from './xxx.js'
- a = {}; // Syntax Error : 'a' is read-only;
- a.foo = 'hello'; // 合法操作
require
:不具有提升效果,到底加载哪一个模块,只有运行时才知道。
- const path = './' + fileName;
- const myModual = require(path);
import
:具有提升效果,会提升到整个模块的头部,首先执行。 import
的执行早于 foo
的调用。本质就是 import
命令是编译阶段执行的,在代码运行之前。
- foo();
- import { foo } from 'my_module';
import()
函数:ES2020提案引入,支持动态加载模块。 import()
函数接受一个参数,指定所要加载的模块的位置,参数格式同 import
命令,两者区别主要是 import()
为动态加载。可用于 按需加载
、 条件加载
、 动态的模块路径
等。
它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块,返回一个 Promise 对象。 import()
加载模块成功以后,该模块会作为一个对象,当作 then
方法的参数。可以使用对象解构赋值,获取输出接口。
- // 按需加载
- button.addEventListener('click', event => {
- import('./dialogBox.js')
- .then({export1, export2} => { // export1和export2都是dialogBox.js的输出接口,解构获得
- // do something...
- })
- .catch(error => {})
- });
- // 条件加载
- if (condition) {
- import('moduleA').then(...);
- } else {
- import('moduleB').then(...);
- }
- // 动态的模块路径
- import(f()).then(...); // 根据函数f的返回结果,加载不同的模块。
require
:很显然是可以使用表达式和变量的
- let a = require('./a.js')
- a.add()
- let b = require('./b.js')
- b.getSum()
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';
- }
而require/exports 和 import/export 本质上的区别,实际上也就是CommonJS规范与ES6模块化的区别
它们有三个重大差异。
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJS 模块的 require()
是 同步
加载模块,ES6 模块的 import
命令是 异步
加载,有一个独立的模块依赖的解析阶段。
导致第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成
CommonJS: 运行时加载
- // CommonJS模块
- let { stat, exists, readfile } = require('fs');
- // 等同于
- let _fs = require('fs');
- let stat = _fs.stat;
- let exists = _fs.exists;
- let readfile = _fs.readfile;
ES6: 编译时加载
或者静态加载
export
命令显式指定输出的代码,再通过 import
命令输入。import { stat, exists, readFile } from 'fs';
标题名称:一文彻底搞懂JS前端5大模块化规范及其区别
转载注明:http://www.shufengxianlan.com/qtweb/news0/239100.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联