webpack与模块化

目录

1.模块化

2.模块化的核心

3.ESM

3.1独立模块作用域

3.2导出模块内部数据

3.3导入外部模块数据

3.3.1静态导入

 3.3.2ESM导入导出——示例:

3.3.3动态导入import()

 4.模块化的向下兼容

5.CommonJS

5.1独立模块作用域

5.2导出模块内部数据

5.3导入外部模块数据

5.4CommonJS规范使用示例

6.AMD

7.AMD——requireJS

7.1独立模块作用域

7.2导出模块内部数据

7.3导入外部模块数据

7.4AMD——requireJS使用示例

8.requireJS 的CommonJS风格

8.1导出模块内部数据

8.2导入外部模块数据

8.3requireJS的CommonJS风格示例

9.UMD


1.模块化

模块化已经是现代前端开发中不可或缺的一部分了。也是后端必备。

把复杂的问题分解成相对独立的模块,这样的设计可以降低程序复杂性,提高代码的重用,也有利于团队协作开发与后期的维护和扩展。

ECMAScript2015 开始引入了模块的概念,我们称为:ECMAScript Module,简称:ESM。

2.模块化的核心

  • 独立的作用域——将代码进行有效的隔离,各模块之间代码不会相互影响
  • 如何导出模块内部数据——访问模块化的数据
  • 如果导入外部模块数据

3.ESM

ECMAScript2015/ECMAScript6 开始,JavaScript 原生引入了模块概念,而且现在主流浏览器也都有了很好的支持。

3.1独立模块作用域

一个文件就是模块,拥有独立的作用域,且导出的模块都自动处于 严格模式,即:'use strict'

如果该文件是通过模块化进行加载的,那么:

  1. 该文件会产生一个独立的作用域;
  2. 该文件内的代码默认是运行在严格模式下即:'use strict'的。

严格模式('use strict'):

  • 变量必须先声明才能使用;
  • 没有变量提升(预解析机制)。

3.2导出模块内部数据

使用 export 语句导出模块内部数据。

// 导出单个特性
export let name1, name2, …, nameN;
export let name1 = …, name2 = …, …, nameN;
export function FunctionName(){...}
export class ClassName {...}

// 导出列表
export { name1, name2, …, nameN };

// 重命名导出
export { variable1 as name1, variable2 as name2, …, nameN };

// 默认导出
export default expression;
export default function (…) { … }
export default function name1(…) { … }
export { name1 as default, … };

// 模块重定向导出
export * from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;

3.3导入外部模块数据

导入分为两种模式

  • 静态导入
  • 动态导入

3.3.1静态导入

在浏览器中,import 语句只能在声明了 type="module" 的 script 的标签中使用

且import语句必须写在JS文件最上面;

import defaultExport from "module-name";
import * as name from "module-name";
import { export } from "module-name";
import { export as alias } from "module-name";
import { export1 , export2 } from "module-name";
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";

静态导入方式不支持延迟加载,import 必须这模块的最开始

document.onclick = function () {

    // import 必须放置在当前模块最开始加载
    // import m1 from './m1.js'

    // console.log(m1);

}

 3.3.2ESM导入导出——示例:

项目路径:

index.html:注意ESM中使用模块化时,script标签中必须要有type="module"属性。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <!-- 在浏览器中,import 语句只能在声明了 type="module" 的 script 的标签中使用。 -->
    <script type="module" src="./js/main.js"></script>
</body>
</html>

main.js:

注意点:

  • 文件路径必须有'.js'结尾;
  • 导入列表时,所有的变量名必须和导出时的变量名一一对应,不想一一对象可使用别名;
  •  default导入导出时都不能加{};
  • 模块化重定向导出(即从另一个模块或脚本文件导出)时,from后表示从已经存在的模块、脚本文件…导出

//导入m1模块
//1.导出单个特性:因为不是默认导出,所以需要声明变量接收,且文件必须有.js结尾
import {cssFunc1,cssFunc2,cssFunc3,M1Class} from './m1.js';

// 2.导入列表::此处a,b,c必须和导出中的变量名一一对应
import {a,b,c} from './m1.js';

