Javascript基礎知識篇(7): 高質量開發准則(下)


1.用匿名函數(function(){})();將腳本包裹起來,有效控制全局變量,避免沖突隱患。

2.在解決JS沖突的前提下,如果需要進行多個模塊(匿名函數)之間的通信,則需要采用唯一全局變量(系約定名:GLOBAL)+命名空間+屬性的方式來解決,同時應該為你維護的模塊添加必要的注釋,以提高團隊合作的效率。對於公共組件,不推薦加前綴(base.js)。對於單獨由某成員負責的組件,推薦以成員縮寫名添加前綴(如張三:zs_news.js)。

3.CSS放在頁頭,JS放在頁尾。即將非腳本資源文件放在head標簽中,而將腳本放到body的尾部。同時,請注意限制外部腳本的數量,在發布正式環境之前,將同一頁面所需的多個外部腳本合並到一個腳本(使用YUI Build Tool:http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js)將放到body尾部。但可能合並腳本文件非常大,盡管此時http請求減少了,如果腳本執行很長一段時間還是會產生性能影響,因此建議添加更多的非阻止腳本,即執行腳本盡量添加到頁面渲染完成之后。

4.可在JS內聯腳本或外部腳本標簽中加入defer屬性(僅IE和Firefox支持),用於延遲腳本加載。即下載腳本后不會立即執行,而要等到其他腳本加載完畢后以及頁面渲染完成后(window.onload之前)才執行。對於不支持defer的瀏覽器將忽略,僅當做普通的腳本按順序執行。

