ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

JS模块化(学习笔记)

2022-01-07 21:31:14  阅读:151  来源: 互联网

标签:function exports 模块化 CommonJS 笔记 JS module 模块 加载


JS模块化

模块化是一个语言膨胀的必经之路 ,它能够帮助开发者拆分和组织代码。

模块化的发展情况: 无模块化–>CommonJS规范–>AMD规范–>CMD规范–>ES6模块化

1. 无模块化

1.1 全局函数模式: 将不同的功能封装成不同的全局函数

/**
 * 全局函数模式: 将不同的功能封装成不同的全局函数
 */
let msg = 'module1'
function foo () {
  console.log('foo()', msg)
}
function bar () {
  console.log('bar()', msg)
}

问题:

  • 全局作用域污染, 容易引起命名冲突
  • 维护成本高
  • 依赖关系不明显

1.2 命名空间模式: 简单对象封装,减少了全局变量。(用对象来暴露功能)

let myModule = {
  data: 'www.baidu.com',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}
myModule.data = 'other data' //能直接修改模块内部的数据
myModule.foo() // foo() other data

问题: 不安全(数据不是私有的, 外部可以直接修改)

1.3 IIFE模式: 匿名函数自调用(闭包),immediately-invoked function expression(立即调用函数表达式)

IIFE模式使用匿名函数自调用 (闭包)来封装 ,通过自定义暴露行为来区分私有成员和公有成员。

/**
 * IIFE模式: 匿名函数自调用(闭包)
 * IIFE : immediately-invoked function expression(立即调用函数表达式)
 * 作用: 数据是私有的, 外部只能通过暴露的方法操作
 * 问题: 如果当前这个模块依赖另一个模块怎么办?
 */
(function (window) {
  let msg = 'module3'
  function foo () {
    console.log('foo()', msg)
  }
  window.module3 = {
    // foo
    foo: foo
  }
})(window)

1.4 IIFE模式增强:引入依赖(jQuery)

IIFE模式增强在闭包的特性的基础上引入jQuery,使得IIFE自调用函数功能更强大。 但是 ,开发者并不能够用它来组织和拆分代码 ,于是乎便出现了以此为基石的模块化规范。

/**
 * IIFE模式增强 : 引入依赖(jQuery)
 * 这就是现代模块实现的基石
 */

// 给页面加红色背景
(function (window, $) {
  let msg = 'module4'
  function foo () {
    console.log('foo()', msg)
  }
  // foo()
  window.module4 = foo
  $('body').css('background', 'red')
})(window, jQuery)

1.5 无模块化带来的问题: 当HTML引用多个js文件时:

  1. 一个页面需要引入多个js文件
  2. 问题:
    1). 请求过多,需要多个js请求
    2). 依赖模糊,模块间可能会相互依赖,要求引入的顺序不能出错。
    3). 难以维护,可能出现牵一发而动全身的情况导致项目出现严重的问题

无模块化固然有多个好处,然而一个页面需要引入多个js文件,就会出现以上这些问题。而这些问题可以通过模块化规范来解决,下面介绍开发中最流行的commonjs, AMD, ES6, CMD规范。

2. CommonJS规范

2.1 CommonJS概述

Node 应用由模块组成,采用 CommonJS 模块规范。每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。

2.2 CommonJS特点

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

2.3 基本语法

  • 暴露模块:module.exports = valueexports.xxx = value
  • 引入模块:require(xxx),xxx为模块名或路径名

CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性

// 文件名 :example.js
let x = 1;
function add() {
    x += 1;
    return x;
}
module.exports.x = x;
module.exports.add = add;

上面代码通过module.exports输出变量x和函数add。

var example = require('./example.js');//如果参数字符串以“./”开头,则表示加载的是一个位于相对路径
console.log(example.x); // 1
console.log(example.add(1)); // 2

require命令用于加载模块文件。require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错

2.4 关于exports变量

Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。

var exports = module.exports;

exports与module.exports之间的区别有时很难分清,一个简单的处理方法,就是放弃使用exports,只使用module.exports

2.5 CommonJS模块的加载机制

