Js中的模块化是如何实现的

由于 js 起初定位的原因(刚开始没想到会应用在过于复杂的场景),所以它本身并没有提供模块系统,随着应用的复杂化,模块化成为了一个必须解决的问题。本着菲麦深入原理的原则,很有必要来揭开模块化的面纱,本文主要介绍了详解js中的模块化是如何实现的,详细的介绍了模块化的运行,具有一定的参考价值,有兴趣的可以了解下,希望能帮助到大家。

一、模块化需要解决的问题

要对一个东西进行深入的剖析,有必要带着目的去看。模块化所要解决的问题可以用一句话概括

在没有全局污染的情况下,更好的组织项目代码

举一个简单的栗子,我们现在有如下的代码:

function doSomething () { const a = 10; const b = 11; const add = function (a + b) {  return a + b } add (a + b)}

登录后复制

在现实的应用场景中,doSomething 可能需要做很多很多的事情,add 函数可能也更为复杂,并且可以复用,那么我们希望可以将 add 函数独立到一个单独的文件中,于是:

// doSomething.js 文件const add = require('add.js');const a = 10;const b = 11;add(a+ b);

登录后复制

// add.js 文件function add (a, b) { return a + b;}module.exports = add;

登录后复制

这样做的目的显而易见,更好的组织项目代码,注意到两个文件中的 require 和 module.exports,从现在的上帝视角来看,这出自 CommonJS 规范(后文会有一个章节来专门讲规范)中的关键字,分别代表导入和导出,抛开规范而言,这其实是我们模块化之路上需要解决的问题。另外,虽然 add 模块需要得到复用,但是我们并不希望在引入 add 的时候造成全局污染

二、引入的模块如何运行

在上述的例子中,我们已经将代码拆分到了两个模块文件当中,在不造成全局污染的情况下,如何实现 require,才能使得例子中的代码做到正常运行呢?

先不考虑模块文件代码的载入过程,假设 require 已经可以从模块文件中读取到代码字符串,那么 require 可以这样实现

function require (path) {  // lode 方法读取 path 对应的文件模块的代码字符串  // let code = load(path);  // 不考虑 load 的过程,直接获得模块 add 代码字符串  let code = 'function add(a, b) {return a+b}; module.exports = add';  // 封装成闭包  code = `(function(module) {$[code]})(context)`  // 相当于 exports,用于导出对象  let context = {};  // 运行代码,使得结果影响到 context  const run = new Function('context', code);  run(context, code);  //返回导出的结果  return context.exports;}

登录后复制

这有几个要点:

1) 为了不造成全局污染,需要将代码字符串封装成闭包的形式,并且导出关键字 module.exports ,module 是与外界联系的唯一载体,需要作为闭包匿名函数的入参,与引用方传入的上下文 context 进行关联

2) 使用 new Function 来执行代码字符串,估计大部分同学对 new Function 是不熟悉的,因为一般情况下定义一个函数无需如此,要知道,用 Function 类可以直接创建函数,语法如下:

var function_name = new function(arg1, arg2, ..., argN, function_body)

登录后复制

在上面的形式中,每个 arg 都是一个参数,最后一个参数是函数主体(要执行的代码)。这些参数必须是字符串。也就是说,可以使用它来执行字符串代码,类似于 eval,并且相比 eval, 还可以通过参数的形式传入字符串代码中的某些变量的值

3)如果曾经你有疑惑过为什么规范的导出关键字只有 exports 而我们实际使用过程中却要使用module.exports(写过 Node 代码的应该不会陌生),那在这段代码中就可以找到答案了,如果只用 exports 来接收 context,那么对 exports 的重新赋值对 context 不会有任何影响(参数的地址传递),不信将代码改成如下形式再跑一跑:

Js中的模块化是如何实现的

演示结果

三、代码载入方式

解决了代码的运行问题,还需要解决模块文件代码的载入问题,根据上述实例,我们的目标是将模块文件代码以字符串的形式载入

在 Node 容器,所有的模块文件都在本地,只需要从本地磁盘读取模块文件载入字符串代码,再走上述的流程就可以了。事实证明,Node 非内建、核心、c++ 模块的载入执行方式大体如此(虽然使用的不是 new Function,但也是一个类似的方法)

在 RN/Weex 容器,要载入一个远程 bundle.js,可以通过 Native 的能力请求一个远程的 js 文件,再读取成字符串代码载入即可(按照这个逻辑,Node 读取一个远程的 js 模块好像也无不可,虽然大多数情况下我们不需要这么做)

在浏览器环境,所有的 Js 模块都需要远程读取,尴尬的是,受限于浏览器提供的能力,并不能通过 ajax 以文件流的形式将远程的 js 文件直接读取为字符串代码。前提条件无法达成,上述运行策略便行不通,只能另辟蹊径

这就是为什么有了 CommonJs 规范了,为什么还会出现 AMD/CMD 规范的原因

那么浏览器上是怎么做的呢?在浏览器中通过 Js 控制动态的载入一个远程的 Js 模块文件,需要动态的插入一个 节点:

// 摘抄自 require.js 的一段代码var node = config.xhtml ?        document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :        document.createElement('script');node.type = config.scriptType || 'text/javascript';node.charset = 'utf-8';node.async = true;node.setAttribute('data-requirecontext', context.contextName);node.setAttribute('data-requiremodule', moduleName);node.addEventListener('load', context.onScriptLoad, false);node.addEventListener('error', context.onScriptError, false);

登录后复制

要知道,设置了 标签的 src 之后,代码一旦下载完成,就会立即执行,根本由不得你再封装成闭包,所以文件模块需要在定义之初就要做文章,这就是我们说熟知的 AMD/CMD 规范中的 define,开篇的 add.js 需要重新改写一下

// add.js 文件define ('add',function () {  function add (a, b) {   return a + b;  }  return add;})

登录后复制

而对于 define 的实现,最重要的就是将 callback 的执行结果注册到 context 的一个模块数组中:

  context.modules = {}  function define(name, callback) {    context.modules[name] = callback && callback()  }

登录后复制

于是 require 就可以从 context.modules 中根据模块名载入模块了,是不是有了一种自己去写一个 “requirejs” 的冲动感

具体的 AMD 实现当然还会复杂很多,还需要控制模块载入时序、模块依赖等等,但是了解了这其中的灵魂,想必去精读 require.js 的源码也不是一件困难的事情

四、Webpack 中的模块化

Webpack 也可以配置异步模块,当配置为异步模块的时候,在浏览器环境同样的是基于动态插入 的方式载入远程模块。在大多数情况下,模块的载入方式都是类似于 Node 的本地磁盘同步载入的方式

嫑忘记,Webpack 除了有模块化的能力,还是一个在辅助完善开发工作流的工具,也就是说,Webpack 的模块化是在开发阶段的完成的,使用 Webpack 构筑的工作环境,在开发阶段虽然是独立的模块文件,但是在运行时,却是一个合并好的文件

所以 Webpack 是一种在非运行时的模块化方案(基于 CommonJs),只有在配置了异步模块的时候对异步模块的加载才是运行时的(基于 AMD)

五、模块化规范

通用的问题在解决的过程中总会形成规范,上文已经多次提到 CommonJs、AMD、CMD,有必要花点篇幅来讲一讲规范

Js 的模块化规范的萌发于将 Js 扩展到后端的想法,要使得 Js 具备类似于 Python、Ruby 和 Java 那样具备开发大型应用的基础能力,模块化规范是必不可少的。CommonJS 规范的提出,为Js 制定了一个美好愿景,希望 Js 能在任何地方运行,包括但不限于:

服务器端 Js 应用

命令行工具

桌面应用

混合应用

CommonJS 对模块的定义并不复杂,主要分为模块引用、模块定义和模块标识

模块引用:使用 require 方法来引入一个模块

模块定义:使用 exports 导出模块对象

模块标识:给 require 方法传入的参数,小驼峰命名的字符串、相对路径或者绝对路径

Js中的模块化是如何实现的

模块示意

CommonJs 规范在 Node 中大放异彩并且相互促进,但是在浏览器端,鉴于网络的原因,同步的方式加载模块显然不太实用,在经过一段争执之后,AMD 规范最终在前端场景中胜出(全称 Asynchronous Module Definition,即“异步模块定义”)

