总有些问题是你百度都不知道怎么打关键字的问题,本文内容就是关于 seajs 的一些边边角角,希望能够帮到你

单例模式的解决方案

js 模块化开发的好处就不啰嗦了,但世上没有什么事物是完美的,无论是 requireJs 或是 seaJs。其实完美这个词也是相对来说得,可能和每个人的设计思路,设计模式都有关系。
就拿本文即将说的所谓 seajs 的单例模式。就是无论你在页面的什么地方,哪个 js 文件里面,只要 require/use 的是同一个 js,那么它们真的就是同一个。。。当初自己还傻傻地在一个模块里这么写过:

1
2
3
4
5
6
7
8
define(function(require, exports, module) {
var a1 = require('a')
var a2 = require('a')
a1.x = '你又在装逼了?'
a2.x = '不装逼我浑身难受!'
alert(a1.x)
alert(a2.x)
})

运行结果:
两个 sb 弹窗都在叫着:”不装逼我浑身难受”!说好的 a1,a2 呢?吓得我差点报警。

又进行了一番测试,a 模块的内容如下:

1
2
3
define(function(require, exports, module) {
alert('a')
})

你会发现,哪怕你 require 一万次 a,弹窗也只会出现 1 次哦真得就出现 1 次哦

而且提醒一下下:require 也好,use 也罢,对应的 js 文件都只会下载一遍!所以,不要担心同一个 js 文件多次 require 会消耗大量的网络资源导致的效率问题。

后来仔细一琢磨,这可能就是模块化开发的特点吧——单例模式,用它就忍着吧。。

问题是机智的我能忍吗?一点也不能惯着!而且原生 JS 也表示不服,说我特么好不容易发明出来的 prototype,this,还有 call 等等这些能够让单身狗愉快地 new 一个对象的技能就这么被干掉了?弄得我好像是低级语言一样。。。(此处应有笑声)

上面这个需求如果不用 seajs 的话肯定是难不倒小伙伴们的:

1
2
3
4
function a() {}
var a1 = new a()
var a2 = new a()
//......后面自行脑补。

当初搜遍了网络,几乎没有发现过有类似的问题,更别提解决方案了,很是头疼,这也可能和本人的编程习惯有关,单例固然是好,但有时候实现某些需求得绕很大的弯子,敲一堆代码,这对于完美主义强迫症的我来说真真是个悲剧。。。

假设上面的 a.js 是我写的一个 seajs 模块,它是一个表格分页插件。在 require 了之后,我需要对其进行一些初始化的配置(绑定表格,设置回调等等)。那么问题来了,如果当前页面上只有一个分页表格一切都好说,该怎么配置插件就怎么配置,那么多个表格呢?我每次调用其方法都把对应的配置给传过去?说实话,我干不出这种蠢事。。于是好戏来了,如果你给 a 模块内部”exports”上下面这个神奇的函数,包你腰不酸了,腿不疼了,敲起代码也更有劲了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
define(function(require, exports, module) {
//hi~我是随便找个地方插进来的new方法
exports.newInstance = (function(fuc) {
return function() {
fuc += ''
if (fuc.indexOf('\nexports=this;') == -1) {
fuc = ('0,(' + fuc + ')').replace('{', '{\nexports=this;')
var p = /([\s;=]+require\s*\([\s\S]*?\))/g
fuc = fuc.replace(p, '$1.newInstance()')
p = /( *\. *newInstance *\( *\)){2}/g
fuc = fuc.replace(p, '.newInstance()')
}
return new (eval(fuc))(require, exports, module)
}
})(arguments.callee)
})

别问为什么,看上去有点蒙,仔细一琢磨,卧槽!原来是酱紫啊!就对了,我的装逼目的也就达到了。

于是 a 模块的多例使用代码如下:

1
2
3
4
5
6
7
define(function(require, exports, module) {
var a1 = require('a')
var a2 = a1.newInstance()
a1.doSth()
a2.doSth()
//......后面自行脑补
})

注意

通过本函数 newInstance 出来的模块如果内部还有依赖,你得给它依赖的模块也实现一个 newInstance 方法,大家 new 才是真的 new,这样出来的模块才是干干净净地,完全独立的模块。当然了,某些依赖链里的模块可能真得不需要重新创建实例,本着浪费就是可耻的原则,你可以把它的 newInstance 方法这么写:

