深入理解模块模式
这里主要引用《JavaScript语言精粹》里面有关函数的理解,结合《JavaScript高级程序设计-第2版》对块级作用域的理解,联想到自己所做的项目,对模块模式作以汇总并评价。
1 /** 2 * 首先需要知道块级作用域的概念—— 3 * 1>JavaScript当中没有真正意义上的块级作用域,我们使用匿名函数进行模拟实现。 4 * 2>在块级作用域中定义的任何变量,都会在执行结束时被销毁(闭包另当别论),源于没有指向该匿名函数的引用。 5 * 3>当里面应用了闭包时,该块级作用域里的代码执行结束时,销毁的是它的作用域链,但是其活动对象仍然保存在内存中。 6 * 3>作用: 7 * a>提高性能(相对而言):对于临时需要的变量,用完立即销毁 8 * b>防止向全局作用域中添加变量和函数。 9 * c>减少闭包占用的内存问题 10 * 4>引入模块模式... 11 */ 12 (function($){ 13 /** 14 * 模块模式 15 * 1>组成 16 * a>外层是一个立即执行的函数表达式 17 * b>模块中定义私有变量(当然也有私有函数) 18 * c>利用闭包创建可以访问私有变量和函数的特权函数。 19 * d>返回这个特权函数 20 * 2>作用 21 * a>摒弃全局变量 22 * b>封装功能或者程序,毫无疑问,更具维护性,更具重用性 23 * 3>优势:只暴露了公有方法,隐藏里面的私有属性 24 */ 25 //举个例子 26 //输入区域键盘处理 27 dom.find('div.ldj-chat-in input, div.ldj-chat-input textarea').bind('keydown keyup change',function(dom){ 28 //这3个变量是专属于这个模块的私有变量 29 var curr = 0; 30 /** 31 * 这里的history是一个数组,用于保存历史记录,很显然,这里的history需要静态的保存历史数据,而不是每一次触发事件的时候,再重新赋值所有历史记录值, 32 * 可能如果是新手的话,可能会把这里的history保存为一个全局变量,以便于操作,保存记录。但是,由于全局变量是共享的,所以如果无意中改变了它的值, 33 * 后果将是很严重的。这也就是我们通常说的,"全局变量是魔鬼"。我们利用了模块模式(一个立即执行的函数表达式),摒弃了全局变量的应用。 34 * 当然我们可以利用传入的参数值来保存对应变量的状态。接着我们要实现我们的功能就是,让history具有全局变量的效果。当然也就想到应用闭包了。 35 * 当这个函数调用完成后,由于这个函数里面存在一个匿名函数,并且里面的匿名函数调用了这里的history变量, 36 * 而且,这个匿名函数赋值给了一个jQuery对象的一个事件,导致这里的history变量一直存在于内存当中。 37 * 当然,这里的curr和now变量也都被引用了,那么这两个变量也将在内存里面。(这里,哪些变量需要放置在这儿,需要依照应用来定)。 38 */ 39 var history; 40 41 var now; 42 43 /** 44 * 返回的是一个函数(闭包),这个函数能够访问到以上3个私有属性变量,拿《JavaScript语言精粹》的话来讲,就是特权函数了, 45 * 因为只有它才能访问当这个模块里的私有变量,走出了这个模块,谁也访问不到了。 46 */ 47 return function(e){ 48 /** 49 * 以下声明的这3个变量,是属于这个匿名函数的动态作用域里面的变量,也就是说,每触发一次事件,这里的值都会重新赋值, 50 * 当然这里的变量赋值也需要依应用而定。就像这里的text变量取的是每进行一次键盘的敲击, 51 * 都需要重新记录文本框里面所有敲击的内容,当然就应该是动态变量的赋值了。 52 */ 53 var l = $(this).data('len') || 0; 54 var text = $(this).val(); 55 var n = text.length; 56 history = history || dom.data('history'); 57 58 if ($.browser.opera || (e.type === 'keyup')) { 59 //回车 60 if (e.keyCode === 13) { 61 curr = 0; 62 //如果消息为空,则不发送 63 if (text.trim() === '') { 64 $(this).val('').focus(); 65 return; 66 } 67 $(this).trigger('send'); 68 return false; 69 } 70 //上 71 if (e.which === 38 && curr < history.length) { 72 now === undefined && (now = $(this).val()); 73 var prev = history[curr]; 74 if ($(this).is('textarea')) { 75 $(this).val(/^#c[0-9a-fA-F]{6}/.test(prev) ? prev.substring(8) : prev); 76 } 77 else { 78 $(this).val(prev); 79 } 80 $(this).data('len', prev.length); 81 curr++; 82 } 83 //下... 84 } 85 } 86 }(dom)); //执行完这个函数的时候,该函数的作用域链会立即销毁,但是它的活动对象一直存在于内存当中。需要强调这里传入的参数的应用理解, 87 //它往往传入的是需要保存状态的参数。另一个在性能的优化上理解的话,就是查询该元素更加快速,很显然,减少了一层闭包的变量查询。 88 /** 89 * 虽然,history变量一直在内存里面,但是在上面的块级作用域的外面是根本访问不到的。访问不到的原因是, 90 * 上一个模块的最外层是由立即执行的函数表达式构成,导致这个匿名函数在执行完成后, 91 * 它的作用域链会被销毁,当然里面的动态局部变量也是被销毁的,如果有闭包的存在,那么它的活动对象依旧在内存中。 92 * 深入理解了这一点,才能对JavaScript代码的调优作以深入分析。 93 */ 94 95 (function(){ 96 //第二个模块 97 //... 98 })(); //当然可以在这里传入必要的全局参数 99 100 (function(){ 101 //第三个模块 102 //... 103 })(); 104 105 /** 106 * 如此这样模块化去写JavaScript代码,是不是感觉代码的封装性更好了呢?答案是肯定的。 107 * 同样的,模块模式的应用提供了一种JavaScript代码的优化思想。 108 */ 109 110 })(jQuery); //最外层同样也是模块模式,同样的,有效摒弃全局变量;起到了块级作用域的效果。 111 112 /** 113 * 1.模块模式的另外一个应用便是功能的有效独立扩展,汤姆大叔的博客解释的清晰明了 114 * http://www.cnblogs.com/TomXu/archive/2011/12/30/2288372.html 115 * 2.会发现,在最外层的代码(function($){...})(jQuery),同样也是模块模式的应用,可以知道, 116 * 模块模式的应用不仅可以应用在程序的局部,同样也能应用在程序的全局。它的好处已经解释过了,在此略过。 117 */
注意,不要仅仅用模块模式去优化我们的JavaScript代码,由于里面有闭包的存在,对于内存的的消耗还是有些大的。当然,我们需要变相的去看待这里的闭包,如果一个变量,我们持续需要检索,我们当然要选择让这种类型的变量一直置于内存中,以提高查询检索速度;不要忘了闭包的存在让这个变量实现静态存储(实现静态存储是有条件的,这里就不再解释了),什么时候需要应用这种方式,不仅要从性能上去考虑,还要从功能需求上考虑。