深入理解requireJS-实现一个简单的模块加载器

在前文中我们不止一次强调过模块化编程的重要性,以及其可以解决的问题:

① 解决单文件变量命名冲突问题

② 解决前端多人协作问题

③ 解决文件依赖问题

④ 按需加载(这个说法其实很假了)

⑤ ......

为了深入了解加载器,中间阅读过一点requireJS的源码,但对于很多同学来说,对加载器的实现依旧不太清楚

事实上不通过代码实现,单单凭阅读想理解一个库或者框架只能达到一知半解的地步,所以今天便来实现一个简单的加载器

加载器原理分析

分与合

事实上,一个程序运行需要完整的模块,以下代码为例:

//求得绩效系数
 var performanceCoefficient = function () {
  return 0.2;
 };

 //住房公积金计算方式
 var companyReserve = function (salary) {
  return salary * 0.2;
 };

 //个人所得税
 var incomeTax = function (salary) {
  return salary * 0.2;
 };

 //基本工资
 var salary = 1000;

 //最终工资
 var mySalary = salary + salary * performanceCoefficient();
 mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary));
 console.log(mySalary);

我一份完整的工资来说,公司会有绩效奖励,但是其算法可能非常复杂,其中可能涉及到出勤率,完成度什么的,这里暂时不管

而有增便有减,所以我们会交住房公积金,也会扣除个人所得税,最终才是我的工资

对于完整的程序来说上面的流程缺一不可,但是各个函数中却有可能异常的复杂,跟钱有关系的东西都复杂,所以单单是公司绩效便有可能超过1000行代码

于是我们这边便会开始分:

<script src="companyReserve.js" type="text/javascript"></script>
<script src="incomeTax.js" type="text/javascript"></script>
<script src="performanceCoefficient.js" type="text/javascript"></script>
<script type="text/javascript">

 //基本工资
 var salary = 1000;

 //最终工资
 var mySalary = salary + salary * performanceCoefficient();
 mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary));
 console.log(mySalary);

</script>

上面的代码表明上是“分”开了,事实上也造成了“合”的问题,我要如何才能很好的把它们重新合到一起呢,毕竟其中的文件可能还涉及到依赖,这里便进入我们的require与define

require与define

事实上,上面的方案仍然是以文件划分,而不是以模块划分的,若是文件名发生变化,页面会涉及到改变,其实这里应该有一个路径的映射处理这个问题

var pathCfg = {
 'companyReserve': 'companyReserve',
 'incomeTax': 'incomeTax',
 'performanceCoefficient': 'performanceCoefficient'
};

于是我们一个模块便对应了一个路径js文件,剩下的便是将之对应模块的加载了,因为前端模块涉及到请求。所以这种写法:

companyReserve = requile('companyReserve');

对于前端来说是不适用的,就算你在哪里看到这样做了,也一定是其中做了一些“手脚”,这里我们便需要依据AMD规范了:

require.config({
 'companyReserve': 'companyReserve',
 'incomeTax': 'incomeTax',
 'performanceCoefficient': 'performanceCoefficient'
});

require(['companyReserve', 'incomeTax', 'performanceCoefficient'], function (companyReserve, incomeTax, performanceCoefficient) {
 //基本工资
 var salary = 1000;

 //最终工资
 var mySalary = salary + salary * performanceCoefficient();
 mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary));
 console.log(mySalary);
});

这里便是一个标准的requireJS的写法了,首先定义模块以及其路径映射,其中定义依赖项

require(depArr, callback)

一个简单完整的模块加载器基本就是这个样子了,首先是一个依赖的数组,其次是一个回调,回调要求依赖项全部加载才能运行,并且回调的参数便是依赖项执行的结果,所以一般要求define模块具有一个返回值

方案有了,那么如何实现呢?

实现方案

说到模块加载,人们第一反应都是ajax,因为无论何时,能拿到模块文件的内容,都是模块化的基本,但是采用ajax的方式是不行的,因为ajax有跨域的问题

而模块化方案又不可避免的要处理跨域的问题,所以使用动态创建script标签加载js文件便成为了首选,但是,不使用ajax的方案,对于实现难度来说还是有要求

PS:我们实际工作中还会有加载html模板文件的场景,这个稍候再说