//3.重命名导出:导入导出的变量名需一一对象,想在导入时使用不同名字可使用别名, 变量名 as 别名
// import {name,pw} from './m1.js'
import {name as name1,pw as pw1} from './m1.js';

//4.默认导入:注意:default导入导出都不需要加{}
// import aa from './m1.js';
// import defaultFunc from "./m1.js"
// import defaultFunc2 from './m1.js';
import bb from './m1.js';

// 5.模块重定向导出
import * as obj from './m1.js'
import {v1,v2} from './m1.js';
import {value1,value2} from './m1.js';
import {default as defaultV} from './m1.js';


// 1.导出单个特性
let a1 = 10;
let m1Class = new M1Class();
m1Class.m1ClassFunc();

console.log("main.js",a1,cssFunc1,cssFunc2,cssFunc3);

//2.导入列表
console.log(a,b,c);//1 2 3

//3.重命名导出
// console.log(name,pw);//张三 1234
console.log(name1,pw1);//张三 1234

//4.默认导入
// defaultFunc();
// defaultFunc2();
// console.log(aa);
// console.log(bb);

//5.模块重定向导出
console.log(obj);//Module {…}
console.log(v1,v2);//2 3
console.log(value1,value2);//2 3
console.log(defaultV);//ƒ m2Func(){ console.log(v1+v2); }

m1.js:

console.log("m1模块...");

function css1(){
    console.log("m1模块下的css1方法");
}


//1.导出单个特性
export let cssFunc1 = css1;

export let cssFunc2 = function css2(){
    console.log("m1模块下的css2方法");
}

export function cssFunc3(){
    console.log("m1模块下的cssFunc3方法");
}

export class M1Class{
    constructor(){

    }
    m1ClassFunc(){
        console.log("m1模块下的m1ClassFunc");
    }
}

//2.导出列表
let a = 1,b=2,c=3;
export {a,b,c};

//3.重命名导出
let username = "张三";
let password = "1234";
export {username as name,password as pw};

//4.默认导出
let aa = 1;

// export default aa;
// export default function() { 
//     let defaultVal = 33;
//     console.log("defaultVal:"+defaultVal);
    
//  }
// export default function defaultFunc2() {
//     console.log("defaultFunc2方法");
// }
let bb = 2,cc = 3;
//不能同时导出多个。如export { bb as default,cc as default};
// export { bb as default};

// 5.模块重定向导出: from-从已经存在的模块、脚本文件…导出
export * from './m2.js';
export {v1,v2} from './m2.js';
export { v1 as value1, v2 as value2 } from './m2.js';
export { default } from './m2.js';

 m2.js:

let v1=2,v2=3;
export {v1,v2};
export default function m2Func(){
    console.log(v1+v2);
}

结果:

3.3.3动态导入import()

  1. 此外,还有一个类似函数的动态 import(),它不需要依赖 type="module" 的 script 标签。
  2. 关键字 import 可以像调用函数一样来动态的导入模块。以这种方式调用,将返回一个 promise
  3. 使用async await 异步延迟加载是,要使用default()方法,必须导出时导出的是函数
  • 方式一:使用promise对象的then方法
import('./m.js')
  .then(m => {
    //...
});
// 也支持 await
let m = await import('./m.js');

通过 import() 方法导入返回的数据会被包装在一个对象中,即使是 default 也是如此

示例:通过import()导出的是一个Promise对象。

不在页面初始化加载时就加载m3.js文件,而是当点击时加载。

m3.js:

let obj = {
    a:1,
    b:2
}

export default obj;

main.js:如果通过import语句导入

//6.动态导入:通过import()方法导入,返回一个promise对象进行异步延迟加载
document.onclick = function(){
    //直接通过import导入会报错
    // import obj from 'm3.js';
    // console.log(obj);
    import('./m3.js').then(obj=>{
        console.log(obj);
        
    });
}

 结果:

  • 方式二:使用async await进行延迟加载

 使用async await 异步延迟加载是,要使用default()方法,必须导出时导出的是函数

m3.js:

function css(){
    console.log("css");
}

export default css;

main.js:

