JavaScript模块化思想之入门篇
在写正文之前先写一点废话,从我大三下学期正式接触前端到现在,已经六个月了。自己从HTML,CSS,简单的JS验证开始,一点点开始走入前端的世界。越发的感觉前端这一领域散发着无穷的魅力,也许这和我真心喜爱前端分不开。有些人总爱说前端技术迭代快,认为这是前端的一个缺点,但我恰恰认为这正是前端的魅力所在,充满着朝气和活力。
小小感慨一下,现在开启我的JavaScript模块化入门之旅,之前自己做的都是小项目,简单的写写JS验证,给按钮添加一些绑定事件等等。随着自己对JS学习和使用的深入,渐渐的发现两个问题:
(1)我之前写的JS代码重用性很低
(2)功能分散,举个栗子,我要获取当前日期,也要获取当前日期加1也就是明天的日期,之前我作为两个独立互不相干的函数去写,但现在想想,其实他们都可以归为一个日期对象的两个方法,把它们绑定在一个对象上,作为它的两个方法,不是更好么?
如果想好好了解一下JavaScript模块化,推荐阮一峰老师的文章。
1. 首先,我们需要明白为什么要用模块化?
接下来,用我的JS代码之路演示一下如何使代码模块化:
功能都是为了解决需求的。模块化可以带来的优点有以下几点:
(1)可维护性。举个例子,如果我们把未使用模块化的代码比作油和水混合在了一起,模块化之后的代码就好像油和水的分层,油就是油,水就是水,这样的代码层次清晰,功能分明。似乎用油和水必然分层的现象来指代JS模块化的大势所趋也很合适。
(2)命名空间。这里需要谈到JS的作用域。又涉及到了作用域链。如果对作用域链不熟悉的同学可以移步我的另一篇文章“理解JavaScript中的作用域链”。JS中是靠函数来区分作用域的。每个函数都有一个作用域链。如果我们把所有的代码都揉到一起,代码行数少还行,多了就难免会造成“命名空间污染”。
(3)可复用性。当我们明白了命名空间,借助命名空间我们就可以实现对模块代码的封装,这样我们就可以在任何我们需要这个功能的时候直接去引用这个功能模块。
接下来,用我的JS代码之路演示一下如何使代码模块化:
(1)原始时代:把所有的要用到的JS代码都堆砌在该页面的一对<script>标签中。
1 function f1(){ 2 //函数体 3 } 4 function f2(){ 5 //函数体 6 }
这样写的缺点:代码基本没有什么复用性可以,应该还会存在和页面隅合度太高的问题。还需要去考虑各种作用域的问题。
(2)古时代:思路就是把模块写成一个对象。比如我们要写一个能控制页面音乐播放,停止,下一首,上一首的功能。就可以封装一个musicPlayer对象
1 // 将基本的方法封装起来 2 var musicPlayer = { 3 4 var musicDom = null, //播放器对象 5 var musicList = [], //存放歌曲列表 6 7 // 初始化音乐播放器 8 var init = function(){ 9 10 }, 11 12 // 添加一首歌曲 13 var add = function(src){ 14 15 }, 16 17 // 根据数组下标决定播放哪一首,索引index从0开始 18 var play = function(index){ 19 20 }, 21 22 // 暂停播放 23 var stop = function(){ 24 25 }, 26 27 // 下一首 28 var next = function(){ 29 30 }, 31 32 // 上一首 33 var prev = function(){ 34 35 } 36 };
这时候,就已经可以称之为一个模块了,在全局作用域中,我们只向window对象上绑定了一个musicPlayer对象,之后我们就可以使用musicPlayer加'.'的形式来调用里面的方法。如“musicPlayer.init();”
这种方式也有一个缺点,就是我们不能去控制我们想暴露的内容,并且在外部可以改写musicPlayer对象的内部方法和变量。
(3)现代。包含IIFE(立即执行函数),放大模式,宽放大模式,输入全局变量
除了IIFE,其他的三种我之前都没有接触过,在这里简单谈谈我的理解。
- IIFE(Immediately-Invoked Function Expression)
1 // 创建一个立即执行的匿名函数 2 // 该函数返回一个对象,包含你要暴露的属性 3 // 如下代码如果不使用立即执行函数,就会多一个属性i 4 // 如果有了属性i,我们就能调用counter.i改变i的值 5 // 对我们来说这种不确定的因素越少越好 6 7 var counter = (function(){ 8 var i = 0; 9 10 return { 11 get: function(){ 12 return i; 13 }, 14 set: function( val ){ 15 i = val; 16 }, 17 increment: function() { 18 return ++i; 19 } 20 }; 21 }()); 22 23 // counter其实是一个对象 24 25 counter.get(); // 0 26 counter.set( 3 ); 27 counter.increment(); // 4 28 counter.increment(); // 5 29 30 counter.i; // undefined i并不是counter的属性 31 i; // ReferenceError: i is not defined (函数内部的是局部变量)
从以上的代码可以看出,counter中其中并没有i这个属性,它只有return 中暴露出来的内容。这样我们就对i实现了私有。
- 放大模式
我对放大模式的理解就是把原函数当作参数传递到IIFE中,然后给原函数添加新的扩展方法,把扩展后的函数返回。就实现了对原函数的“放大”。
1 var module1 = (function (mod){ 2 mod.m3 = function () { 3 //... 4 }; 5 return mod; 6 })(module1);
在这个例子中,就给module1添加了一个新的方法m3并返回。
- 宽放大模式
宽放大模式就是在放大模式的基础上新增了一个特性:IIFE的参数可以是空对象。
1 var module1 = ( function (mod){ 2 //... 3 return mod; 4 })(window.module1 || {});
IIFE传入的参数:如果window.module1有定义,就传入该参数,如果为undefined就传入一个空对象。
- 输入全局变量
如果我们要在IIFE内使用全局变量,最好把全局变量通过参数传递进去。
1 var module1 = (function ($, YAHOO) { 2 //... 3 })(jQuery, YAHOO);
如上所示的代码将jQuery和YUI两个库的全局变量当作参数传入了module1。