5.發布正式前壓縮JS文件:Packer(http://dean.edwards.name/packer)和YUI Compressor

(http://developer.yahoo.com/yui/compressor)。后者需安裝JDK和配置java環境,下載jar文件並通過命名進行調用。常見命令:java -jar yuicompressor-x.y.z.jar myfile.js -o myfile.min.js。不過也可在線使用YUI Compressor: http://refresh-sf.com/yui。同時,可以使用反壓縮工具http://jsbeautifier.org/對已壓縮后的文件進行恢復。

6.利用JS分層對應用程序代碼組織進行更好的控制:base、common、page。其中base層完成封裝不同瀏覽器下JS的差異,提供統一的接口,位於三層中的最底層。common層依賴於base層,提供可復用的組件,和具體的頁面功能沒有直接關系,為page層提供組件。page層依賴於base和common層,提供具體的頁面功能。

7.利用getElementsByClassName()和getElementsByTagName()都可獲取一組具有"相似功能"的DOM節點,但后者適合穩定的HTML結構,而前者使得JS和HTML實現最大程序解耦。

8.要滿足一組具有"相似功能"的DOM節點能夠復用,則不能使用父容器的ID作為掛鈎,而是對這組節點使用相同的類

9.在同一頁面使用多個內容結構相似的組件時,應分別為每個組件指定一個根節點,以保持每個組件之間的獨立性。

10.如果一個函數內部某個因素非常不穩定,可將它從函數內部分離出來,以參數形式傳入,實現將不穩定因素和函數解耦。

11.可通過匿名函數,call以及apply來調整this指向,也可在this指向變化之前保存為另一變量

12.為函數預留回調接口增加可擴展性。

13.通常利用function來定義類,一般函數名為大寫(同時代表構造函數)。每定義一個類時,同時將產生一個該類對應的原型(hash對象),也可在原型中定義屬性和行為,但構造函數中的屬性和行為比原型優先級高,對於同名屬性和行為,構造函數中的對應的屬性和行為將覆蓋原型中定義的同名屬性和行為。通過this關鍵字實現構造函數和原型進行通信。

14.推薦將行為封裝到類的函數原型里,屬性封裝到構造函數中。通過this定義公有屬性和行為,通過var定義私有屬性和行為(通過作用域實現)。私有屬性和行為是不能在原型中被訪問。通常情況下定義多實例情況下,構造函數中的屬性和行為都會復制一份到每個實例中,而原型中的屬性和行為都會在多實例中共享(不會復制)。

15.如要實現公有行為訪問到私有成員,最簡單的解決辦法就是將所有的公有成員定義到構造函數中(與私有成員在同一作用域),但這樣做會消耗內存,僅適合於對私有性要求非常高的項目(通常具有強制性要求),但一般情況下不推薦這樣做。

16.定義在原型中的行為一定是公有的,如果項目對私有成員並沒有強制性要求,推薦以下做法:約定私有成員的定義形式(通常在成員前加_以示區分公有成員),將私有屬性定義到構造函數中,私有行為定義到原型中。但注意:既然只是約定,就不能真正實現私有化,只是團隊成員調用私有成員屬於不合乎規范而已(不能強制避免,只是約定而已)。

17. 極端的OO采用get和set訪問實現對私有屬性的完全私有化,同樣會帶來很大的內存消耗,但可以定制私有屬性(如只讀等)。對於比較簡單的應用,直接采用this.**對屬性進行讀寫,而對於較復雜的應用,則采用get和set訪問器實現

18.在接觸到19之前,有必要說下JS傳值和傳址的問題。所謂傳值,即復制一份副本傳遞,而不改變其本身,適合於基本數據類型(如:int,bool,string等);所謂傳址,即將當前變量的地址傳遞給新變量,此時兩個變量都引用同一個地址,對新變量的更改也會影響原變量本身,適合於數組、對象等復雜數據類型。那如何實現給復雜數據類型(其中包含基本數據類型)傳值呢?首先定義一個新變量,可通過for in循環其中的基本數據類型,來分別將每個基本類型賦值給新變量即可完成。對於數組來說,還可通過自身方法slice或concat來實現。

19.簡單的說,要實現類繼承,無外乎就是將父類構造函數和原型中的屬性和行為復制到子類的過程。但不能通過直接復制或調用父類構造函數就能完成,必須通過call或apply調整當前對象指向(window->this)來完成調用對於原型繼承,也不可直接將父類的原型直接拷貝到子類,我們都知道原型實際上可看成一個hash對象,在JS中傳值方式分為傳值和傳址兩種方式,對於hash對象來說當然是傳址(即子類原型和父類原型指向同一個對象),這時要是在子類中添加了新的行為(實際上正是因為有了繼承,子類肯定會添加新的行為),同時也將改變父類的原型,當然就違背繼承的初衷了。我們可以讓父類原型以傳值的方式給子類(即只給一個副本給子類,子類原型改變而不影響父類自身),通常有兩種方式可以實現:定義子類新原型,通過for in循環父類原型,將其中的值(其實都已經是基本類型)逐個拷貝到子類新原型中。其次利用構造函數特性,new一個父類對象給子類原型,同時調整子類原型構造函數為子類本身,就可簡單實現父類原型中的成員復制到子類。

20.JS在獲取HTML屬性時,在各瀏覽器存在一定差異,對於HTML節點node常規屬性時

(如<a id="blog" href="http://www.cnblogs.com/hmiinyu/" class="blog"  blogType="js">Miracle's Blog</a>),使用node.**比node.getAttribute("**")時更具兼容性,如a.innerHTML="Miracle's Blog"(IE,FireFox),而a.getAttribute("innerHTML")="Miracle's Blog"(IE),null(FireFox),另外由於class是關鍵字,因此獲取class屬性時應使用className;但對於自定義屬性時,使用node.getAttribute("**")更具兼容性,如a.getAttribute("blogType")="js"(IE,FireFox), 因此總結:獲取常規屬性,統一使用node.**;獲取自定義屬性,同一使用node.getAttribute("**")

21.剛才20提到自定義屬性可以保存客戶端需要的字符串,如果我們稍加技巧,還能利用它來保存數組、hash對象等復雜數據類型。秘訣在於:雖然自定義屬性看起來只能保存字符串,但如果我們把字符串設計成如下這樣:<a id="blog" href="http://www.cnblogs.com/hmiinyu/" class="blog"  blogInfo="{name: 'Miracle',type: '前端開發'}">Miracle's Blog</a>,我們發現自定義屬性blogInfo:{name: 'Miracle',type='前端開發'}跟hash對象(或json)長得很像,利用eval函數來反序列化將字符串轉化hash對象,具體做法:var blog = a.getAttribute("blogInfo"), var b = eval("("+blog+")"),然后就可分別訪問b.name, b.type了,是不是很巧妙呢。

22.JS事件機制:對IE來說,event對象是作為window的全局屬性存在;對FireFox來說,event對象作為function的事件參數傳入,因此我們在事件處理函數中返回arguments.length,IE返回0,FF返回1,常見的兼容事件對象這樣寫:btn.onlick = function(e) { e = window.e || e;***};。但對於內聯事件(即在事件屬性中賦值,如:<input id="btn" type="button" value="click me" onclick="handler();">, function handler(){alert(arguments.length)})來說,IE和FF都返回0,也即此時內聯事件處理函數並未被替換為btn.onclick = handler,而是btn.onclick = function() { handler() };但如果內聯事件和外部事件同時存在時,外部事件將覆蓋內聯事件,除非通過attachEvent(IE)或addEventListener(FF)來附加內聯事件。另外,值得一提的是,在內聯事件中也可寫注釋和代碼(極力不推薦這么做,除非只有極少的代碼時,如***;return false;等)。

23.JS數據有四種存放方式:字面量、本地變量、數據和對象。相對於數據和對象而言,字面量和變量的存取速度非常快速,可以忽略。以訪問20萬個數據值在各大瀏覽器中進行比較:推薦盡量多使用字面量和本地變量,以及限制數組和對象的大量使用,以提高代碼執行的速度。

這里有一段代碼的兩個版本來說明本地變量的性能:第一個版本三次使用document(存在於執行上下文作用域鏈的global objects中,根據標識符解析規則,一般都是先查找activation objects,如果查找不到再到global objects中最后查找,這里要進行三次查找document);第二個版本首先將document存放於本地變量中,只進行一次查找,細微的改動(可能真實的應用程序會有多次查找)就顯著提升了應用程序的性能。因此,將頻繁使用的(特別是作用域之外的)變量首先存放在本地變量中,然后再使用本地變量

function initUI() {
    var bd = document.body,
        links = document.getElementsByTagName("a"),
        i = 0,
        len = links.length;
    while(i < len){
        update(links[i++]);
    }
    document.getElementById("go-btn").onclick = function() {
        start();
    };
    bd.className = "active";
}
function initUI() {
    var doc = document,
        bd = doc.body,
        links = doc.getElementsByTagName("a"),
        i= 0,
        len = links.length;
    while(i < len){
        update(links[i++]);
    }
    doc.getElementById("go-btn").onclick = function() {
        start();
    };
    bd.className = "active";
}

可能有人提出with也可解決這個問題,首先來解釋一下with的用法,with將創建一個在作用域鏈上的臨時對象區(提供該對象的公共訪問點),並將對臨時對象區移動到作用域鏈的最前端,這是本地變量的對象區將相應的下移,導致本地變量的查找出現二次查找,影響了程序性能,此時作用域鏈發生變化,也即動態作用域鏈。因此,一般情況下無特殊要求不建議使用with來解決公用全局對象的作用域鏈查找性能問題

使用with來解決全局對象的作用域鏈查找性能
function initUI() {
    with (document){ //avoid!
        var bd = body,
            links = getElementsByTagName("a"),
            i= 0,
            len = links.length;
        while(i < len){
            update(links[i++]);
        }
        getElementById("go-btn").onclick = function() {
            start();
        };
        bd.className = "active";
    }
}

24. 基於23提出的with性能問題,其實JS中還有很多類似的特性。如try-catch中catch塊,當在try塊中遇到異常時會馬上跳轉至catch塊中,這時函數執行上下文作用域鏈會新增一個catch異常對象區,且提升到作用域鏈的最前端,以供異常對象的查找。但異常處理在JS中是非常有意義的,因此不能像with那樣不使用就可以了。推薦做法:一般不使用try-catch來解決JS本身的錯誤(語法錯誤等),將catch塊中內容封裝成單一函數,以減少在catch塊中本地變量的二次查找性能開銷

try {
    methodThatMightCauseAnError();
} catch (ex) {
    handleError(ex); //delegate to handler method
}

25. 盡量減少document重繪過程(repaint&reflow)而帶來的布局變動(如樣式變動),可采用打包(batches)這些變動一次性提交(即僅完成一次重繪)。主要有以下三種方式:1.隱藏元素,提交變動,再打開元素;2. 將變動臨時存儲到文檔片段中,然后將文檔片段插入到對應元素;3. 復制需要插入位置的原始元素,然后將變動提交到該復制元素,再次將復制元素替換原始元素。用一個示例來說明這些方式,假設已經有如下片段,需要將已有數據插入到該片段中。

<ul id="websites">
 <li><a href="http://www.baidu.com">百度</a></li>
 <li><a href="http://www.sina.com">新浪</a></li>
</ul>
var data = [
    {
        "name": "搜狐",
        "url": "http://www.sohu.com"
    },
    {
        "name": "騰訊",
        "url": "http://www.qq.com"
    }
];

首先定義公共函數appendDataToElement。

function appendDataToElement(appendToElement, data) {
    var a, li;
    for (var i = 0, max = data.length; i < max; i++) {
        a = document.createElement('a');
        a.href = data[i].url;
        a.appendChild(document.createTextNode(data[i].name));
        li = document.createElement('li');
        li.appendChild(a);
        appendToElement.appendChild(li);
    }
};

1.先隱藏元素,提交變動,再打開元素。

var ul = document.getElementById('websites');
ul.style.display = 'none';
appendDataToElement(ul, data);
ul.style.display = 'block';

2.將變動臨時存儲到文檔片段中,然后將文檔片段插入到對應元素(推薦)。

var fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
document.getElementById('websites').appendChild(fragment);

3.復制需要插入位置的原始元素,然后將變動提交到該復制元素,再次將復制元素替換原始元素。

var old = document.getElementById('websites');
var clone = old.cloneNode(true);
appendDataToElement(clone, data);
old.parentNode.replaceChild(clone, old);

26. JS包含四種循環體結構(while, do...while, for, for...in),其中for...in是用來遍歷對象的命名屬性列表,返回對象的實例屬性和從原型繼承而來的屬性。但相對於其他三種循環來說,由於每次循環都需要迭代搜索實例屬性或原型屬性,因此性能損耗很大(幾乎要慢7倍之多)。因此除非需要對數目不詳的對象屬性操作,否則避免使用for...in循環,反之可采用其他三種循環來代替

我這里用實例來說明以上的准則(完整版,):

利用面向過程設計Tab
<html>
    <head>
        <style type="text/css">
            ul {padding:0;margin:0;}
            .tab {width:400px;}
            .tab .tab-currentMenu {background-color:#333;color:#fff;}
            .tab .tab-currentMenu2 {background-color:blue;color:#fff;}
            .underline {text-decoration:underline;}
            .tab-menu {padding-left:20px;}
            .tab-menu li {float:left;display:inline;padding:5px;border:1px solid #333;border-bottom:none;margin-right:5px;}
            .tab-content {border:1px solid #333;clear:left;padding:5px;}
        </style>
    </head>
    <body>
        <div class="tab J_tab">
            <ul class="tab-menu">
                <li class="J_tab-menu">menu1-1</li>
                <li class="J_tab-menu">menu1-2</li>
                <li class="J_tab-menu underline">menu1-3</li>
            </ul>
            <div class="tab-content">
                <div class="J_tab-content"><div>content1-1</div><ul><li>abc</li></ul></div>
                <div class="J_tab-content" style="display:none;"><p>content1-2</p><div>abc</div></div>
                <div class="J_tab-content" style="display:none;">content1-3</div>
            </div>
        </div>
        <div class="tab J_tab">
            <ul class="tab-menu">
                <li class="J_tab-menu tab-currentMenu">menu2-1</li>
                <li class="J_tab-menu">menu2-2</li>
            </ul>
            <div class="tab-content">
                <div class="J_tab-content"><div>content2-1</div><ul><li>abc</li></ul></div>
                <div class="J_tab-content" style="display:none;"><p>content2-2</p><div>abc</div></div>
            </div>
        </div>
        <div class="tab J_tab">
            <ul class="tab-menu">
                <li class="J_tab-menu tab-currentMenu2">menu3-1</li>
                <li class="J_tab-menu">menu3-2</li>
                <li class="J_tab-menu">menu3-3</li>
                <li class="J_tab-menu">menu3-4</li>
                <li class="J_tab-menu">menu3-5</li>
            </ul>
            <div class="tab-content">
                <div class="J_tab-content"><div>content3-1</div><ul><li>abc</li></ul></div>
                <div class="J_tab-content" style="display:none;"><p>content3-2</p><div>abc</div></div>
                <div class="J_tab-content" style="display:none;">content3-3</div>
                <div class="J_tab-content" style="display:none;"><p>content3-4</p><div>abc</div></div>
                <div class="J_tab-content" style="display:none;">content3-5</div>
            </div>
        </div>
        <script type="text/javascript">
            var GLOBAL = {};
            GLOBAL.namespace = function (str) {
                var arr = str.split("."), o = GLOBAL;
                for (i = (arr[0] == "oa") ? 1 : 0; i < arr.length; i++) {
                    o[arr[i]] = o[arr[i]] || {};
                }
                o = o[arr[i]];
            };
            GLOBAL.namespace("dom");
            GLOBAL.dom.getElementsByClassName = function(str, parent, tag) {
                if(parent) {
                    parent = typeof parent == "string" ? document.getElementById(parent) : parent;
                } else {
                    parent = document.body;
                }
                tag = tag || "*";
                var elems = parent.getElementsByTagName(tag);
                var array = [];
                for(var i = 0, len = elems.length; i < len; i++) {
                    for (var j = 0, k = elems[i].className.split(" "), l = k.length; j < l; j++) { 
                        if(k[j]==str) {
                            array.push(elems[i]);
                            break;
                        }
                    }
                }
                return array;
            }
            GLOBAL.dom.addClass = function(node, str) {
                if (!new RegExp("(^|\\s+)" + str).test(node.className)) {
                    node.className = node.className + " " + str;
                }
            }
            GLOBAL.dom.removeClass = function(node, str) {
                node.className = node.className.replace(new RegExp("(^|\\s+)" + str), "");
            }
            GLOBAL.namespace("event");
            GLOBAL.event.on = function(node, eventType, handler, scope) {
                node = typeof node == "string" ? document.getElementById(node) : node;
                scope = scope || node;
                //可通過匿名函數,call以及apply來調整this指向,也可在this指向變化之前保存為另一變量
                if(document.all) {
                    //node.attachEvent("on" + eventType, handler);
                    node.attachEvent("on" + eventType, function() {
                        handler.apply(scope, arguments);
                    });
                } else {
                    //node.addEventListener(eventType, handler, false);
                    node.addEventListener(eventType, function() {
                        handler.apply(scope, arguments);
                    }, false);
                }
            }
            function setTab(config) {
                var parent = config.parent;
                var currentClass = config.currentClass;
                var trigger = config.trigger || "click";
                var handler = config.handler;
                var autoPlay = config.autoPlay;
                var playTime = config.playTime || 3000;
                var menus = GLOBAL.dom.getElementsByClassName("J_tab-menu", parent);
                var contents = GLOBAL.dom.getElementsByClassName("J_tab-content", parent);
                var currentIndex = 0;
                function showItem(n) {
                    for(var i = 0, len = contents.length; i < len; i++) {
                        contents[i].style.display = "none";
                    }
                    contents[n].style.display = "block";
                    if(currentClass) {
                        var currentMenu = GLOBAL.dom.getElementsByClassName(currentClass, parent)[0];
                        if (currentMenu) {
                            GLOBAL.dom.removeClass(currentMenu, currentClass);
                        }
                        GLOBAL.dom.addClass(menus[n], currentClass);
                    }
                    //預留回調接口
                    if(handler) {
                        handler(n);
                    }
                }
                function autoHandler() {
                    currentIndex++;
                    if(currentIndex >= menus.length) {
                        currentIndex = 0;
                    }
                    showItem(currentIndex);
                }
                if(autoPlay) {
                    setInterval(autoHandler, playTime);
                }
                for (var i = 0, mLen = menus.length; i < mLen; i++) {
                    menus[i]._index = i;
                    GLOBAL.event.on(menus[i], trigger, function() {
                        showItem(this._index);
                        currentIndex = this._index;
                    });
                }
            }
            var    tabs = GLOBAL.dom.getElementsByClassName("J_tab");
            setTab({ parent: tabs[0], trigger: "mouseover" });
            setTab({ parent: tabs[1], currentClass: "tab-currentMenu", autoPlay: true, playTime: 4000 });
            setTab({ parent: tabs[2], currentClass: "tab-currentMenu2", trigger: "mouseover", handler: function (index) {
                    alert("你激活的是第" + (index + 1) + "個標簽");
                }
            });    
        </script>
    </body>
</html>
利用面向對象設計Tab
<html>
    <head>
        <style type="text/css">
            ul {padding:0;margin:0;}
            .tab {width:400px;}
            .tab .tab-currentMenu {background-color:#333;color:#fff;}
            .tab .tab-currentMenu2 {background-color:blue;color:#fff;}
            .underline {text-decoration:underline;}
            .tab-menu {padding-left:20px;}
            .tab-menu li {float:left;display:inline;padding:5px;border:1px solid #333;border-bottom:none;margin-right:5px;}
            .tab-content {border:1px solid #333;clear:left;padding:5px;}
        </style>
    </head>
    <body>
        <div class="tab J_tab">
            <ul class="tab-menu">
                <li class="J_tab-menu">menu1-1</li>
                <li class="J_tab-menu">menu1-2</li>
                <li class="J_tab-menu underline">menu1-3</li>
            </ul>
            <div class="tab-content">
                <div class="J_tab-content"><div>content1-1</div><ul><li>abc</li></ul></div>
                <div class="J_tab-content" style="display:none;"><p>content1-2</p><div>abc</div></div>
                <div class="J_tab-content" style="display:none;">content1-3</div>
            </div>
        </div>
        <div class="tab J_tab">
            <ul class="tab-menu">
                <li class="J_tab-menu tab-currentMenu">menu2-1</li>
                <li class="J_tab-menu">menu2-2</li>
            </ul>
            <div class="tab-content">
                <div class="J_tab-content"><div>content2-1</div><ul><li>abc</li></ul></div>
                <div class="J_tab-content" style="display:none;"><p>content2-2</p><div>abc</div></div>
            </div>
        </div>
        <div class="tab J_tab">
            <ul class="tab-menu">
                <li class="J_tab-menu tab-currentMenu2">menu3-1</li>
                <li class="J_tab-menu">menu3-2</li>
                <li class="J_tab-menu">menu3-3</li>
                <li class="J_tab-menu">menu3-4</li>
                <li class="J_tab-menu">menu3-5</li>
            </ul>
            <div class="tab-content">
                <div class="J_tab-content"><div>content3-1</div><ul><li>abc</li></ul></div>
                <div class="J_tab-content" style="display:none;"><p>content3-2</p><div>abc</div></div>
                <div class="J_tab-content" style="display:none;">content3-3</div>
                <div class="J_tab-content" style="display:none;"><p>content3-4</p><div>abc</div></div>
                <div class="J_tab-content" style="display:none;">content3-5</div>
            </div>
        </div>
        <script type="text/javascript">
            var GLOBAL = {};
            GLOBAL.namespace = function (str) {
                var arr = str.split("."), o = GLOBAL;
                for (i = (arr[0] == "oa") ? 1 : 0; i < arr.length; i++) {
                    o[arr[i]] = o[arr[i]] || {};
                }
                o = o[arr[i]];
            };
            GLOBAL.namespace("dom");
            GLOBAL.dom.getElementsByClassName = function(str, parent, tag) {
                if(parent) {
                    parent = typeof parent == "string" ? document.getElementById(parent) : parent;
                } else {
                    parent = document.body;
                }
                tag = tag || "*";
                var elems = parent.getElementsByTagName(tag);
                var array = [];
                for(var i = 0, len = elems.length; i < len; i++) {
                    for (var j = 0, k = elems[i].className.split(" "), l = k.length; j < l; j++) { 
                        if(k[j]==str) {
                            array.push(elems[i]);
                            break;
                        }
                    }
                }
                return array;
            }
            GLOBAL.dom.addClass = function(node, str) {
                if (!new RegExp("(^|\\s+)" + str).test(node.className)) {
                    node.className = node.className + " " + str;
                }
            }
            GLOBAL.dom.removeClass = function(node, str) {
                node.className = node.className.replace(new RegExp("(^|\\s+)" + str), "");
            }
            GLOBAL.namespace("event");
            GLOBAL.event.on = function(node, eventType, handler, scope) {
                node = typeof node == "string" ? document.getElementById(node) : node;
                scope = scope || node;
                //可通過匿名函數,call以及apply來調整this指向,也可在this指向變化之前保存為另一變量
                if(document.all) {
                    //node.attachEvent("on" + eventType, handler);
                    node.attachEvent("on" + eventType, function() {
                        handler.apply(scope, arguments);
                    });
                } else {
                    //node.addEventListener(eventType, handler, false);
                    node.addEventListener(eventType, function() {
                        handler.apply(scope, arguments);
                    }, false);
                }
            }
            //面向對象
            function Tab(config) {
                this._parent = config.parent;
                this._currentClass = config.currentClass;
                var trigger = config.trigger || "click";
                this._handler = config.handler;
                var autoPlay = config.autoPlay;
                var playTime = config.playTime || 3000;
                this._menus = GLOBAL.dom.getElementsByClassName("J_tab-menu", this._parent);
                this._contents = GLOBAL.dom.getElementsByClassName("J_tab-content", this._parent);
                this.currentIndex = 0;
                var $this = this;
                if(autoPlay) {
                    setInterval(function() { $this._autoHandler(); }, playTime);
                }
                for (var i = 0, mLen = this._menus.length; i < mLen; i++) {
                    this._menus[i]._index = i;
                    GLOBAL.event.on(this._menus[i], trigger, function() {
                        $this.showItem(this._index);
                        this.currentIndex = this._index;
                    });
                }
            }
            Tab.prototype = {
                showItem: function(n) {
                    for(var i = 0, len = this._contents.length; i < len; i++) {
                        this._contents[i].style.display = "none";
                    }
                    this._contents[n].style.display = "block";
                    if(this._currentClass) {
                        var currentMenu = GLOBAL.dom.getElementsByClassName(this._currentClass, this._parent)[0];
                        if (currentMenu) {
                            GLOBAL.dom.removeClass(currentMenu, this._currentClass);
                        }
                        GLOBAL.dom.addClass(this._menus[n], this._currentClass);
                    }
                    //預留回調接口
                    if(this._handler) {
                        this._handler(n);
                    }
                },
                _autoHandler: function() {
                    this.currentIndex++;
                    if(this.currentIndex >= this._menus.length) {
                        this.currentIndex = 0;
                    }
                    this.showItem(this.currentIndex);
                }
            };
            var    tabs = GLOBAL.dom.getElementsByClassName("J_tab");
            new Tab({ parent: tabs[0], trigger: "mouseover" });
            new Tab({ parent: tabs[1], currentClass: "tab-currentMenu", autoPlay: true, playTime: 4000 });
            new Tab({ parent: tabs[2], currentClass: "tab-currentMenu2", trigger: "mouseover", handler: function (index) {
                    alert("你激活的是第" + (index + 1) + "個標簽");
                }
            });    
        </script>
    </body>
</html>
利用面向對象設計Rating
<html>
    <head>
    </head>
    <body>
        <h1>我的飯店</h1>
        <p>衛生:<p>
        <p class="J_rate">
            <img src="star_gray.png" title="很爛" />
            <img src="star_gray.png" title="一般" />
            <img src="star_gray.png" title="還好" />
            <img src="star_gray.png" title="較好" />
            <img src="star_gray.png" title="很好" />
        </p>
        <p>價格:<p>
        <p class="J_rate">
            <img src="star_gray.png" title="很爛" />
            <img src="star_gray.png" title="一般" />
            <img src="star_gray.png" title="還好" />
            <img src="star_gray.png" title="較好" />
            <img src="star_gray.png" title="很好" />
        </p>
        <p>味道:<p>
        <p class="J_rate">
            <img src="star_gray.png" title="很爛" />
            <img src="star_gray.png" title="一般" />
            <img src="star_gray.png" title="還好" />
            <img src="star_gray.png" title="較好" />
            <img src="star_gray.png" title="很好" />
        </p>
        <script type="text/javascript">
            var GLOBAL = {};
            GLOBAL.namespace = function (str) {
                var arr = str.split("."), o = GLOBAL;
                for (i = (arr[0] == "oa") ? 1 : 0; i < arr.length; i++) {
                    o[arr[i]] = o[arr[i]] || {};
                }
                o = o[arr[i]];
            };
            GLOBAL.namespace("dom");
            GLOBAL.dom.getElementsByClassName = function(str, parent, tag) {
                if(parent) {
                    parent = typeof parent == "string" ? document.getElementById(parent) : parent;
                } else {
                    parent = document.body;
                }
                tag = tag || "*";
                var elems = parent.getElementsByTagName(tag);
                var array = [];
                for(var i = 0, len = elems.length; i < len; i++) {
                    for (var j = 0, k = elems[i].className.split(" "), l = k.length; j < l; j++) { 
                        if(k[j]==str) {
                            array.push(elems[i]);
                            break;
                        }
                    }
                }
                return array;
            }
            GLOBAL.namespace("event");
            GLOBAL.event.on = function(node, eventType, handler, scope) {
                node = typeof node == "string" ? document.getElementById(node) : node;
                scope = scope || node;
                //可通過匿名函數,call以及apply來調整this指向,也可在this指向變化之前保存為另一變量
                if(document.all) {
                    //node.attachEvent("on" + eventType, handler);
                    node.attachEvent("on" + eventType, function() {
                        handler.apply(scope, arguments);
                    });
                } else {
                    //node.addEventListener(eventType, handler, false);
                    node.addEventListener(eventType, function() {
                        handler.apply(scope, arguments);
                    }, false);
                }
            }
            //Rating
            function Rating(parent) {
                var rate = typeof parent == "string" ? document.getElementById(parent) : parent;
                var items = rate.getElementsByTagName("img");
                var imgs = ["star_gray.png", "star_gold.png"];
                var flag;
                for(var i = 0, len = items.length; i < len; i++) {
                    items[i]._index = i;
                    GLOBAL.event.on(items[i], "mouseover", function(e) {
                        if(flag) return;
                        var target = e.target || e.srcElement;
                        if(target.tagName.toLowerCase() != "img") return;
                        for(var j = 0; j < len; j++) {
                            if(j <= this._index) {
                                items[j].src = imgs[1];
                            } else {
                                items[j].src = imgs[0];
                            }
                        }
                    });
                    GLOBAL.event.on(items[i], "mouseout", function() {
                        if(flag) return;
                        for(var j = 0; j < len; j++) {
                             items[j].src = imgs[0];
                        }
                    });
                    GLOBAL.event.on(items[i], "click", function() {
                        if(flag) return;
                        flag = true;
                        alert("你的分數為:" + (this._index + 1) +"," + this.title);
                    });
                }
            }
            var rates = GLOBAL.dom.getElementsByClassName("J_rate");
            for(var i = 0, len = rates.length; i < len; i++) {
                new Rating(rates[i]);
            }
        </script>
    </body>
</html>

可以參照完整版代碼去查看整個重構的完整過程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM