JavaScript模块化历程(一)

文章目录

      • 1. 模块化概述
      • 2. 模块化演变[^2]
        • 2.1.文件划分模式(`了解`)
        • 2.2.命名空间模式(`了解`)
        • 2.3.IIFE(立即执行函数表达式)和参数依赖声明(`了解`)
      • 3.模块化规范
        • 3.1 CommonJS
          • `Require的基本实现逻辑(重点看)`
        • 3.2 AMD
          • `AMD的基本实现逻辑(重点看)`
        • 3.3 CMD
      • 4.模块化标准规范

1. 模块化概述

  • 提高代码的复用性:模块化可以将代码划分成可重用的部分,降低代码的冗余和重复,提高代码的复用性。

  • 简化代码的维护和调试:当一个软件系统变得越来越复杂时,进行模块化开发可以使得每个模块都相对独立,这样就可以方便地维护和调试每个模块,而不必考虑整个系统的复杂性。

  • 提高代码的可读性:模块化可以使得代码更加结构化,清晰明了,从而提高代码的可读性和可维护性。

  • 提高开发效率:模块化开发可以使得团队成员在不同模块上并行开发,从而提高开发效率。

  • 降低项目的风险:模块化开发可以使得开发人员更加关注模块之间的接口和依赖关系,从而降低项目的风险。
    总之,模块化开发是一种有效的软件开发模式,可以提高软件开发的质量、效率和可维护性,特别是在大型软件系统的开发中,模块化更是必不可少的1

2. 模块化演变2

2.1.文件划分模式(了解)

将每个功能以及它相关的一些状态数据,单独存放到不同的文件当中,我们去约定每一个文件就是一个独立的模块。
我们去使用这个模块,就是将这个模块引入到页面当中,然后直接调用模块中的成员(变量 / 函数)。
一个script标签就对应一个模块,所有模块都在全局范围内工作

缺点:

  • 污染全局作用域
  • 命名冲突问题
  • 无法管理模块依赖关系

示例代码:

// Student.js
var maths = 80;
var chinese = 90;
function score() {
    return maths + chinese;
}
// Cook.js
var name = "煮饭";
function make_food() {
    return new Promise(function (resolve, reject) {
        setTimeout(() => {
            resolve("煮好饭了")
        }, 1000);
    })
}

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件划分模式title>
head>
<body>
	<script src="Cook.js">script>
    <script src="Student.js">script>
    <script>
        var studentScore = score();
        console.log(studentScore );
		make_food().then((message) => console.log(message));
    script>

body>
html>

2.2.命名空间模式(了解)

我们约定每个模块只暴露一个全局的对象,我们所有的模块成员都挂载到这个全局对象下面。
在第一阶段的基础上,通过将每个模块「包裹」为一个全局对象的形式实现,
有点类似于为模块内的成员添加了「命名空间」的感觉。

  • 没有私有空间
  • 模块成员仍然可以在外部被访问/修改
  • 无法管理模块依赖关系

迪米特法则:又叫作最少知识原则(The Least Knowledge Principle),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。英文简写为: LOD

// Student.js
var Student = {
    maths: 80,
    chinese: 90,
    score: function () {
        return this.maths + this.chinese;
    },
    skills: [Cook]
}
// Cook.js
var Cook = {
    name: "煮饭",
    make_food: function () {
        return new Promise(function (resolve, reject) {
            setTimeout(() => {
                resolve("煮好饭了")
            }, 1000);
        })
    }
}

DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>命名空间划分模式title>
head>

<body>
    
    <script src="Cook.js">script>
    <script src="Student.js">script>
    <script>
        // 我们可以用命名空间来访问属性,成员
        var studentScore = Student.score();
        console.log(studentScore);
        Student.skills[0].make_food().then((message) => console.log(message));
    script>

body>

html>

2.3.IIFE(立即执行函数表达式)和参数依赖声明(了解)

使用立即执行函数的方式,去为我们的模块提供私有空间。将模块中每个成员都放在一个函数提供的私有作用域当中,
对于需要暴露给外部的成员,我们可以通过return的方式实现。确保了私有成员的安全。
有了私有成员的概念,私有成员只能在模块成员内通过闭包的形式访问。
利用立即执行函数的参数传递模块依赖项

立即执行函数 : (function() {})()

// Student.js
; let Student = (function (modules) {
    var maths = 80;
    var chinese = 90;
    var skills = [modules['cook']];
    score = function () {
        return maths + chinese;
    }
    return {
        score,
        skills
    }
})({ cook: Cook }); // 模块的传入
// Cook.js
// 为什么加 ; 
// 怕别的导入的库后面没有加 ";"
; let Cook = (function () {
    var name = "煮饭";
    make_food = function () {
        return new Promise(function (resolve, reject) {
            setTimeout(() => {
                resolve("煮好饭了")
            }, 1000);
        })
    }
    return {
        make_food: make_food
    }
})();

DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>立即执行函数title>
head>

<body>
    <script src="Cook.js">script>
    <script src="Student.js">script>
    <script>
        // 我们可以用命名空间来访问属性,成员
        var studentScore = Student.score();
        console.log(studentScore);
        Student.skills[0].make_food().then((message) => console.log(message));
    script>

body>

html>

3.模块化规范

3.1 CommonJS

一个文件就是一个模块
每个模块都有单独的作用域
通过module.exports导出成员
通过require函数载入模块