1
2
3
4
5
6
define(function(require, exports, module) {
//hi~我是随便找个地方插进来的不会创建新实例的new方法
exports.newInstance = function() {
return exports
}
})

现在(2019/06/30)回顾这篇博客看来,对于那种需要多实例的模块,为什么当时自己不直接 export 一个 class 出来呢?

模块记载机制

千万别天真的以为你在模块里面这样:

1
2
3
4
5
// 不要在意a,b,c,d这种命名方式。。。
define(function(require, exports, module) {
var a = require('a')
var b = require('b')
})

就天真地以为,这个模块会加载完 a 模块之后再加载 b 模块。。真相其实是这样的:

在一个模块里面,require 语句的优先级是最高的,无论你将它放在模块的任意位置会优先执行 require。注意,优先执行 require 不代表你可以这么写:

1
2
3
4
5
define(function(require, exports, module) {
alert(a.x)
var a = require('a')
a.x = 1
})

除非 a.js 是个三方插件或者 a 模块不对外提供任何方法和属性,仅仅是用于给页面来点特效啦,加载一些数据啦等,和代码的下文没有什么关系的事情,你大可以把 require(“a”)放在任何地方,也就没必要将其赋值给某个变量了。
比如 a.js 的内容如下:

1
2
// a.js的全部代码就这一行 是一个并没有遵循seajs规范的三方插件
alert("a");

一个依赖 a 模块的模块内容如下:

1
2
3
4
define(function(require, exports, module) {
alert('t')
require('a')
})

执行结果你会发现弹窗内容果然先是「a」,后是「t」。

值得注意的是 sea.js 还有一种模块加载方法是 use,它的加载时机就得看你把它写在什么地方了。接上文的那个依赖 a 模块的模块内容如果是这么写:

1
2
3
4
define(function(require, exports, module) {
alert('t')
seajs.use('a')
})

这次的执行结果就是先”t”后”a”了,关于这一点,后面我会继续说。

言归正传,a 和 b 到底谁先加载,我可以肯定地告诉你,确实是 a 先加载,但是!恩,但是来了。但是 a 和 b 谁先加载完就得看各自的造化了,或是因为网络原因,或是因为文件大小,意思就是说你 require 的模块它们之间一定不要有啥耦合关系,否则很有可能会因为加载的先后顺序导致一些异常,不过话又说回来,如果你严格按照 seajs 的规范来设计模块的话这种事问题肯定不会出现的,各个模块内部都有独立的作用域和其明确的依赖链嘛。。

举一反四:

1
2
3
4
define(function(require, exports, module) {
seajs.use('a')
seajs.use('b')
})

a 和 b 的加载情况和上述是一样一样的。

现在(2019/06/30)回顾这篇博客看来,目前我们司空见惯的模块规范,对于当时的自己来说就像是发现了新大陆,哈哈

如何使用非 seajs 插件

不得不承认,目前的 seajs 插件少得令人发指!(现在有 seajs 插件集了)在实际开发过程中,总会用到各种各样五颜六色的插件,它们不是 jquery 插件就是 jquery 插件!咱们不可能说一个项目当中的所有插件都自己用 seajs 的规范再重复造一个吧?不说时间够不够了,就算够,你造出来之后总得优化,总得调 bug 吧?哪有直接用市面上已经现成的,修炼多年的插件来得爽快?就算你还是说,不行!我就是喜欢重复造轮子!我就是觉得自己写的插件是最好的,别人的就是垃圾!shit!哥们,我太喜欢你了,其实你和我一样,我还真是不喜欢用别人的插件,不说别人写得好不好吧,其实主要讨厌还得去看他们出的文档,我打死都不会承认我看不懂某些充满了浓浓装逼气息的 api 文档的。。我靠,我又说废话了!

结合上文说的 seajs 加载机制,如果想要使用三方插件,比如:jquery-table-xxx.js,显然,它是一个 jQuery 插件,而且显然还依赖 jquery-table.js。

也就是有了如下依赖关系:

1
2
3
- jquery-table-xxx
- jquery-table
- jquery

假设 jquery.js 内容如下:

1
var a = 1

jquery-table 内容如下:

1
var b = a + 1

