浅谈JS前端模块化的几种规范

2022-04-15 0 927
目录
  • 前言
  • 前端模块化开发的价值
    • 恼人的命名冲突
    • 繁琐的文件依赖
  • 模块化的好处
    • CommonJS 规范
      • CommonJS 与 ES6 模块的差异
    • AMD 规范
      • CMD 规范
        • UMD 规范
          • 回到正题
            • 总结

              前言

              有这样一个场景,客户端运行很久,但是法务部和数据部需要收集用户的一些信息,这些信息收集好之后需要进行相应的数据处理,之后上报到服务端。客户端提供一个纯粹的js执行引擎,不需要 WebView 容器。iOS 端有成熟的JavaScriptCore、Android 可以使用 V8 引擎。这样一个引擎配套有一个 SDK,访问 Native 的基础能力和数据运算能力,可以看成是一个阉割版的 Hybrid SDK 额外增加了一些数据处理能力。

              问题结束了吗?处理逻辑的时候还需要用到2个库:cheerio和sql。因为都是 Node 工程,所以纯粹的js环境是没办法直接执行。所以需求就进行了转变 ———— 将 Node 项目打包成 UMD 规范。这样就可以在纯粹的 JS 环境下运行。接下来的文章就分析下各种规范。其实也就是前端模块化的几种规范。

              前端模块化开发的价值

              随着互联网的飞速发展,前端开发越来越复杂。本文将从实际项目中遇到的问题出发,讲述模块化能解决哪些问题,以及以 Sea.js 为例讲解如何进行前端的模块化开发。

              恼人的命名冲突

              我们从一个简单的习惯出发。我做项目时,常常会将一些通用的、底层的功能抽象出来,独立成一个个函数,比如

              function  each(arr) {
              // 实现代码
              } 
              
              function  log(str) {
              // 实现代码
              }

              并像模像样的将这些代码抽取出来并统一到util.js中,在需要使用的地方引入该文件,看起来很棒,团队内的同事很感激我提供了这么便利的工具包。

              直到团队越来越大,开始有人抱怨

              小杨:我定义了一个 each 方法遍历对象,但是 util.js 中已经存在一个 each 方法,每次都需要改方法名,我只能叫 eachObject 方法。<br>张三:我定义了一个 log 方法,可是王武的代码出问题了,谁来看看?

              抱怨越来越多,最后参照 Java 的方式,引入命名空间解决问题。于是 util.js 代码变成了

              var  org = {};
              
              org.Utils = {};
              
              org.Utils.each = function (arr) {
              
              // 实现代码
              
              };
              
              org.Utils.log = function (str) {
              
              // 实现代码
              
              };

              可能看上去的代码很 low,其实命名空间在前端领域的布道者是 Yahoo!的 YUI2 项目,看看下面的代码,是 Yahoo!的一个开源项目

              if (org.cometd.Utils.isString(response)) {
              
              return  org.cometd.JSON.fromJSON(response);
              
              }
              
              if (org.cometd.Utils.isArray(response)) {
              
              return  response;
              
              }

              通过命名空间虽然可以极大的解决冲突问题,但是每次在调用一个方法时都需要写一大堆命名空间相关的代码,剥夺了编码乐趣。

              另一种方式是一个自执行函数来实现。

              (function (args) {
              
              //...
              
              })(this);

              繁琐的文件依赖

              继续上述场景,很多情况下都需要开发 UI 层通用组件,这样项目组就不需要重复造轮子。其中有一个高频使用的组件就是 dialog.js

              <script  src="util.js"></script>
              
              <script  src="dialog.js"></script>
              
              <script>
              
              org.Dialog.init({  /* 传入配置 */  });
              
              </script>

              虽然公共组做项目都会编写使用文档、发送邮件告知全员(项目地址、使用方式等),但是还是有人问「为什么 dialog.js 有问题」,最后排查的结果基本都是没有引入 util.js

              <script  src="dialog.js"></script>
              
              <script>
              
              org.Dialog.init({  /* 传入配置 */  });
              
              </script>

              命名冲突和文件依赖是前端开发中2个经典问题,经过开发者不断的思考和研究,诞生了模块化的解决方案,以 CMD 为例

              define(function(require, exports) {
              
              exports.each = function (array) {
              
              // ...
              
              };
              
              exports.log = function(message) {
              
              // ...
              
              };
              
              });

              通过 exports 就可以向外提供接口, dialog.js 代码变成

              define(function(require, exports) {
              
              var  util = require('./util.js')
              
                
              
              exports.init = function () {
              
              // ...
              
              };
              
              });

              使用的时候可以通过require(‘./util.js’)获取到 util.js 中通过 exports 暴露的接口。 require 的方式在其他很多语言中都有解决方案:include、

              模块化的好处

              1.模块的版本管理:通过别名等配置,配合构建工具,可以轻松实现模块的版本管理

              2.提高可维护性: 模块化可以实现每个文件的职责单一,非常有利于代码的维护。

              3.前端性能优化: 对于前端开发来说,异步加载模块对于页面性能非常有益。

              4.跨环境共享模块: CMD 模块定义规范与 NodeJS 的模块规范非常相近,所以通过 Sea.JS 的 NodeJS 版本,可以方便的实现模块的跨服务器和浏览器共享。

              CommonJS 规范

              CommonJS 是服务器端模块的规范。NodeJS 采用了这个规范。CommonJS 加载模块是同步的,所以只有加载完成后才能执行后面的操作。

              因为服务器的特点,加载的模块文件一般都存在在本地硬盘,所以加载起来比较快,不用考虑异步的方式。

              CommonJS 模块化的饿规范中,每个文件都是一个模块,拥有独立的作用域、变量、以及方法等,对其他模块不可见。 CommonJS 规范规定,每个模块内部,module变量表示当前模块,它是一个对象,它的exports属性是对外的接口,加载某个模块,其实是加载该模块的 module.exports 属性,require 方法用于加载模块。

              // Person.js
              
              function  Person () {
              
              this.eat = function () {
              
              console.log('eat something')
              
              }
              
                
              
              this.sleep = function () {
              
              console.log('sleep')
              
              }
              
              }
              
                
              
              var  person = new  Person();
              
              exports.person = person;
              
              exports.name = name;
              
                
              
              // index.js
              
              let  person = require('./Person').person;
              
              person.eat()

              CommonJS 与 ES6 模块的差异

              1.CommonJS 模块输出的是值的拷贝,ES6 模块输出的是值的引用

              2.CommonJS 模块是运行时加载,ES6 模块是编译时输出接口

              CommonJS 模块导出的是一个对象(module.exports 属性),该对象只在脚本运行完才会生成。

              ES6 的模块机制是 JS 引擎对脚本进行静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用,等到脚本真正执行时,再根据这个只读引用到被加载的模块中取值,

              AMD 规范

              AMD(Asynchronous Module Definition) 是在 Require.JS 推广的过程中对模块定义的规范化产出。AMD 推崇依赖前置。它是 CommonJS 模块化规范的超集,作用在浏览器上。它的特点是异步,利用了浏览器的并发能力,让模块的依赖阻塞变少。

              AMD 的 API

              define(id?, dependencyies?, factory);

              id 是模块的名字,是可选参数。 dependencies 指定了该模块所依赖的模块列表,是一个数组,也是可选参数。每个依赖的模块的输出都将作为参数依次传入 factory 中。

              require([module], callback)

              AMD 规范允许输出模块兼容 CommonJS 规范,这时 define 方法如下

              define(['module1', 'module2'], function(module1, module2) {
              
              function  foo () {
              
              // ...
              
              }
              
              return { foo:  foo };
              
              });
              
              
              define(function(require, exports, module) {
              
              var  requestedModule1 = require('./module1')
              
              var  requestedModule2 = require('./module2')
              
              function  foo () {
              
              // ...
              
              }
              
              return { foo:  foo };
              
              });

              优点: 适合在浏览器环境中加载模块,可以实现并行加载多个模块

              缺点: 提高了开发成本,并不能按需加载,而是提前加载所有的依赖

              CMD 规范

              CMD 是 Sea.JS 推广的过程中对模块定义的规范化产出。CMD 推崇依赖就近。

              CMD 规范尽量保持简单,并与 CommonJS 规范中的 Module 保持兼容,通过 CMD 规范编写的模块,可以在 NodeJS 中运行。

              CMD 模块定义规范

              CMD 中 require 依赖的描述用数组,则是异步加载,如果是单个依赖使用字符串,则是同步加载。

              AMD 是 RequireJS 在推广过程中对模块定义的规范化产出,CMD是SeaJS 在推广过程中被广泛认知。SeaJS 出自国内蚂蚁金服玉伯。二者的区别,玉伯在12年如是说:

              RequireJS 和 SeaJS 都是很不错的模块加载器,两者区别如下:

              • 两者定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。SeaJS 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 服务器端
              • 两者遵循的标准有差异。RequireJS 遵循的是 AMD(异步模块定义)规范,SeaJS 遵循的是 CMD (通用模块定义)规范。规范的不同,导致了两者API 的不同。SeaJS 更简洁优雅,更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
              • 两者社区理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。SeaJS 不强推,而采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。
              • 两者代码质量有差异。RequireJS 是没有明显的 bug,SeaJS 是明显没有 bug。
              • 两者对调试等的支持有差异。SeaJS 通过插件,可以实现 Fiddler 中自动映射的功能,还可以实现自动 combo 等功能,非常方便便捷。RequireJS无这方面的支持。
              • 两者的插件机制有差异。RequireJS 采取的是在源码中预留接口的形式,源码中留有为插件而写的代码。SeaJS 采取的插件机制则与 Node 的方式一致开放自身,让插件开发者可直接访问或修改,从而非常灵活,可以实现各种类型的插件。

              UMD 规范

              UMD(Universal Module Definition)是随着大前端的趋势产生,希望提供一个前后端跨平台的解决方案(支持 AMD、CMD、CommonJS 模块方式)。

              实现原理:

              1.先判断是否支持 Node.js 模块格式(exports 是否存在),存在则使用 Node.js 模块格式

              2.再判断是否支持 AMD 模块格式(define 是否存在),存在则使用 AMD 模块格式

              3.前2个都不存在则将模块公开到全局(window 或 global)

              // if the module has no dependencies, the above pattern can be simplified to
              
              (function (root, factory) {
              
              if (typeof  define === 'function' && define.amd) {
              
              // AMD. Register as an anonymous module.
              
              define([], factory);
              
              } else  if (typeof  exports === 'object') {
              
              // Node. Does not work with strict CommonJS, but
              
              // only CommonJS-like environments that support module.exports,
              
              // like Node.
              
              module.exports = factory();
              
              } else {
              
              // Browser globals (root is window)
              
              root.returnExports = factory();
              
              }
              
              }(this, function () {
              
                
              
              // Just return a value to define the module export.
              
              // This example returns an object, but the module
              
              // can return a function as the exported value.
              
              return {};
              
              }));

              可能有些人就要问了,为什么在上面的判断中写了 AMD,怎么没有 CMD?因为前端构建工具webpack不可识别 CMD 规范,使用 CMD 就需要引用工具,比如 Sea.JS

              讲道理,如果想判断 CMD,那 UMD 代码如何写?

              (function(root, factory) {
              
              if (typeof  define === 'function' && define.amd) {
              
              // AMD. Register as an anonymous module.
              
              define([], factory);
              
              } else  if (typeof  define === 'function' && define.cmd) {
              
              // CMD
              
              define(function(require, exports, module) {
              
              module.exports = factory()
              
              })
              
              } else  if (typeof  exports === 'object') {
              
              // Node. Does not work with strict CommonJS, but
              
              // only CommonJS-like environments that support module.exports,
              
              // like Node.
              
              module.exports = factory();
              
              } else {
              
              // Browser globals (root is window)
              
              root.returnExports = factory();
              
              }
              
              }(this, function() {
              
              // Just return a value to define the module export.
              
              // This example returns an object, but the module
              
              // can return a function as the exported value.
              
              return {};
              
              }))

              回到正题

              Cheerio 如何打包到普通的 JS 执行环境中。

              借助webpack可以方便的打出一个 umd 规范的包。

              module.exports = {
              
              entry:  './src/cheerio.js',
              
              output: {
              
              filename:  'cheerio.js',
              
              // export to AMD, CommonJS, or window
              
              libraryTarget:  'umd',
              
              // the name exported to window
              
              library:  'cheerio',
              
              globalObject:  'this'
              
              }
              
              }

              总结

              手机端(无论 iOS 还是 Android)的底层渲染内核都是类 Chrome v8 引擎。v8 引擎在执行 JS 代码时,是将代码先以 MacroAssembler 汇编库在内存中先编译成机器码再送往 CPU 执行的,并不是像其它 JS 引擎那样解析一行执行一行。所以,静态加载的 ES6 模块规范,更有助于 v8 引擎发挥价值。而运行时加载的 CommonJS、AMD、CMD 规范等,均不利于 v8 引擎施展拳脚。

              在 NodeJS 开发项目中,Node9 已经支持 ES6语法,完全可以使用 ES6 模块规范。NodeJS 的诞生,本身就基于 Google 的 v8 引擎,没有理由不考虑发挥 v8 的最大潜能。

              在浏览器 JS 开发项目中,因为从服务器加载文件需要时间,使用 CommonJS 规范肯定是不合适了。至于是使用原生的 ES 模块规范,还是使用 Sea.js,要看具体场景。如果想页面尽快加载,Sea.js 适合;如果是单页面网站,适合使用原生的 ES6 模块规范。还有一点,浏览器并非只有 Chrome 一家,对于没有使用 v8 引擎的浏览器,使用 ES6 原生规范的优势就又减少了一点。

              以上就是浅谈JS前端模块化的几种规范的详细内容,更多关于JS前端模块化的资料请关注NICE源码其它相关文章!

              免责声明:
              1、本网站所有发布的源码、软件和资料均为收集各大资源网站整理而来;仅限用于学习和研究目的,您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 不得使用于非法商业用途,不得违反国家法律。否则后果自负!

              2、本站信息来自网络,版权争议与本站无关。一切关于该资源商业行为与www.niceym.com无关。
              如果您喜欢该程序,请支持正版源码、软件,购买注册,得到更好的正版服务。
              如有侵犯你版权的,请邮件与我们联系处理(邮箱:skknet@qq.com),本站将立即改正。

              NICE源码网 JavaScript 浅谈JS前端模块化的几种规范 https://www.niceym.com/34282.html