操刀 requirejs,自己动手写一个


这篇文章讲的是,我怎么去写一个 requirejs 。

去 github 上fork一下,顺便star~

requirejs,众所周知,是一个非常出名的js模块化工具,可以让你使用模块化的方式组织代码,并异步加载你所需要的部分。balabala 等等好处不计其数。

之所以写这篇文章,是做一个总结。目前打算动一动,换一份工作。感谢 一线码农 大大帮忙推了携程,得到了面试的机会。

面试的时候,聊着聊着感觉问题都问在了自己的“点”上,应答都挺顺利,于是就慢慢膨胀了。在说到模块化的时候,我脑子一抽,凭着感觉说了一下requirejs实现的大概步骤,充满了表现欲望,废话一堆。侥幸不可能当场让我写一遍,算是过了,事后尝试了一下,在这里跟大家分享一下我的实现。

上面是我划分的项目结构:

tool,工具模块,存放便捷方法,很多地方需要用到。 async,异步处理模块,主要实现了 promisedeferred 。逻辑上的异步。 requirejs -> loader ,amd加载器,处理模块的依赖和异步加载。物理上的异步。

因为对于异步流程控制方面,研究过一段时间,所以这里第一时间想到的就是 promise ,如果用这个来做,所有的模块放入字典,路径做key,promise做value,所有依赖都结束之后,才进行下一步操作。 不用管复杂的依赖关系,把逻辑尽量简单化:

首先有一个字典,存放所有的模块。key放地址,value放promise,promise在模块加载完毕的时候resolve。 如果依赖某个模块,先根据路径从字典找key,存在就用该promise,不存在就去加载该模块并放入字典,并使用该模块的promise。 所有的模块,我只用它的 promise ,在它的回调中写我的后续操作。它的resolve应该单独抽离出来,属于异步加载方面。

大致思路有了,当然实际写的时候肯定困难重重,不过没关系,遇到问题再去解决。

考虑到代码的简易性,以及我的个人习惯。我打算用类似于 jquery 的 $.Deferred() 和它的promise,与es6的promise有一定的出入。这样代码书写更简易,并且逻辑上更清晰,es6的promise用起来确实稍显麻烦。我需要的是一个 pub/sub 模式,一个地方触发,多个回调执行的并行方式,es6的promise,需要在then中一次次返回,并且resolve起来也不方便,最最主要的是需要 polyfill 一下,而我想自己写,写我熟悉且喜欢的代码 。

回调模块 callbacks,熟悉jquery的朋友接下来可能会觉得使用方式很熟悉,没错,我受jq的影响算是比较深的。以前在学习jq源码的时候,就觉得这个很好用,你可以从我的代码里面看到jq的影子 :

 1 import _ from '../tool/tool';
 2 
 3 /**
 4  * 基础回调模块
 5  * 
 6  * @export
 7  * @returns callbacks
 8  */
 9 export default function () {
10     let list = [],
11         _args = (arguments[0] || '').split(' '),           // 参数数组
12         fireState = 0,                                     // 触发状态  0-未触发过 1-触发中  2-触发完毕
13         stopOnFalse = ~_args.indexOf('stopOnFalse'),       // stopOnFalse - 如果返回false就停止
14         once = ~_args.indexOf('once'),                     // once - 只执行一次,即执行完毕就清空
15         memory = ~_args.indexOf('memory') ? [] : null,     // memory - 保持状态
16         fireArgs = [];                                     // fire 参数
17 
18     /**
19      * 添加回调函数
20      * 
21      * @param {any} cb
22      * @returns callbacks
23      */
24     function add(cb) {
25         if (memory && fireState == 2) {  // 如果是memory模式,并且已经触发过
26             cb.apply(null, fireArgs);
27         }
28 
29         if (disabled()) return this;      // 如果被disabled
30 
31         list.push(cb);
32         return this;
33     }
34 
35     /**
36      * 触发
37      * 
38      * @param {any} 任意参数
39      * @returns callbacks
40      */
41     function fire() {
42         if (disabled()) return this; // 如果被禁用
43 
44         fireArgs = _.makeArray(arguments); // 保存 fire 参数
45 
46         fireState = 1; // 触发中 
47 
48         _.each(list, (index, cb) => { // 依次触发回调
49             if (cb.apply(null, fireArgs) === false && stopOnFalse) { // stopOnFalse 模式下,遇到false会停止触发
50                 return false;
51             }
52         });
53 
54         fireState = 2; // 触发结束
55 
56         if (once) disable(); // 一次性列表
57 
58         return this;
59     }
60 
61     function disable() {    // 禁止
62         list = undefined;
63         return this;
64     }
65 
66     function disabled() {  // 获取是否被禁止
67         return !list;
68     }
69 
70     return {
71         add: add,
72         fire: fire,
73         disable: disable,
74         disabled: disabled
75     };
76 }
View Code

 

这是一个工厂方法,每次所需的对象由该方法生成,用闭包来隐藏局部变量,私有方法。而最后暴露(发布)出来的对象,用 pub/sub 模式,提供了 订阅触发禁用查看禁用 4个方法。 这里要说的是 ,提供了3个参数:stopOnFalseoncememory。触发的时候,按照订阅顺序依次触发,如果是 stopOnFalse 模式,当某个订阅的函数,返回是 false 的时候,停止整个触发过程。 如果是 once ,表示每个函数只能执行一次,在执行过后,会被移除队列。而 memory 状态下,在 callback 触发后,会被保持状态,之后添加的方法,添加后会直接执行。

这三种模式,传参的时候直接传入字符串,可以随意组合,用空格分开,比如:callbacks('once memory')

该模块用于整个项目中,处理所有的回调。使用方式类似于jquery的:$.Callbacks(...)

 

写到这里,就已经结束了。本文讲了对于requirejs,我的实现思路,列举了可能遇到的问题,及我的解决方式。希望能给大家的学习提供点帮助。

去 github 上fork一下,顺便star~

上面是github的地址,求star啊,作为一个虚荣的人,我对这个很看重的,哈哈,也就这点追求了。再次感激 一线码农 大哥的推荐,还有 linkFly 的经验指导。

//