通常我们是这样做的,require作为程序入口,调度javascript资源,而加载到各个define模块后,各个模块便悄无声息的创建script标签加载

加载结束后便往require模块队列报告自己加载结束了,当require中多有依赖模块皆加载结束时,便执行其回调

原理大致如此,剩下的只是具体实现,而后在论证这个理论是否靠谱即可

加载器阉割实现

核心模块

根据以上理论,我们由整体来说,首先以入口三个基本函数来说

var require = function () {
};
require.config = function () {
};
require.define = function () {
};

这三个模块比不可少:

① config用以配置模块与路径的映射,或者还有其他用处

② require为程序入口

③ define设计各个模块,响应require的调度

然后我们这里会有一个创建script标签的方法,并且会监听其onLoad事件

④ loadScript

其次我们加载script标签后,应该有一个全局的模块对象,用于存储已经加载好的模块,于是这里提出了两个需求:

⑤ require.moduleObj 模块存储对象

⑥ Module,模块的构造函数

有了以上核心模块,我们形成了如下代码:

(function () {

 var Module = function () {
  this.status = 'loading'; //只具有loading与loaded两个状态
  this.depCount = 0; //模块依赖项
  this.value = null; //define函数回调执行的返回
 };

 var loadScript = function (url, callback) {

 };

 var config = function () {

 };

 var require = function (deps, callback) {

 };

 require.config = function (cfg) {

 };

 var define = function (deps, callback) {

 };

})();

于是接下来便是具体实现,然后在实现过程中补足不具备的接口与细节,往往在最后的实现与最初的设计没有半毛钱关系......

代码实现

这块最初实现时,本来想直接参考requireJS的实现,但是我们老大笑眯眯的拿出了一个他写的加载器,我一看不得不承认有点妖

于是这里便借鉴了其实现,做了简单改造:

(function () {

 //存储已经加载好的模块
 var moduleCache = {};

 var require = function (deps, callback) {
  var params = [];
  var depCount = 0;
  var i, len, isEmpty = false, modName;

  //获取当前正在执行的js代码段,这个在onLoad事件之前执行
  modName = document.currentScript && document.currentScript.id || 'REQUIRE_MAIN';

  //简单实现,这里未做参数检查,只考虑数组的情况
  if (deps.length) {
   for (i = 0, len = deps.length; i < len; i++) {
    (function (i) {
     //依赖加一
     depCount++;
     //这块回调很关键
     loadMod(deps[i], function (param) {
      params[i] = param;
      depCount--;
      if (depCount == 0) {
       saveModule(modName, params, callback);
      }
     });
    })(i);
   }
  } else {
   isEmpty = true;
  }

  if (isEmpty) {
   setTimeout(function () {
    saveModule(modName, null, callback);
   }, 0);
  }

 };

 //考虑最简单逻辑即可
 var _getPathUrl = function (modName) {
  var url = modName;
  //不严谨
  if (url.indexOf('.js') == -1) url = url + '.js';
  return url;
 };

 //模块加载
 var loadMod = function (modName, callback) {
  var url = _getPathUrl(modName), fs, mod;

  //如果该模块已经被加载
  if (moduleCache[modName]) {
   mod = moduleCache[modName];
   if (mod.status == 'loaded') {
    setTimeout(callback(this.params), 0);
   } else {
    //如果未到加载状态直接往onLoad插入值,在依赖项加载好后会解除依赖
    mod.onload.push(callback);
   }
  } else {

   /*
   这里重点说一下Module对象
   status代表模块状态
   onLoad事实上对应requireJS的事件回调,该模块被引用多少次变化执行多少次回调,通知被依赖项解除依赖
   */
   mod = moduleCache[modName] = {
    modName: modName,
    status: 'loading',
    export: null,
    onload: [callback]
   };

   _script = document.createElement('script');
   _script.id = modName;
   _script.type = 'text/javascript';
   _script.charset = 'utf-8';
   _script.async = true;
   _script.src = url;

   //这段代码在这个场景中意义不大,注释了
   //   _script.onload = function (e) {};

   fs = document.getElementsByTagName('script')[0];
   fs.parentNode.insertBefore(_script, fs);

  }
 };

 var saveModule = function (modName, params, callback) {
  var mod, fn;

  if (moduleCache.hasOwnProperty(modName)) {
   mod = moduleCache[modName];
   mod.status = 'loaded';
   //输出项
   mod.export = callback ? callback(params) : null;

   //解除父类依赖,这里事实上使用事件监听较好
   while (fn = mod.onload.shift()) {
    fn(mod.export);
   }
  } else {
   callback && callback.apply(window, params);
  }
 };

 window.require = require;
 window.define = require;

})();