//使用async  await进行异步延迟加载
document.onclick = async function(){
    let m1 = await import('./m3.js');
    console.log(m1);
    m1.default();
}

结果:

 

 4.模块化的向下兼容

  • CommonJS
  • AMD
  • UMD
  • ESM

无论是那种模块化规范,重点关注:

  • 独立模块作用域
  • 导出模块内部数据
  • 导入外部模块数据

5.CommonJS

在早起前端对于模块化并没有什么规范,反而是偏向服务端的应用有更强烈的需求,CommonJS 规范就是一套偏向服务端的模块化规范,NodeJS 就采用了这个规范

NodeJS和前端JS是同宗同源,NodeJS使用V8解析器,ECMAscript最为底层语言,NodeJS基于此延伸出了操作浏览器之外的如操作文件系统,网络,硬盘方法。

CommonJS 是后端模块化规范,通过操作文件系统进行实现,但是前端不能操作文件系统,所以前端使用不了CommonJS 规范。

5.1独立模块作用域

一个文件就是模块,拥有独立的作用域。

CommonJS是使用同步加载方法夹杂模块化文件。只有模块化加载成功后才会继续往下执行。

5.2导出模块内部数据

通过 module.exportsexports 对象导出模块内部数据。

注意: module.exportsexports 两种方式不能同时使用。

// a.js
let a = 1;
let b = 2;

module.exports = {
  x: a,
  y: b
}
// or
exports.x = a;
exports.y = b;

5.3导入外部模块数据

通过 require 函数导入外部模块数据

// b.js
let a = require('./a');
a.x;
a.y;

5.4CommonJS规范使用示例

通过nodeJS环境,使用nodemon main.js启动main.js。

m1.js:

let a = 1, b =2;
// module.exports = {
//     x:a,
//     y:b
// }

exports.l = a;
exports.m = b;

main.js:

let obj = require('./m1');
// console.log(obj);//{ x: 1, y: 2 }
console.log(obj);//{ l: 1, m: 2 }

6.AMD

因为 CommonJS 规范一些特性(基于文件系统,同步加载),它并不适用于浏览器端,所以另外定义了适用于浏览器端的规范

AMD(Asynchronous Module Definition)。AMD没有办法获取本地文件,因此使用异步文件加载方式实现模块化加载。

https://github.com/amdjs/amdjs-api/wiki/AMD

浏览器并没有具体实现该规范的代码,我们可以通过一些第三方库来解决。如requireJS。

7.AMD——requireJS

官网:https://requirejs.org/

必须在页面通过data-main指定入口文件;

// 1.html
<script data-main="scripts/main" src="https://cdn.bootcss.com/require.js/2.3.6/require.min.js"></script>

7.1独立模块作用域

通过一个 define 方法来定义一个模块,并通过该方法的第二个回调函数参数来产生独立作用域

// scripts/Cart.js
define(function() {
  // 模块内部代码
})

7.2导出模块内部数据

两种方式导出模块:

  • return方式;
  • 使用CommonJS风格进行导出

通过 return 导出模块内部数据:可以导出方法,对象等东西;

// scripts/Cart.js
define(function() {
  return class Cart {
    add(item) {
      console.log(`添加商品:${item}`)
    }
  }
})

7.3导入外部模块数据

通过前置依赖列表导入外部模块数据

// scripts/main.js
// 定义一个模块,并导入 ./Cart 模块
define(['./Cart'], function(Cart) {
  let cart = new Cart()
  cart.add({name: 'iphoneXX', price: 1000000})
})

7.4AMD——requireJS使用示例

  •  必须在页面通过data-main指定入口文件
  • 当使用data-main指定入口文件后,会动态创建一个script标签,然后将文件通过ajax的方式(网络请求方法)去加载需要加载的JS文件(main.js),因为页面必须运行在服务器环境下;
  • 通过define 方法来定义一个模块,并将模块代码进行隔离;
  • 导出模块:方式一:再通过 return 导出模块内部数据;方式二:CommonJS风格导出;

index.html:

必须使用data-main指定入口文件

<body>
    <script data-main="js/main.js" src="js/require.js"></script>
