手动实现一个最low逼的require.js

一、代码看:https://github.com/lizhongzhen11/myStudy/tree/master/js/AmdRequire

参考自:
https://github.com/youngwind/blog/issues/98
https://segmentfault.com/a/1190000006040968
http://www.cnblogs.com/yexiaochai/p/3632580.html

二、前言

      先让我缓一缓,抚平一下激动地心灵!
      好了,说话吧。
      首先,为什么会有这篇文章?
      因为我不想做一个coder~。
      说人话!
      哦,其实吧,我以前没用过require.jssea.js,也不知道什么是Amd规范CommonJs规范,因为我一接触前端就开始es6了,直接importexport别提多爽!但是,我并不知道它们的原理是什么。当然,如果仅仅会用这两个api也能干活,但是成长低,容易被替代。还有比较关键的是,前端发展很快,现在不少面试官估计都经历了很多前端技术变革,时不时的问一些以前的优秀技术来秀技术,要是回答不出来吧,估计面试官内心鄙视一波:你真菜。。。其实最重要的是:不要做一个只用api的coder,要学会去思考这个api或者框架的原理,思考的过程也是技术成长的过程!

三、let’s go!

      先把参考链接看一下,尤其是第一个,作者讲了他的思想。当然,少不了看他的代码,我的实现里没有考虑循环依赖等问题,所以是最low级别的。
      其实一开始我并没有定义defineOfLzz这个变量,因为没用过require.js,所以一开始我想的是直接实现一个类似于<script src="b.js" />即加载js文件的功能。所以第一个版本很简单,在index.html里面只需要引入我的requireOfLzz.js,然后设置一个主入口js文件–a.js,在a.js里面可以直接调用requireOfLzz()来加载所有其他的js文件。一开始的想法真的很简单,不过由于js基础还是不够,一些不怎么用的api以前没用过只能一个个去试。
      这里我需求有了,接下来就要看实践了。第一个问题:如何定义主入口js
      我看require.js是这样使用的:<script src="require.js" type="text/javascript" data-main="main.js"></script>,那么这里的data-main不就是主入口js吗?所以我依葫芦画瓢就好了,直接先这样写:<script data-main="a.js" src="requireOfLzz.js"></script>
      那么问题又来了,我该怎么去加载这个主入口a.js呢?
      根据需求,index.html里只能引入一个requireOfLzz.js,其他的js看来只有靠requireOfLzz.js内部代码去引入了。但是如何做?How to do ?
      引入一个js貌似一般就是用<script src="">这种方式,但是,原谅我菜,我没想到怎么在requireOfLzz.js里面实现。这时多亏了本文第一个参考链接里面的代码,我才恍然大悟,附上代码:

1
2
3
4
5
6
7
var script = document.createElement('script');
script.src = path; // 路径
script.type = 'text/javascript';
script.onerror = function() {
console.error('请输入正确路径下的js');
}
document.body.appendChild(script);

      是的,直接通过DOM提供的api去创建一个新的script标签,并指定src以及type,然后插入进DOM树中即可。原谅我之前脑子没转过弯来,因为下面有个问题真的是脑子没转过弯来。。。
      好,此时已经知道如何去引入js了,那么我需要src的属性值吧。我得想办法把path传过来。data-main<script>标签里面的属性,我怎么得到呢?如果是正常的<dic>我可以直接document.getElementByTagName('div'),但是<script>标签可以吗?还有,万一不会用的人在index.html写了其他的<script>标签怎么办呢?

      得益于编辑器的提醒功能以及本文第一个参考链接的代码,我得知了document.currentScript方法,遂打印出来,发现通过document.currentScript.dataset.main就能拿到<script>标签里的data-main属性值,very good!

      接下来,我就可以把得到的data-main属性值赋值给path不就行了。其中,data-main必须是相对于index.html的路径。