什么是 AMD,为什么需要 AMD ?在前述模块化实现的推演过程中,你应该能够找到答案

除此之外还有国内玉伯提出的 CMD 规范,AMD 和 CMD 的差异主要是,前者需要在定义之初声明所有的依赖,后者可以在任意时机动态引入模块。CMD 更接近于 CommonJS

两种规范都需要从远程网络中载入模块,不同之处在于,前者是预加载,后者是延迟加载

相关推荐:

javascript高级模块化require.js的具体使用方法实例分享

Laravel 的模块化开发框架 Notadd RC1

JavaScript使用高级模块化require.js的具体方法详解

以上就是Js中的模块化是如何实现的的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2594944.html

(0)
上一篇 2025年3月6日 17:41:52
下一篇 2025年3月5日 18:31:31

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • three.js如何本地运行详解

    本文主要给大家介绍了关于three.js中文文档学习之如何在本地运行的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。 从外部文件加载内容 如果你从外部文件下载…

    编程技术 2025年3月6日
    200
  • JS中for循环闭包问题如何解决

    本文我们将和大家分享两种js中for循环闭包问题如何解决的方法,希望能帮助到大家。 像这样一个代码片段,初学者会理所当然地认为依次点击Li会弹出相应的0、1、2、3、4、5,但实际结果却是这样的 我们无论点哪个按钮,最后弹出来的都是6。这就…

    2025年3月6日 编程技术
    200
  • 多种创建js对象的方法详细讲述

    在javascript开发中使用js对象是很常见的,而我们如何去创建js对象呢,方法有多种,那么我们今天就来讲讲多种创建js对象的方法吧 JavaScript中对象的创建有以下几种方式: 使用内置对象 使用JSON符号 自定义对象构造 一、…

    编程技术 2025年3月6日
    200
  • JS正则的单行模式详解

    这次给大家带来JS正则的单行模式详解,使用JS正则单行模式的注意事项有哪些,下面就是实战案例,一起来看一下。 正则表达式最早是由 Ken Thompson 于 1970 年在他改进过的 QED 编辑器里实现的,正则里最简单的元字符 “.” …

    编程技术 2025年3月6日
    200
  • 怎么使用Ajax实现循环

    这次给大家带来怎么使用Ajax实现循环,使用Ajax实现循环的注意事项有哪些,下面就是实战案例,一起来看一下。 Ajax 简介 Ajax 由 HTML、JavaScript™ 技术、DHTML 和 DOM 组成,这一杰出的方法可以将笨拙的 …

    2025年3月6日
    200
  • JSON与XML使用详解

    这次给大家带来JSON与XML使用详解,JSON与XML使用的注意事项有哪些,下面就是实战案例,一起来看一下。 1. 定义介绍 1.1 XML定义 扩展标记语言 (Extensible Markup Language, XML) ,用于标记…

    编程技术 2025年3月6日
    200
  • 基于Node.js的JavaScript项目构建工具gulp的使用方法

    这篇文章主要介绍了关于基于node.js的javascript项目构建工具gulp的使用方法,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下 也许你使用过grunt,那么这里来安利gulp的话就更加不会陌生了,下面我们就来看一…

    2025年3月6日
    200
  • JS中的模块规范(CommonJS,AMD,CMD)详解

    先回答我:为什么模块很重要? 答:因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套! 于是下面三个模块规范…

    编程技术 2025年3月6日
    200
  • JavaScript模块规范的CommonJS、AMD和CMD的介绍

    本篇文章给大家带来的内容是关于javascript模块规范的commonjs、amd和cmd的介绍,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。 本篇文章来自对文章《js模块化编程之彻底弄懂CommonJS和AMD/CMD…

    编程技术 2025年3月6日
    200
  • JavaScript中7个处理undefined的小技巧

    当原作者开始学习JS时,遇到了一个奇怪的情况,既存在undefined 的值,也存在表示空值的null。它们之间的明显区别是什么?它们似乎都定义了空值,而且,比较null == undefined的计算结果为true。 大多数现代语言,如R…

    2025年3月6日 编程技术
    200

发表回复

登录后才能评论