缺点:

  • CommonJS的出现本来是为了解决Node.js问题,约定的是以同步模式加载模块,在浏览器端使用会导致效率低下
// Student.js
let cook = require("./Cook")

var maths = 80;
var chinese = 90;
var skills = [cook];
function score() {
    return maths + chinese;
}

module.exports = {
    score,
    skills
}
var name = "煮饭";
function make_food() {
    return new Promise(function (resolve, reject) {
        setTimeout(() => {
            resolve("煮好饭了")
        }, 1000);
    })
}
module.exports = { make_food }
/**
 * 一个文件就是一个模块
 * 每个模块都有单独的作用域
 * 通过module.exports导出成员
 * 通过require函数载入模块
 * CommonJS约定的是以同步模式加载模块,在浏览器端使用会导致效率低下。
 */
let student = require('./Student')
var studentScore = student.score();
student.skills[0].make_food().then((message) => console.log(message));
console.log(studentScore)
Require的基本实现逻辑(重点看)
/**
 * require的基本实现原理
 * webpack打包原理
 */

(function () {
    var modules = {
        "./Cook.js": function (module, require) {
            var name = "煮饭";
            function make_food() {
                return new Promise(function (resolve, reject) {
                    setTimeout(() => {
                        resolve("煮好饭了")
                    }, 1000);
                })
            }
            module.exports = { make_food }
        },
        "./Student.js": function (module, require) {
            let cook = require("./Cook.js")

            var maths = 80;
            var chinese = 90;
            var skills = [cook];
            function score() {
                return maths + chinese;
            }
            module.exports = {
                score,
                skills
            }
        },
        "./main.js": function (module, require) {
            let student = require('./Student.js')
            var studentScore = student.score();
            student.skills[0].make_food().then((message) => console.log(message));
            console.log(studentScore)
        }
    }

    // 为什么多次require只执行一次 因为有缓存
    // 为什么多次require只执行一次 因为有缓存
    var caches = {};
	
	// 基本实现逻辑
	// 基本实现逻辑
    function require(moduleId) {
        if (caches[moduleId]) {
            return caches[moduleId];
        }
        var module = { exports: {} }
        //需要把模块运行一下
        modules[moduleId](module, require);
        caches[moduleId] = module.exports;
        return module.exports;
    }

    require("./main.js")
})();

3.2 AMD

这个规范的出现主要是为了解决在浏览器里面,因为网络慢,加载JS卡顿的问题
作为一个规范,只需定义其语法API,而不关心其实现。AMD规范简单到只有一个API,即define函数:
define([module-name?], [array-of-dependencies?], [module-factory-or-object]);

  • module-name: 模块标识,可以省略。
  • array-of-dependencies: 所依赖的模块,可以省略。
  • module-factory-or-object: 模块的实现,或者一个JavaScript对象。
AMD的基本实现逻辑(重点看)
// 缓存 为什么多个require只会执行一次
var modules = {};

// 代码实现页面可能有需要依赖的其他js
// 所以里面也用到了loadScript
function define(name, deps, factory) {
    var pending = deps.length;
    // 创建一个数组 来看看依赖是否全部加载完成
    var resolvedDeps = new Array(pending);

    deps.forEach(function (dep, index) {
        if (modules[dep]) {
            resolvedDeps[index] = modules[dep];
            pending--;
        } else {
            loadScript(dep + ".js", function () {
                resolvedDeps[index] = modules[dep];
                pending--;
                // 这里面是异步执行了的 所以在这里
                // 在if外面都要做一个加载完成的判断
                if (pending === 0) {
                    modules[name] = factory.apply(null, resolvedDeps);
                }
            })
        }

        if (pending === 0) {
            modules[name] = factory.apply(null, resolvedDeps);
        }
    });
}

// 引用的时候也有可能需要依赖其他的js
// 所以在这个里面也用到了 loadScript
function require(deps, callback) {
    var pending = deps.length;
    var resolvedDeps = new Array(pending);

    deps.forEach(function (dep, index) {
        if (modules[dep]) {
            resolvedDeps[index] = modules[dep];
            pending--;
        } else {
            loadScript(dep + ".js", function () {
                resolvedDeps[index] = modules[dep];
                pending--;
                // 这里面是异步执行了的 所以在这里
                // 在if外面都要做一个加载完成的判断
                if (pending === 0) {
                    callback.apply(null, resolvedDeps);
                }
            })
        }
        if (pending === 0) {
            callback.apply(null, resolvedDeps);
        }
    })
}

// 动态加载script的话,并不会让document的渲染进行卡顿
function loadScript(url, callback) {
    var script = document.createElement("script");
    script.src = url;
    // 加载的过程实际就是 执行 define 函数的过程
    script.onload = callback || function () { };
    document.head.appendChild(script);
}

require(["student"], function (student) {
    var studentScore = student.score();
    console.log(studentScore)
})
//student.js
// AMD的实现
define("student", [], function () {
    var maths = 80;
    var chinese = 90;
    function score() {
        return maths + chinese;
    }
    return { score }
});

3.3 CMD

实现逻辑与AMD基本类似,就不写了

4.模块化标准规范

ToDO


  1. 什么是模块化?为什么要进行模块化开发? ↩︎

  2. 细说前端模块化开发 ↩︎

你可能感兴趣的:(JavaScripy,javascript,开发语言,模块化)