前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端模块化

前端模块化

作者头像
前端小tips
发布2021-12-10 10:54:31
4810
发布2021-12-10 10:54:31
举报
文章被收录于专栏:前端文章小tips

js本身的问题: 不具有模块化的语法规则,在语言层面没有命名空间。 JavaScript 编程过程中很多时候,我们都在修改变量,在一个复杂的项目开发过程中,如何管理函数和变量作用域,显得尤为重要。

代码语言:javascript
复制
  function m1(){
    //...
  }
  function m2(){
    //...
  }
  1. 通用模块将所有函数方法暴露给全局作用域,造成命名冲突。
  2. 多个script标签在解析过程中,按照从上到下的顺序解析,如果有依赖规则,必须按照执行顺序,被依赖者先执行,依赖者后执行。

模块化的作用:

  1. 避免命名冲突
  2. 依赖管理
  3. 提供可维护和可复用的代码
  4. 对象写法:函数m1()和m2(),都封装在module1对象里。使用的时候,就是调用这个对象的属性。module1.m1();但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。module1._count = 5;
代码语言:javascript
复制
  var module1 = new Object({
    _count : 0,
    m1 : function (){
      //...
    },
    m2 : function (){
      //...
    }
  });
  1. 命名空间:顶级的应用命名空间被挂载到唯一一个全局对象上的对象(在浏览器中是 window,在 Node.js 应用中是 global)
代码语言:javascript
复制
var MYNAMESPACE = MYNAMESPACE || {};
​
MYNAMESPACE.person = function(name) {
    this.name = name;
};
​
MYNAMESPACE.person.prototype.getName = function() {
    return this.name;
};
​
// 使用方法
var p = new MYNAMESPACE.person("doc");
p.getName();
​
// 嵌套的命名空间
var myMasterNS = myMasterNS || {};
myMasterNS.mySubNS = myMasterNS.mySubNS || {};
myMasterNS.mySubNS.someFunction = function(){
    //插入逻辑 
};
  1. 闭包:匿名自执行函数,外部代码无法直接改变内部计数器的值。module1._count = 5
代码语言:javascript
复制
  var module1 = (function(){
    var _count = 0;
    var m1 = function(){
      //...
    };
    var m2 = function(){
      //...
    };
    return {
      m1 : m1,
      m2 : m2
    };
  })();
  1. 继承模块:
代码语言:javascript
复制
  var module1 = (function (mod){
    mod.m3 = function () {
      //...
    };
    return mod;
  })(module1);
​
// 模块可能为空
  var module1 = ( function (mod){
    //...
    return mod;
  })(window.module1 || {});

解决方案

模块化 CJS、AMD、CMD、UMD、ESM

统一模块规范

commonjs:

代码语言:javascript
复制
var MySalute = "Hello";
module.exports = MySalute;
​
// world.js
var MySalute = require("./salute");
var Result = MySalute + " world!";
console.log(Result);
  1. node环境 CommonJS规范

所有代码都运行在模块作用域,不会污染全局作用域。 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。 模块加载的顺序,按照其在代码中出现的顺序。

代码语言:javascript
复制
var math = require('math');
​
var math = require('math');
math.add(2,3); // 5
  1. 浏览器环境 Uncaught ReferenceError: require is not defined 浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量: module exports require global 浏览器加载 CommonJS 模块的原理与实现 browserify

第二行math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成。也就是说, 如果加载时间很长,整个应用就会停在那里等。这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。

AMD:

以浏览器第一的原则发展,异步加载模块。主要有两个Javascript库实现了AMD规范:require.jscurl.js

  1. require.js js 文件越来越大,需要同时加载多个js文件。
代码语言:javascript
复制
<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
<script src="4.js"></script>
<script src="5.js"></script>
<script src="6.js"></script>

问题:

  1. 加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长
  2. 其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。

