自 es6 以前,JavaScript 是天生模块化缺失的,即缺少类似后端语言的 class,
作用域也只以函数作为区分。这与早期 js 的语言定位有关,
作为一个只需要在网页中嵌入几十上百行代码来实现一些基本的交互效果的脚本语言,
确实用不着严格的组织代码规范。但是随着时代的发展,js 承担的任务越来越重,
从原先的 script 引入几十行代码即可的状态变成现在多人协作文件众多的地步,
管理和组织代码的难度越来越大,模块化的需求也越来越迫切。
在此背景下,众多的模块化加载器便应运而生。
前文提到在 es6 模块化出现之前,为了解决模块化的需求,出现了众多的模块化机制例如 cmd,amd 等。遵循不同规范有 sea.js, require.js 等实现。
- //依赖前置,jquery模块先声明
- define(['jquery'],
- function($) {
- /***/
- })
- //同步加载
- var $ = require('jquery');
- /****/
- module.exports = myFunc;
- define(function (require, exports, module) {
- // 就近依赖
- var $ = require('jquery');
- /****/
- })
综上所诉,各个不同的规范都有各自的优点,具体使用需要是各自项目情况而定。没有好不好只有适用与否。
- //做的工作其实就是这么粗暴,判断当前用的什么就以当前规范来定义
- (function(root, factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD
- define(['jquery'], factory);
- } else if (typeof exports === 'object') {
- // CommonJS
- module.exports = factory(require('jquery'));
- } else {
- // 全局变量
- root.returnExports = factory(root.jQuery);
- }
- } (this,
- function($) {
- // methods
- function myFunc() {};
- // exposed public method
- return myFunc;
- }));
以上各种模块加载器关于模块化的实现都有各自的特点,并且是比较成型完善的体系。本文也无意去重新实现一个大而全的模块加载器。
本着学习的态度,简单对其中的部分原理进行部分探究。
所有的模块加载器的实现都要有以下步骤:
既然是有个加载器,当然是会指定一些规则来让使用者遵循。否则也实现不了相应的方法。不同的框架的实现方式也是不同的,不过速途同归。
作为一个模块加载器 (简单归简单),基本的接口如下:
- modules[ src ] = {
- name : name || src,
- src : src,
- dps : [],
- exports : (typeof fn === "function")&&fn(),
- state : "complete"
- };
这样而来 require 模块的完全可以通过 define 来实现了。
- //不支持
- var a = require('a');
- //支持
- require(['a'],
- function(a) {
- var a1 = a;
- });
- /**
- * @param src string
- * 此处的src为路径,即define里的字段
- * */
- var loadScript = function(src) {
- /**
- * 进一步处理,是否网络路径或者本地路径
- * */
- var scriptSrc = getUrl(src);
- /**
- * 接下来实现大同小异,无非是脚本加载变化时的处理函数的做法
- * */
- var sc = document.createElement("script");
- var head = document.getElementsByTagName("head")[0];
- sc.src = scriptSrc;
- sc.onload = function() {
- console.log("script tag is load, the url is : " + src);
- };
- head.appendChild( sc );
- };
由前面创建脚本可知,需要解析脚本路径来分别区分当前不同路径。
路径和模块的对应关系遵循 id = 路径的原则
- //此处的a对应的路径即为base+a.js.
- require('a',
- function() {
- //abcc
- })
当然实际情况中的匹配是很复杂的,简单实现就不考虑那么多。
对于匿名模块的存在,是可以通过 document.currentScript 获取当前路径手动给其增加标识的。
脚本路径无外乎一下几种情况:
此处还需要获取当前的根路径,模块化加载必定会有 script 来加载加载器 js。所以可以据此来判断当前路径。
- var getUrl = function(src) {
- var scriptSrc = "";
- //判断URL是否是
- //相对路径'/'或者'./'开头的,获取当前根路径替换掉其他字符即可。
- if( src.indexOf("/") === 0 || src.indexOf("./") === 0 ) {
- scriptSrc = require.config.base + src.replace(/(^\/|^\.\/)/,"");
- }else if( src.indexOf("http:") === 0 ) {
- //直接获取
- scriptSrc = src;
- }else if( src.match(/^[a-zA-Z1-9]/) ){
- //不以路径符开头的直接凭借
- scriptSrc = require.config.base + src;
- }else if(true) {
- alert("src错误!");
- };
- if (scriptSrc.lastIndexOf(".js") === -1) {
- scriptSrc += ".js";
- };
- return scriptSrc;
- };
- //去除&?等字符
- var repStr = function(str) {
- return (str || "").replace(/[\&\?]{1}.+/g,"") || "";
- };
- if(document.currentScript) return repStr(document.currentScript.src);
脚本加载之后,需要根据模块不同的状态进行处理。模块主要分以下状态:
1 init:
初始化,即刚进行模块相关属性的处理,未进行模块解析。即将进行模块加载处理
2 loading:
模块解析中,即将完成
3 complete:
模块解析完成,将参数对象,exports 接口存在缓存中。依赖模块解析完成之后进行执行。
至此,关于模块化的探究就基本结束了。说来原理大家都知道。无非就是解析一下模块路径,然后动态创建脚本,控制下加载就可以了。实现以下还是有很多收获的
来源: http://www.cnblogs.com/pqjwyn/p/6590717.html