</body>
</html>

require.js:

由于官网加载很慢,将以下地址再网页上执行后,将网页上JS复制下来,生成require.js文件,放入指定目录

https://cdn.bootcss.com/require.js/2.3.6/require.min.js

main.js:

导入文件时,文件必须使用中括号[ ] 进行应用

//导入模块化文件时,通过方法的参数获取到模块中导出的数据
//注意导入时,文件名需要使用中括号
define(['./m1'],function(Cart){
    let cart = new Cart()
        return cart.add({name: 'iphoneXX', price: 1000000})
    }
);

 m1.js:

通过return方式导出模块化数据

define(function () {
    return class Cart {
        add(item) {
            console.log("m1模块");
            console.log(`添加商品:${item}`, item)
        }
    };
});

结果:

 

8.requireJS 的CommonJS风格

require.js 也支持 CommonJS 风格的语法

8.1导出模块内部数据

// scripts/Cart.js
define(['require', 'exports', 'module'], function(require, exports, module) {
  class Cart {
    add(item) {
      console.log(`添加商品:${item}`)
    }
  }
  exports.Cart = Cart;
})
// 忽略不需要的依赖导入
define(['exports'], function(exports) {
  class Cart {
    add(item) {
      console.log(`添加商品:${item}`)
    }
  }
  exports.Cart = Cart;
})
// 如果是依赖的导入为:require, exports, module,也可以省略依赖导入声明
define(function(require, exports, module) {
  class Cart {
    add(item) {
      console.log(`添加商品:${item}`)
    }
  }
  exports.Cart = Cart;
})

8.2导入外部模块数据

//CommonJS风格导入导出模块化数据
define(function(require) {
    //注意如果导出的是类,则需要将对象中的类解构出来才能用
    let {Cart} = require('./m2');
    let cart = new Cart();
    
    cart.add({name: 'iphoneXX', price: 1000000})
  })

8.3requireJS的CommonJS风格示例

m2.js:

// define(['require', 'exports', 'module'], function(require, exports, module) {
//     class Cart {
//       add(item) {
//             console.log("m2模块");
//             console.log(`添加商品:${item}`, item)
//       }
//     }
//     exports.Cart = Cart;
//   })

// // 忽略不需要的依赖导入
// define(['exports'], function(exports) {
//     class Cart {
//       add(item) {
//         console.log(`添加商品:${item}`,item)
//       }
//     }
//     exports.Cart = Cart;
//   })

  // 如果是依赖的导入为:require, exports, module,也可以省略依赖导入声明
  define(function(require, exports, module) {
    class Cart {
      add(item) {
        console.log(`添加商品:${item}`,item)
      }
    }
    exports.Cart = Cart;
  })

 main.js:

//CommonJS风格导入导出模块化数据
define(function(require) {
    //注意如果导出的是类,则需要将对象中的类解构出来才能用
    let {Cart} = require('./m2');
    let cart = new Cart();
    
    cart.add({name: 'iphoneXX', price: 1000000})
  })

9.UMD

严格来说,UMD 并不属于一套模块规范,它主要用来处理 CommonJSAMDCMD 的差异兼容,是模块代码能在前面不同的模块环境下都能正常运行

  • 判断如果module是object对象,且导出类型时obejct,就是在nodeJS 环境下导出;
  • define为function且define.amd为真,则再AMD下使用;
  • 判断完后,会执行函数自执行里的导出;
  • 通过判断就能在浏览器下执行AMD模块化导出,而再后端nodeJS下就能使用CommonJS规范进行模块化导出
(function (root, factory) {
  	if (typeof module === "object" && typeof module.exports === "object") {
        // Node, CommonJS-like
        module.exports = factory(require('jquery'));
    }
    else if (typeof define === "function" && define.amd) {
      	// AMD 模块环境下
        define(['jquery'], factory);
    }
}(this, function ($) { // $ 要导入的外部依赖模块
    $('div')
    // ...
    function b(){}
    function c(){}

    // 模块导出数据
    return {
        b: b,
        c: c
    }
}));
发布了95 篇原创文章 · 获赞 115 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_34569497/article/details/102579729