**CommonJS模块的加载机制是,module.exports输出字面量的拷贝值,而并非该字面量的引用。**这点与ES6模块化有重大差异(下文会介绍),请看下面这个例子:

// lib.js
var counter = 3;
function incCounter() {
	counter++; 
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};

上面代码输出内部变量counter和改写这个变量的内部方法incCounter。

// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;
console.log(counter);  // 3
incCounter(); //目的是要改变counter值
console.log(counter); // 3 这里输出的还是3,而并非4。

//若想输出4,则需要在lib.js中的函数中加return counter
function incCounter() {
	counter++;
 	return counter;
}

上面代码说明,counter输出以后,lib.js模块内部的变化就影响不到main.js导入后的counter值了,但在lib.js里counter值发生了变化。这是因为counter是一个原始类型的值,会被缓存。

我们可以通过IIFE模式模拟CommonJS原理 ,就可以很好的解释CommonJS的加载机制了。 因为CommonJS需要通过赋值的方式 来获取匿名函数自调用的返回值 ,所以require函数在加载模块是同步的。 然而CommonJS模块的加载机制局限了CommonJS 在客户端上的使用 ,因为通过HTTP同步加载CommonJS模块是非常耗时的。

let xModule = (function (){
	let x = 1;
    function add() {
        x += 1;
        return x;
    }
    return { x, add };
})() ;
let xm = xModule;
console.log(xm.x); // 1
console.log(xm.add()); // 2
console.log(xm.x); // 1

3. AMD规范

  • AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。区别于CommonJS ,AMD规范的被依赖模块是异步加载的,异步加载意味着允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。此外AMD规范比CommonJS规范在浏览器端实现要来着早。

定义AMD规范模块:

//定义没有依赖的模块
define(function(){
   return 模块
})
//定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){
   return 模块
})

引入使用模块:

require(['module1', 'module2'], function(m1, m2){
   使用m1/m2
})

4. CMD

  • CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
  • AMD 推崇依赖前置 ,CMD 推崇依赖就近。

CMD集成了CommonJS和AMD的的特点 ,支持同步和异步加载模块。 CMD加载完某个依赖模块后并不执行 ,只是下载而已 , 在所有依赖模块加载完成后进入主逻辑 ,遇到require语句的时候才执行对应的模块 ,这样模块的执行顺序和书写顺序是完全 一致的。 因此 ,在CMD中require函数同步加载模块时没有HTTP请求过程。在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。

定义CMD暴露模块:

//定义没有依赖的模块
define(function(require, exports, module){
  exports.xxx = value
  module.exports = value
})
复制代码
//定义有依赖的模块
define(function(require, exports, module){
  //引入依赖模块(同步)
  var module2 = require('./module2')
  //引入依赖模块(异步)
    require.async('./module3', function (m3) {
    })
  //暴露模块
  exports.xxx = value
})
复制代码

引入CMD使用模块:

define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})

5.ES6模块化

ES6的模块化已经不是规范了 ,而是JS语言的特性。 随着ES6的推出 ,AMD和CMD也随之成为了历史。 ES6模块与模块化规范相比 ,有两大特点 :

  • 模块化规范输出的是一个值的拷贝 ,ES6 模块输出的是值的引用。
  • 模块化规范是运行时加载 ,ES6 模块是编译时输出接口。

模块化规范输出的是一个对象 ,该对象只有在脚本运行完才会生成。 而 ES6 模块不是对象 ,ES6 module 是一个多对象输出 ,多对象加载的模型。 从原理上来说 ,模块化规范是匿名函数自调用的封装 ,而ES6 module则是用匿名函数自调用去调用输出的成员。

ES6模块化语法:

export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
    return a + b;
};
export { basicNum, add };

/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
    ele.textContent = add(99 + basicNum);
}

如上例所示,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

// export-default.js
export default function () {
  console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'

模块默认输出, 其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。

6. 总结

  • CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案。
  • AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。
  • CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM 打包,模块的加载逻辑偏重
  • ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案

标签:function,exports,模块化,CommonJS,笔记,JS,module,模块,加载
来源: https://blog.csdn.net/weixin_45719821/article/details/122372651

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有