首先这段代码有一些问题:

没有处理参数问题,字符串之类皆未处理

未处理循环依赖问题

未处理CMD写法

未处理html模板加载相关

未处理参数配置,baseUrl什么都没有搞

基于此想实现打包文件也不可能

......

但就是这100行代码,便是加载器的核心,代码很短,对各位理解加载器很有帮助,里面有两点需要注意:

① requireJS是使用事件监听处理本身依赖,这里直接将之放到了onLoad数组中了

② 这里有一个很有意思的东西

document.currentScript

这个可以获取当前执行的代码段

requireJS是在onLoad中处理各个模块的,这里就用了一个不一样的实现,每个js文件加载后,都会执行require(define)方法

执行后便取到当前正在执行的文件,并且取到文件名加载之,正因为如此,连script的onLoad事件都省了......

demo实现

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <title></title>
</head>
<body>
</body>
<script src="require.js" type="text/javascript"></script>
<script type="text/javascript">
 require(['util', 'math', 'num'], function (util, math, num) {

  num = math.getRadom() + '_' + num;
  num = util.formatNum(num);
  console.log(num);
 });
</script>
</html>
//util
define([], function () {
 return {
  formatNum: function (n) {
   if (n < 10) return '0' + n;
   return n;
  }
 };
});
//math
define(['num'], function (num) {
 return {
  getRadom: function () {
   return parseInt(Math.random() * num);
  }
 };
});
//math
define(['num'], function (num) {
 return {
  getRadom: function () {
   return parseInt(Math.random() * num);
  }
 };
});

小结

今天我们实现了一个简单的模块加载器,通过他希望可以帮助各位了解requireJS或者seaJS,最后顺利进入模块化编程的行列

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

您可能感兴趣的文章:

  • javascript高级模块化require.js的具体使用方法
  • 基于Require.js使用方法(总结)
  • 基于require.js的使用(实例讲解)
  • 详解require.js配置路径的用法和css的引入
  • angular.js + require.js构建模块化单页面应用的方法步骤
  • 利用require.js与angular搭建spa应用的方法实例
  • require.js中的define函数详解
  • Require.js的基本用法详解
  • 使用Require.js封装原生js轮播图的实现代码
(0)