jquery-table-xxx 内容如下:

1
var c = b + 1

主 js 内容如下:

1
2
3
4
5
6
seajs.use('jquery', function() {
seajs.use('jquery-table', function() {
seajs.use('jquery-table-xxx')
})
})
alert(c)

那么思考一下,主 js 的运行结果一定是 3 吗?

思考结束,答案是也许是 3。一定要搞明白一点,主 js 中的 alert 语句虽然一定会在 jquery-table-xxx.js 加载之后执行,但不一定是 jquery-table-xxx.js 加载完毕之后执行!加载之后,加载完毕之后是不一样滴~骚年!。只有这点搞懂了,才会知道为什么 seajs 的项目中使用三方插件会出现一些随机错误!什么?你问为什么不在各个 js 文件里 require 各自的依赖?都说了别人写的插件,并没有遵循 seajs 规范,你也就不能使用 require 了!懂?

肯定有小伙伴会说,如果主 js 这么写:

1
2
3
4
5
6
7
seajs.use('jquery', function() {
seajs.use('jquery-table', function() {
seajs.use('jquery-table-xxx', function() {
alert(c)
})
})
})

不就完事大吉了吗?是的。。但是你不觉得这种嵌套太变态了么。而且如果除了上述的三个插件,再来些插件:lalala.js,lalala-xxx.js,你单纯 use 的话。。。主 js 要这样吗:

1
2
3
4
5
6
7
8
9
10
11
12
seajs.use('jquery', function() {
seajs.use('jquery-table', function() {
seajs.use('jquery-table-xxx', function() {
seajs.use('lalala', function() {
seajs.use('lalala-xxx', function() {
alert(c)
//这里省略使用lalala-xxx模块的代码。。编不下去了
})
})
})
})
})

卧槽!!哈哈,而且重点是 lalala,lalala-xxx 和另外三个 js 是没有半毛钱关系的,凭什么要它俩等那三个加载完毕再加载?效率呢?那么主 js 要这样吗:

1
2
3
4
5
6
7
8
9
10
11
12
seajs.use('jquery', function() {
seajs.use('jquery-table', function() {
seajs.use('jquery-table-xxx', function() {
alert(c)
})
})
})
seajs.use('lalala', function() {
seajs.use('lalala-xxx', function() {
//这里省略使用lalala-xxx模块的代码。。编不下去了
})
})

不错,加载效率是上去了,那么代码的可读性呢?业务逻辑代码会被这些加载代码给拆得七零八落

于是下面我要写的就是一个能够让小伙伴们愉快地使用三方插件的插件。。

  1. 打开记事本(都是装逼界的同仁,你就当做我打开的是记事本呗)
  2. 新建文件->superUse.js(是时候来个响亮的名字亮瞎你们了)
  3. 内容如下:
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
define(function(require, exports, module) {
var newUse = function(array, fuc) {
this.num = array.length
this.fuc = fuc
for (var i = 0; i < array.length; i++) {
this.loadJs(array[i])
}
}
newUse.prototype.loadJs = function(array) {
var that = this
if (typeof array == 'string') array = [array]
var callBac =
array.length == 1
? function() {
if (--that.num == 0) {
if (that.fuc) that.fuc()
}
}
: function() {
array.shift()
that.loadJs(array)
}
seajs.use(array[0], callBac)
}
exports.use = function(array, fuc) {
new newUse(array, fuc)
}
})

调用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
define(function(require, exports, module) {
var superUse = require('superUse')
superUse.use(['a', ['b', 'c'], ['e', 'f']], function() {
alert('所有js全部加载完毕的回调函数')
})
//参数1(数组):["a",["b","c"],["e","f"]]
//a是个独立的三方js插件
//c依赖b为一个三方插件
//f依赖e为一个三方插件
//它们三个会同时按照各自的组内的依赖关系同时加载。
//参数2(function):function(){alert("所有js全部加载完毕的回调函数");}
//表示上述的js全部加载完毕之后会执行该回调函数,建议把业务逻辑代码全部丢进去,这样能够保证它们的执行是在三方插件全部加载完毕之后
})

好消息!

好消息!根据以上逻辑,现在已经有插件实现该 use 方法了,而且更加强大——支持动态 use 更多静态资源(js,css,html)。

它就是:seajs-utils


 评论