【javascript激增的思考01】模块化编程
前言
之前我做过一个web app(原来可以这么叫啦),在一个页面上有很多小窗口,每个小窗口都是独立的应用,比如:
① 我们一个小窗口数据来源是腾讯微博,需要形成腾讯微博app小窗口
② 我们一个小窗口数据来源新浪微博,需要形成新浪微博的小窗口
我们注意到以上2个的数据源与处理方式较一致,但是需要做处理,而且其鉴权也不尽相同,所以这个js代码有相同的,也有不相同的。
③ 我们的一个小窗口数据来源于百度RSS,需要形成点击标题展开的功能
④ 我们一个小窗口数据来源于XXX,其表现形式为选项卡......
⑤ 我们一个小窗口是flash,需要......
⑥ 我们一个小窗口是个综合应用,里面还会有定时器,自动的更新其数据
......
① 点击最大化图标会最大化窗口,里面的数据滚屏分页
② 拖动排序,点击选择图标分配(导航上有不同的类)
③ 新闻预览,类似于google效果
④ 延迟加载
......
各位,这些功能都在一个页面呢,我们的js该怎么组织了,我们的js要怎么写才能让后来的人读的清晰呢?
然后,响应式布局流行了,小叶你去搞下,实现响应式布局!
然后,瀑布流很火,小叶不然把瀑布流也加上
然后,纯屏设计出来了,小叶,将我们的程序设置一下吧,用户可以选择自己想要的。。。
......
我们知道一个网站干不了这么多事情,但是一个公司不是研发最大,不是产品最大,而是老板最大!
老板说需要,那就必须需要,你不行就闪开,有人行。
我们这里先不说风云变幻的需求,就说以上应用,面对不停增加的不停种类的小窗口,我们应当如何组织,并且控制我们的javascript代码呢???
javascript之路
javascript最初使用并没有那么复杂,基本就是一个页面一个js解决问题,但是现在情况变了,我们的js现在需要干的事情不比后端少了,而且还有越来越多的趋势!
在这个时候很多后端的东西便直接上来了:
① MVC
② MVVM
③ 模块化编程
④ 设计模式
以上的提出都是为了更好的帮助前端兄弟组织自己的代码,但是我们知道一个事实:
设计模式这个东西没有几年扎实的功底根本就达不到一个高度,比如本人,在刚毕业时候还在搞.net便看了几本设计模式的书籍,自以为有所得,现在想来,尼玛的神马都不记得了!!!
所以,一个事实,前端很多兄弟,并且是比较优秀的兄弟,对设计模式了解不够深入啦(我反正不深)。
从优秀到高手的蜕变中,我们会写大量js代码,我们会发现,尼玛这一坨代码和那一坨代码好像长得有点像,
尼玛这三块代码感觉就只有一点差距啦!于是小钗就干过这个事情:
看着js里面有几块相同的,然后将它和到一个函数中了,并在函数中做了一点点变化,实现功能了,小钗看到少了几十行代码,小钗感到很高兴。
于是慢慢小钗在编程过程中有意无意的发现每次都会有几个家伙会重复,所以有一天小钗终于决定将至封装一番,从那以后,小钗遇到这个问题变不会写多余的代码了。
其实,通过以上的做法,优秀的程序员慢慢有了模块化编程的思想,或者面向对象编程的思维,刚开始以实现了很多的插件沾沾自喜,到后面点,就会站在框架与js/css代码的组织的高度审视整个项目的实现了。
这个时候他便是高手啦,但是有一段很长的路要走。。。
模块化编程
javascript时不存在类这种说法的,所以模块(module)就是一个传说(ECMAScipt第六版表示会支持)。
但是我们伟大的前端技术人员模拟实现了类这个家伙,最后还实现了模块这个家伙。
最初的想法:模块就是实现特定功能的一组方法
1 function m1() {} 2 function m2() {}
将上面两个函数加在一起,便可直接调用,但是我们知道这种做法会导致全局对象污染,所以我们换个做法:
1 var person = { 2 name: '叶小钗', 3 getName: function () {}, 4 setName: function () {} 5 };
这样做又会导致内部的局部变量会被外部篡改,这显然不够“oop”,我们知道javascript唯一可以产生局部作用域的方式便是函数,所以我们有了这种写法:
1 var Person = (function () { 2 3 var name='叶小钗'; 4 var getName: function () {}; 5 var setName: function () {}; 6 7 return {get: getName, set: setName}; 8 9 })();
如此一来,我们内部的的变量就老实了,这样看上去也比较面向对象啦。
以上就是我们模块化编程的基本做法,下面来一点点变化,因为我们的模块可能会很大的:
当模块很大时,便需要用到“放大模式”,这些高级概念我们后面点再去理解。
1 var person = (function (mod) { 2 3 mod.getAge = function () {}; 4 5 ...... 6 7 })(Person);
我们看到我们原来是没有age这个变量的,所以我们一个模块需要用到的变量最好能在上面定义。
异步问题
以上的方式其实比较乐观,但是我们很多时候其实并不能保证person已经被定义了,所以上面的用法可能是错误的,这在异步编程模型中会经常遇到,这个时候我们有了一个“宽放大模式”:
1 var person = (function (mod) { 2 3 mod.getAge = function () {}; 4 5 ...... 6 7 })(Person || {});
做了一点点的变化,但是整个程序的容错度上升了。
模块化的重要性
模块化后,我们可以方便的使用其他同事的代码,也不必担心命名污染的问题了,目前通行的javascript模块规范有两种:CommonJS与AMD。
node.js这一神器的产物的诞生(我后面点会针对此写一个系列的文章,在此叮嘱自己),标志着javascript模块化编程正式诞生,因为网页一般不会很复杂,服务器的话,没有模块便做死吧。。。。
node.js的模块系统是参照CommonJS规范实现的,在CommonJS中有一个全局性方法require(),用于加载模块。
若是有一数学模块math.js,便可以如此解决:
1 var math = require('math'); 2 math.add(1, 4);
若是要搞node.js的兄弟先从这个家伙开始吧。
客服端的疑惑
以上的代码,我们知道第一行其实是会加载一段math.js的函数的,意思是我们下面的math.add要等到文件加载结束才行啦。。。这就是同步加载与异步加载的痛苦。
我们知道好的网站是不能容忍“假死”的,但是我们并不能知道网速等因素,所以同步的话,浏览器必定会假死,异步加载模型再一次出现{asynchronous},传说中的AMD。
AMD:Asynchronous Module Definition
异步模块定义,他采用异步方式加载模块,模块加载不影响后面语句执行,其实我们对他的实现应该非常熟悉的,想想我们的ajax啦:
1 require('math', function (math) { 2 math.add(); 3 });
这个内部发生了什么大家应该一目了然,应该和$.getScript是一个原理的。于是我们来介绍一个实现了AMD的javascript库:require.js。
PS:初步印象,这样虽然是按需加载的,但是以我们之前小窗口的需求来说,我们可能每一个小窗口会对应一个js文件,页面小窗口过多(延迟加载可以忽略),可能导致同时加载几个js会不会对性能有影响吗?这里我们先不关注他。
我们来看看我们的require.js,他的提出为了解决以下问题:
① 实现javascript异步加载,避免页面假死
② 管理模块之间的依赖性,便于代码编写与维护(这是重点啊)
要使用require.js需要下载最新的版本。这里比较重要,我们单独开一段吧
require.js
我们先去官网下载脚本:http://requirejs.org/docs/release/2.1.6/r.js
他这个最让人兴奋的就是,我们可以如此指定一个头main入口文件:
<script src="js/require.js" data-main="js/main"></script>
比如我现在会一次性加载几个js文件:
1 require (['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){ 2 // some code here 3 });
以上是主模块依稀三个模块的写法,若是主模块依赖于jquery的话:
require(['jquery'], function ($) { });
AMD模块写法
require.js加载采用AMD规范,假定有一个math.js文件,他定义了一个math模块,那么math需要这样写:
//math.js define(function () { var func = function () {}; return {func: func}; }); //加载方式 require(['math'], function (math) { //... })
算了,关于require.js这块东西,我等下做几个例子再跟进吧,现在都不熟悉,说这么多也没用。
结语
这块东西我们暂时说道这里,接下来我们来使用一番require.js,以及来说下MVC与MVVM是神马东西吧。
参考: