没有合适的轮子,只好自己造( ╯□╰ )
模块化在当前大中型web项目开发中已经成为共识,从浏览器端AMD的requirejs到node.js遵循的commonjs规范,再到ES6开放了import关键字来管理模块都是最好的说明。其优势就是方便多人协作开发,代码可移植性可测试性高。
angular本身自带模块定义和依赖管理功能,这个模块管理一般是怎么使用的?
我们找个网站分析一波~
作为爱智求真的小伙伴,我们就来看看 知乎 旗下的 知乎专栏 。
先打开调试工具,截个图慢慢看~
点开这个带有hash戳被压缩的js文件,可以看到如下几个模块:
(function() {
angular.module("auth", ["auth.interceptor"])
}
).call(this),
...
function() {
angular.module("auth.interceptor", []).
}.call(this)
...
var app = angular.module("columnWebApp", ["auth", "blueimp.fileupload", "placeholderShim", "angularytics", "ngAnimate", "ngRoute", "ngTouch", "ngResource", "ngSanitize"])
...
这里声明了3个模块:
auth
模块依赖了 auth.interceptor
模块。 auth.interceptor
模块无依赖。 columnWebApp
模块是个花心大萝卜,和一堆的模块有依赖关系。 弱弱地吐槽一波:用匿名函数做闭包来避免全局污染的是常见的传统做法,这里初步判断应该是由多个js文件合并而成,这里起码有两点是可以改进的。 一是 window
、 angular
这种全局变量完全可以传参到闭包内部便于代码压缩,例如:
//压缩前
(function(window, angular){
angular.module('auth', []);
}(window, angular));
//压缩后可能是
(function(window, a){
a.module('auth', []);
}(window, angular));
二是这种压缩打包的方式还是比较粗糙的,建议开发人员有时间还是可以将多余的模块引用去掉,变成链式调用,例如:
// 合并前
angular.module('auth').service('xxx', function(){
...
});
angular.module('auth').controller('xxCtrl', ['$scope', function($scope){
...
}]);
// 合并后
angular.module('auth').service('xxx', function(){
...
}).controller('xxCtrl', ['$scope', function($scope){
...
}]);
另外严格模式也建议开启。
吐槽 end~
从上面的例子可以看出,Angular项目通用的做法是:开发的时候按照业务逻辑写成不同的js文件,然后把这些js文件压缩合并成一个或多个,在用户第一次访问的时候全部返回给浏览器做预加载。
这种处理方式在小型网站还好,性能要求不高的中型网站也勉强可用,但是对于对于大型网站影响,随着js文件变多用户首次访问的加载时间会跟着增加,看看那些PV上千万、上亿的网站为了首页优化各种技术层出不穷。而且这种处理方式也是很不合理的,用户可能只想看看首页或者只使用某个功能模块,为毛要把所有的代码都家再过来浪费用户时间?
张鑫旭写过一篇 《基于用户行为的图片等资源预加载》 说的也是类似问题。这个解决思路很有意思,分析用户的操作行为,按需进行预加载。有兴趣大家可以看一看。
既然有了这个需求,我们看看目前有什么现成的解决方案~
目前angular项目普遍使用 ui.router 模块已经成了不争的事实了,用它来管理每个“页面(视图)”和“页面对应的业务逻辑(控制器)”,依照大多数人的直觉思维。将按需加载理解为按页面加载,于是便出现了一些解决方案,基本思路都是一致的:
在“页面(视图)跳转”的时候,按需加载业务逻辑(控制器)。而恰好ui.router模块在配置路由的时候提供了一个resovle属性,可以在页面逻辑(控制器)加载之前进行一些操作。略有不同的便是有的使用了 oclazyload ,这个还比较好,起码是遵循angular框架的第三方模块,有的使用了requirejs,这种感觉就像是阿玛尼西装配阿迪达斯球鞋,虽然都是“阿”字系的,但想去甚远,做angular开发的时候还是尽量不要应用不按照angular规范开发的第三方插件,毕竟angular这种强框架不像requirejs有shim,所以还是避免给自己挖坑。
可这真的就是完美的解决方案吗?起码有以下几个问题:
将前面提到的模块化和按需加载结合起来看,比较好的解决方案应该是:按照业务功能划分模块,当用户点击某个功能模块的时候,按需加载该功能所需的文件。
举个不太恰当但容易理解的例子,当用户点击百度音乐的时候,从用户行为角度分析,这个用户很有可能会进行一些相关操作:比如搜歌、听歌等等。同时很有可能用户当前并不会访问贴吧或者新闻。所以此时按需加载音乐模块的代码逻辑,对于后续的搜歌、听歌操作实现了预加载。
这样的实现方式还会带来其它好处,下回详述~
同时也欢迎读者提供好的想法或者其它解决方案~ (●’◡’●)
博客: http://yalishizhude.github.io
作者:亚里士朱德