1
2
3
4
5
6
7
8
var dataMain = document.currentScript.dataset.main;
var script = document.createElement('script');
script.src = dataMain; // 路径
script.type = 'text/javascript';
script.onerror = function() {
console.error('请输入正确路径下的js');
}
document.body.appendChild(script);

      • 中场休息

      好,主入口a.js可以加载了,接下来只需要在主入口a.js里面去加载其他的js就好了。那么,How to do ?
      首先,还是利用创建一个新的script标签然后插入body的方法。那么我是不是可以把该方法写成一个函数方便去调用呢?of course!createScript()函数应运而生~
      另一个问题:如何在主入口a.js里面传递其他js文件的地址方便我去调用createScript()呢?
      这时,查看源码以及参考链接都会发现,在(function(){})()外面都定义了一些变量,包括requiredefine。那我继续也定义一个requireOfLzz变量。然后怎么做呢?当然是继续参考人家的代码啦。。。
      其实根据需求来说,我肯定是要把其他js的路径传过来的,那么无疑通过函数传参这种方式了。简易版的:

1
2
3
requireOfLzz = function(path) {
createScript(path);
}

      这样只需要在主入口a.js里面requireOfLzz('b.js')就能把b.js路径传进来了,然后requireOfLzz函数内部去调用createScript(path)不就可以加载了吗?那我的最初版构思不就完成了吗?哈哈哈哈哈哈!当然,我这里都没做判断,代码不够健壮起码需要判断path是不是String吧。
      但是仔细和他人代码对比发现,尼玛,相差十万八千里啊!他们的define用来干嘛的?还有,好像还需要提供回调函数啊。。。
      回调简单,加上不就好了,哈哈哈哈哈。(坑来了,可惜我不会打哭的表情。)
      requireOfLzz改写:

1
2
3
4
5
6
7
requireOfLzz = function(path, ck, errCk) {
if (!path || !isString(path)) {
console.error('请输入正确路径下的js');
return;
}
createScript(path);
}

      问题来了,怎么执行回调呢?成功时执行成功的回调,失败时执行失败的回调?这才像样嘛!这时候我也想骚一把,学前辈们的代码定义一个ModuleOfLzz函数,然后在它原型上预定初始化initfetch方法,同时,我如何去判断执行成功和失败呢?我需要预定一些状态常量,然后改写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
requireOfLzz = function(path, ck, errCk) {
if (!path || !isString(path)) {
console.error('请输入正确路径下的js');
return;
}
moduleOfLzz = ModuleOfLzz(path, ck, errCk);
}
function ModuleOfLzz (path, ck, errCk) {
this.status = ModuleOfLzz.STATUS.INITED;
this.init(path, ck, errCk);
}
ModuleOfLzz.STATUS = {
INITED: 0,
ERROR: 1,
SUCCESS: 2
}
ModuleOfLzz.prototype.init = function(path, ck, errCk) {
this.path = path;
this.ck = ck;
this.errCk = errCk;
this.fetch();
}
ModuleOfLzz.prototype.fetch = function() {
createScript(this.path, this);
if (this.status === ModuleOfLzz.STATUS.SUCCESS && isFun(this.ck)) {
this.ck();
}
if (this.status === ModuleOfLzz.STATUS.ERROR && isFun(this.errCk)) {
this.errCk();
}
}
function createScript (path, object) {
if (isString(path)) {
var script = document.createElement('script');
script.src = path;
script.type = 'text/javascript';
script.onerror = function() {
console.error('请输入正确路径下的js');
if (object && isModuleOfLzz(object)) {
object.status = ModuleOfLzz.STATUS.ERROR;
}
}
document.body.appendChild(script);
if (object && isModuleOfLzz(object)) {
object.status = ModuleOfLzz.STATUS.SUCCESS;
}
}
}

      这样是不是好很多了呢?但是到这里我只实现了require,全程不见export的身影,那么做人有点追求嘛,哪怕再low,基础的export也得实现啊。可惜,我百思不得其解,怎么去export???
      幸亏找到了参考第二个链接,虽然也看了半天,复制下来在本地跑起来发现没错,然后自己再一个个的对比着去写。我所纠结的地方在我的代码里也给出了,其实就是回调函数呢打印参数是undefined!网上的案例却没有这个问题,经过反复思索与尝试,
发现是自己call方法用错了,只传了this却没有传其他参数,尴尬!而加上defineOfLzz函数,我想可能是减轻requireOfLzz的负担吧,毕竟不是所有的使用都需要执行回调函数的。

Older Post

js event loop 学习

一、参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoophttps://www.w3.org/TR/html5/webappapis.html#event-loopshttps://juejin.im/post/5a6 …

继续阅读