require.js的诞生,就是为了解决这两个问题: (1)实现js文件的异步加载,避免网页失去响应; (2)管理模块之间的依赖性,便于代码的编写和维护。

代码语言:javascript
复制
require([module], callback);

require(['math'], function (math) {
   math.add(2, 3);
});

require.js的加载:

代码语言:javascript
复制
<script src="js/require.js" defer async="true" ></script> 
// IE不支持这个属性,只支持defer,所以把defer也写上。
// 加载requirejs文件,也可能造成网页失去响应。
// 解决办法有两个,一个是把它放在网页底部加载,
// 另一个是写成上面这样

// 加载我们自己的代码了。假定我们自己的代码文件是main.js,也放在js目录下面。
<script src="js/require.js" data-main="js/main"></script>

CMD:lazyload

依赖就近

代码语言:javascript
复制
define(function(require, exports, module) {
   var clock = require('clock');
   clock.start();
});

AMD和CMD最大的区别是对依赖模块的执行时机处理不同,二者皆为异步加载模块。

AMD依赖前置,js可以方便知道依赖模块是谁,立即加载;而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略。

代码语言:javascript
复制
// CMD
define(function(require, exports, module) {   
    var a = require('./a')  
    a.doSomething()   // 此处略去 100 行   
    var b = require('./b') // 依赖可以就近书写   
    b.doSomething()   // 
    ... 
})
// AMD 
define(['./a', './b'], function(a, b) {  // 依赖必须一开始就写好    
    a.doSomething()    // 此处略去 100 行    
    b.doSomething()    
    ...
})

UMD:

UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

代码语言:javascript
复制
(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 () {
    //module ...
});

ES6 Module

代码语言:javascript
复制
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

// defer与async的区别是:
// defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;
// async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
// 一句话,defer是“渲染完再执行”,async是“下载完就执行”。
// 另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,
// 而多个async脚本是不能保证加载顺序的。

浏览器:

type属性设为module,浏览器知道这是一个 ES6 模块。 浏览器对于带有type="module"<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。

代码语言:javascript
复制
<script type="module" src="./foo.js"></script>

如果网页有多个<script type="module">,它们会按照在页面出现的顺序依次执行。

<script>标签的async属性也可以打开,这时只要加载完成,渲染引擎就会中断渲染立即执行。执行完成后,再恢复渲染。

代码语言:javascript
复制
<script type="module" src="./foo.js" async></script>

一旦使用了async属性,<script type="module">就不会按照在页面出现的顺序执行,而是只要该模块加载完成,就执行该模块。

ES6 模块也允许内嵌在网页中,语法行为与加载外部脚本完全一致。

代码语言:javascript
复制
<script type="module">
  import utils from "./utils.js";

  // other code
</script>

node:

Node.js 要求 ES6 模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,那么就必须采用.mjs后缀名。Node.js 遇到.mjs文件,就认为它是ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定"use strict"

如果不希望将后缀名改成.mjs,可以在项目的package.json文件中,指定type字段为module。 如果这时还要使用 CommonJS 模块,那么需要将 CommonJS 脚本的后缀名都改成.cjs

CommonJS和ES6的区别:

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。 CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。 CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

CommonJs

代码语言:javascript
复制
// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};
代码语言:javascript
复制
// main.js
var mod = require('./lib');

console.log(mod.counter);  // 3
mod.incCounter();
console.log(mod.counter); // 3
代码语言:javascript
复制
// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  get counter() {
    return counter
  },
  incCounter: incCounter,
};

ES6

代码语言:javascript
复制
// lib.js
export let counter = 3;
export function incCounter() {
  counter++;
}

// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4

umd规则实现方法

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
命令行工具
腾讯云命令行工具 TCCLI 是管理腾讯云资源的统一工具。使用腾讯云命令行工具,您可以快速调用腾讯云 API 来管理您的腾讯云资源。此外,您还可以基于腾讯云的命令行工具来做自动化和脚本处理,以更多样的方式进行组合和重用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档