相关推荐

  • 详解require.js配置路径的用法和css的引入

    前端开发在近一两年发展的非常快,JavaScript作为主流的开发语言得到了前所未有的热捧.大量的前端框架出现了,这些框架都在尝试着解决一 些前端开发中的共性问题,但是实现又不尽相同.通常一般的前端加载js文件都是这样 : <script type="text/javascript" src="js/js1.js"></script> <script type="text/javascript" src="

  • 使用Require.js封装原生js轮播图的实现代码

    index.html页面: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>require.js封装轮播图</title> <style type="text/css"> *{ margin: 0; padding: 0; list-style: none; } #banner{ width: 830px; he

  • 基于Require.js使用方法(总结)

    一.为什么要使用require.js 首先一个页面如果在加载多个js文件的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长:其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序,当依赖关系很复杂的时候,代码的编写和维护都会变得困难. require.js,就是为了解决这两个问题: 1. 实现js文件的异步加载,避免网页失去响应: 2. 管理模块之间的依赖性,便于代码的编写和维护. 二.require.js的加载 第一步,去官网下载最新版本,直接放到页面进行加载 <sc

  • 基于require.js的使用(实例讲解)

    本篇文章总结下require.js使用方法. 1.为什么使用require.js? 使用之前,我的页面的js是这样的 <script type="text/javascript" src="/js/jquery.min.js"></script> <script type="text/javascript" src="/js/bootstrap.min.js"></script>

  • javascript高级模块化require.js的具体使用方法

    本文介绍了require.js的具体使用方法,分享给大家,也给自己留个笔记. Require.js: RequireJS是一个非常小巧的javascript模块载入框架,是AMD(Asynchronous Module Definition,异步模块加载机制)规范最好的实现之一.最新版的requireJS压缩后只有14k,堪称非常轻量.它还同时可以和其他的框架协调工作,使用requireJS必将使我们的前端代码质量得以提升. 首先我们先来看一下一个普通的页面js加载 <!DOCTYPE html

  • angular.js + require.js构建模块化单页面应用的方法步骤

    前言 本文主要给大家介绍的是关于利用angular.js + require.js构建模块化单页面应用的方法,分享出来供大家参考学习,需要的朋友们下面来一起看看详细的介绍吧. AngularJS描述: angularjs是可以用来构建WEB应用的,WEB应用中的一种端对端的完整解决方案.通过开发者呈现一个更高层次的抽象来简化应用的开发.最适合的就是用它来构建一个CRUD应用,它提供了非常方便的且统一高效的解决方案,其数据绑定.基本模版标识符.表单验证.路由.深度链接.组件重用.依赖注入.以及HT

  • 利用require.js与angular搭建spa应用的方法实例

    前言 AngularJS诞生于2009年,由Misko Hevery 等人创建,后为Google所收购.是一款优秀的前端JS框架,已经被用于Google的多款产品当中.AngularJS有着诸多特性,最为核心的是:MVVM.模块化.自动化双向数据绑定.语义化标签.依赖注入等等. 本文是接上篇,angular 实战部分,angular比较适合spa项目,这里不借助任何seed和构建工具,直接从零搭建,基本的angular项目结构大致包含如下几个部分: 1)app.js 入口 2)index.htm

  • Require.js的基本用法详解

    一:什么是require.js ①:require.js是一个js脚本加载器,它遵循AMD(Asynchronous Module Definition)规范,实现js脚本的异步加载,不阻塞页面的渲染和其后的脚本的执行,并提供了在加载完成之后的执行相应回调函数的功能: ②:require.js要求js脚本必须要实现模块化,即文件化:而require.js的作用之一就是加载js模块,也就是js文件. ③:require.js可以管理js模块/文件之间的依赖;即不同的框架例如Jquery,Angul

  • require.js中的define函数详解

    前言 模块不同于传统的脚本文件,它良好地定义了一个作用域来避免全局名称空间污染.它可以显式地列出其依赖关系,并以函数(定义此模块的那个函数)参数的形式将这些依赖进行注入,而无需引用全局变量.RequireJS的模块是模块模式的一个扩展,其好处是无需全局地引用其他模块. RequireJS的模块语法允许它尽快地加载多个模块,虽然加载的顺序不定,但依赖的顺序最终是正确的.同时因为无需创建全局变量,甚至可以做到在同一个页面上同时加载同一模块的不同版本. 在定义一个模块的时候,方法的第一行写一个"use

  • 深入理解requireJS-实现一个简单的模块加载器

    在前文中我们不止一次强调过模块化编程的重要性,以及其可以解决的问题: ① 解决单文件变量命名冲突问题 ② 解决前端多人协作问题 ③ 解决文件依赖问题 ④ 按需加载(这个说法其实很假了) ⑤ ...... 为了深入了解加载器,中间阅读过一点requireJS的源码,但对于很多同学来说,对加载器的实现依旧不太清楚 事实上不通过代码实现,单单凭阅读想理解一个库或者框架只能达到一知半解的地步,所以今天便来实现一个简单的加载器 加载器原理分析 分与合 事实上,一个程序运行需要完整的模块,以下代码为例: /

  • 代码详解javascript模块加载器

    定义 var MyModules = (function Manager() { var modules = {}; function define (name, deps, impl) { for(var j = 0, length = deps.length; j < length; j++){ deps[j] = modules[deps[j]]; } modules[name] = impl.apply(impl, deps); } function get (name) { retur

  • 通过实例解析js简易模块加载器

    前端模块化 关注前端技术发展的各位亲们,肯定对模块化开发这个名词不陌生.随着前端工程越来越复杂,代码越来越多,模块化成了必不可免的趋势. 各种标准 由于javascript本身并没有制定相关标准(当然es6已经有了import和export),所以在模块化方面诞生了各种不同的规范.主要有AMD规范(随requirejs诞生而普及),CMD规范(随seajs的出现而普及),commonjs(主要用于node,并不适合前端).至于以上几种规范的异同,无耻的我在这里就不多费口水了,请还不了解的亲们自行

  • 概述如何实现一个简单的浏览器端js模块加载器

    在es6之前,js不像其他语言自带成熟的模块化功能,页面只能靠插入一个个script标签来引入自己的或第三方的脚本,并且容易带来命名冲突的问题.js社区做了很多努力,在当时的运行环境中,实现"模块"的效果. 通用的js模块化标准有CommonJS与AMD,前者运用于node环境,后者在浏览器环境中由Require.js等实现.此外还有国内的开源项目Sea.js,遵循CMD规范.(目前随着es6的普及已经停止维护,不论是AMD还是CMD,都将是一段历史了) 浏览器端js加载器 实现一个简

  • 一个简单的动态加载js和css的jquery代码

    一个简单的动态加载js和css的jquery代码,用于在生成页面时通过js函数加载一些共通的js和css文件. //how to use the function below: //$.include('file/ajaxa.js');$.include('file/ajaxa.css'); //or $.includePath = 'file/';$.include(['ajaxa.js','ajaxa.css']);(only if .js and .css files are in the

  • Webpack常见静态资源处理-模块加载器(Loaders)+ExtractTextPlugin插件

    webpack系列目录 webpack 系列 二:webpack 介绍&安装 webpack 系列 三:webpack 如何集成第三方js库 webpack 系列 四:webpack 多页面支持 & 公共组件单独打包 webpack 系列 五:webpack Loaders 模块加载器 webpack 系列 六:前端项目模板-webpack+gulp实现自动构建部署 基于webpack搭建纯静态页面型前端工程解决方案模板, 最终形态源码见github: https://github.com

  • 详解webpack模块加载器兼打包工具

     什么是 webpack? webpack是近期最火的一款模块加载器兼打包工具,它能把各种资源,例如JS(含JSX).coffee.样式(含less/sass).图片等都作为模块来使用和处理. 我们可以直接使用 require(XXX) 的形式来引入各模块,即使它们可能需要经过编译(比如JSX和sass),但我们无须在上面花费太多心思,因为 webpack 有着各种健全的加载器(loader)在默默处理这些事情,这块我们后续会提到. 你可以不打算将其用在你的项目上,但没有理由不去掌握它,因为以近

  • in.js 一个轻量级的JavaScript颗粒化模块加载和依赖关系管理解决方案

    国外的像基于jQuery的RequireJs,YUI Loader,LabJs,RunJs,国内也有淘宝的SeaJs,豆瓣的DoJs等,这些都是一些十分优秀的模块加载器.但是本文将会向大家介绍一个新的开源的轻量级"多线程"异步模块加载器In.js,In的开发借鉴了Do的一些思路和使用习惯,在此期间感谢@kejun同我的耐心交流,In.js压缩后只有4.77k,不仅小巧而且十分好用. 优点: 按需加载 无阻塞加载 依赖关系管理 颗粒化模块管理 如何使用? A.引入In.js 复制代码 代

  • require加载器实现原理的深入理解

    前言 我们常说node并不是一门新的编程语言,他只是javascript的运行时,运行时你可以简单地理解为运行javascript的环境.在大多数情况下我们会在浏览器中去运行javascript,有了node的出现,我们可以在node中去运行javascript,这意味着哪里安装了node或者浏览器,我们就可以在哪里运行javascript. 1.node模块化的实现 node中是自带模块化机制的,每个文件就是一个单独的模块,并且它遵循的是CommonJS规范,也就是使用require的方式导入

  • Node.js模块加载详解

    JavaScript是世界上使用频率最高的编程语言之一,它是Web世界的通用语言,被所有浏览器所使用.JavaScript的诞生要追溯到Netscape那个时代,它的核心内容被仓促的开发出来,用以对抗Microsoft,参与当时白热化的浏览器大战.由于过早的发布,无可避免的造成了它的一些不太好的特性. 尽管它的开发时间很短,但是JavaScript依然具备了很多强大的特性,不过,每个脚本共享一个全局命名空间这个特性除外. 一旦Web页面加载了JavaScript代码,它就会被注入到全局命名空间,

随机推荐