公司项目最近需要将js文件迁移到seajs来进行模块化管理,由于我以前主要接触模块化开发是接触的AMD规范的requireJS,没有接触过CMD规范,而且在实际项目中还没有用过类似技术。
于是,我非常兴奋的开始了seajs的学习,正好对模块化开发仰慕已久,终于有机会大展身手了!
一开始总是有点曲折的,我照着玉伯的github上的教程一步步来,然后发现在我引入jquery的时候,require到的jquery竟然是undefined
经过一番摸索,我发现原来jquery是基于amd规范的,seajs官网的例子之所以能够成功引入jquery,是因为其jquery是采用cmd规范模块化之后的产物
而显然我从百度cdn上拿下来的jquery是没有经过该处理的。
百度了一下,发现jquery想改造成seajs能引入的模块,有至少两种方法
1. 在jquery源码外层套上define,将其以cmd的规范模块化
2.找到jquery最后几行的&&amd的条件,可以使用ctrl+f搜索amd即可找到,删掉该条件
显然,第二种方法更为方便,改造之后,果然jquery能够正确引入了
不过据其他部门同事的反馈,改造后的jquery模块在某些情况下会出现莫名其妙的bug,据他说在ios上有bug
由于暂时没遇到bug,因此暂且还是先将jquery也封装为模块
seajs的基本配置还是比较简单的
首先,在最前面先引入seajs本身,这应该是毋庸置疑的。
然后开始配置seajs,这里我遇到一个坑,网上部分教程指出config可以使用data-config来引入,写在单独文件里,我愉快的这样做了,然后一直报错,发现路径指向错误,纠结了我半天,最后突然看到原来不知道从哪个新版本开始移除了这个属性,我的天,太逗了
废话少说,正式开始配置
seajs.config({ base:"../sea-modules", alias:{ "jquery":"jquery/jquery.min.js" } }); base提供基础路径 alias是别名,用于将较长的路径简化 以上两个参数是最基本的参数,实际上还有以下几个参数 paths:用来统一路径前缀,适用于较长的外网前缀 preload:预加载部分模块,貌似已移除 debug:设置为true开启调试模式,在控制台输出一些错误警告 map:将某个路径映射到另一个,常用于在线调试,比如spm构建得到的一般有xx.js和xx-debug.js,此时可用map将.js映射为-debug.js,方便在线调试 vars:设置seajs自带变量,可用{变量名}来获取,常用于模块路径一开始不确定的情况,例如中文或英文,zh-cn或en,该变量是加在路径上的,src="../{变量名}/main.js" charset:引用script文件时的charset属性,默认为utf-8,该属性可以为函数,具体值为函数返回的值 以上即是seajs常用属性的用法 另外seajs.config函数可以运行多次,多次运行会自动合并配置的参数 配置结束后,开始写入口函数 seajs.use("../static/main"); 这是最基本的入口,use方法用来引入模块,此处我引入了main.js,也就是入口js 由于seajs是异步加载模块的,所以这里还可以加入回调函数,传入一个形参,即可获取到main模块,接下来就可以调用main暴露出来的方法 配置完,写完入口,接下来就是重头戏,写模块了 seajs遵循cmd规范,要求每个模块需要按该规范风格书写 即define(id,dependencies,function(require,exports,module){ //前面两个参数,一个是当前模块唯一标志,一个是当前模块依赖的模块。正常情况下不必指定这两个参数,seajs会帮我们自动获取,第三个factory函数是模块的工厂函数 //require用于获取其他模块,如: var a = require("moduleA"); //此处require内写的名称可以是具体路径,也可以是alias里定义的别名,一般写的是别名 //通过require语句执行了对应的模块函数,并返回该模块的module.exports对象 //注意,除了返回对象外,也执行了该函数,比如,该模块里如果有一句alert(1)不在exports暴露的方法里,会在require调用的同时直接执行。 a.fn();//获取到a模块后,即可调用a模块暴露出来的方法 }) 以下是a模块,同样使用cmd规范 define(function(require,exports,module){ var bb="no bb"; exports.fn=function(){ console.log(bb); }; }); 显然,用脚也能想到控制台会打印 “no bb” cmd规范里比较重要的概念就是使用exports来暴露属性或方法, 例如exports.a=3,exports.fnn=function(){}, 这样其他模块用require关键字获取到对象的同时,就能使用这些暴露出来的属性或方法了 可能有人已经注意到了,我们的factory函数里三个参数,前面两个已经用到了,require用来获取模块,exports用来暴露模块,那第三个参数呢,有什么作用? 问得好!其实一开始我也很纳闷,这个玩意到底是干嘛的,经过一番研究,我大体上了解了这个参数 原来require获取到的模块实际上最后返回的是module,而调用方法也是通过module.exports获取到的 exports是module.exports的一个引用,至于为什么要拐个弯,我个人猜测有两个原因(作为一个初学者大胆的猜测,如果有误欢迎指正) 1.名字短,写的爽。好吧,我开个玩笑。 2.避免随意改动模块对象,这个才是重点,前面也强调了exports只是一个引用,其指向了module.exports的内存地址 但是引用毕竟是引用,修改引用是不会改动被引用的对象的,举个例子说明一下。 module.exports=5; exports=3; 此时require后返回的值就会是5,而不会是3,这就是引用和本身最大的区别。 关于这两个的区别有一个新手使用常犯的错误。好吧,我没犯过(得意中~) 某些场景里,我们频繁使用exports向外提供接口,可能写了多个exports.xxx=xxx 这个时候,初学者可能会想我可以这样写 exports={ a:xxx, b:xxx, c:xxx } 想想就激动啊,这样写多专业,就好像js面向对象里,给构造函数的prototype拓展方法和属性时,也会用到这种写法 然后,很不幸的告诉你,这种写法是错误的,至于原因嘛,还是刚刚提到的知识点,exports仅仅是module.exports的一个引用,改变exports的值并不会影响到module.exports。 所以你费尽千辛万苦简化的代码并没有什么卵用,最后require时引用的module.exports根本没有像你想的那样赋值 当然,这种写法的方向是正确的,确实可以简化代码,如果需要这样写,这里一般有两种写法 方法1:module.exports={ .... } 这种方法直接给module.exports赋值,一了百了。 方法2:return{ .... } 利用return返回的内容默认也相当于传入了module.exports 到此为止,我们已经能够基本使用require来获取模块,exports来暴露接口。 但是,还没结束呢,在我学习的过程中,可不止引入了一个模块,这个时候,引入多个模块会有一个小问题。 比如 var a=require("modulessA"); var b=require("modulesB"); 我引入了两个模块,一个modulesA,一个modulesB,但由于粗心,我写错了第一个模块的名字,此时获取a模块那一行会报错,从而阻塞后续代码的运行 这样会造成很不好的影响,要知道seajs的初衷就是尽量0阻塞 此处,seajs提供了require.async方法来异步获取模块, var a=require.async("modulesssssA"); 此时,虽然该行会报错,但不会影响后续代码的执行,这就是异步加载带来的好处,使用async时还可以传入回调函数来指定加载完之后执行的逻辑 谈到异步与同步,突然又想起一个需要注意的地方 那就是module.exports的赋值必须是同步完成,而不能放在回调函数里, 例如setTimeout(function(){ module.exports=... },0); 此时module.exports会变为 undefined 在文章的最后,还提醒大家注意到一个性能问题,那就是seajs模块化项目之后,如果功能较多,大量的模块js加载会造成大量请求,这显然对项目性能是有影响的 玉伯本人是推荐是用spm工具来压缩合并这些模块,这样所有的模块会合并到一个js里,适合项目上线使用 至于spm的具体配置,本人就不详细讲解了,百度一下,你就知道。 听我啰嗦了半天,希望初学者能对seajs有充分的了解,我本人也是初次接触,这是我学习了一天之后总结出来的一些基础知识,适用于刚刚上手的朋友,至于大牛们,还请批评指正,毕竟我的理解也比较浅薄,难免有疏漏和表达不当的地方。 最后还啰嗦一句! seajs和requirejs感觉最大的不同在于seajs是按需加载,用到的时候再加载,而requirejs是提前加载,提前就将用到的模块写在一个数组里一开始就加载好, 至于孰优孰劣,在下也不好评价。
时间: 2024-10-10 07:32:13