每個(gè)人都有自己的編程風(fēng)格,也無(wú)可避免的要去感受別人的編程風(fēng)格——修改別人的代碼。”修改別人的代碼”對(duì)于我們來(lái)說(shuō)的一件很痛苦的事情。因?yàn)橛行┐a并不是那么容易閱讀、可維護(hù)的,讓另一個(gè)人來(lái)修改別人的代碼,或許最終只會(huì)修改一個(gè)變量,調(diào)整一個(gè)函數(shù)的調(diào)用時(shí)機(jī),卻需要花上1個(gè)小時(shí)甚至更多的時(shí)間來(lái)閱讀、縷清別人的代碼。本文一步步帶你重構(gòu)一段獲取位置的”組件”——提升你的javascript代碼的可讀性和穩(wěn)定性。

  本文內(nèi)容如下:

 分離你的javascript代碼

  下面一段代碼演示了難以閱讀/修改的代碼:

(function (window, namespace) {
    var $ = window.jQuery;
    window[namespace] = function (targetId, textId) {
        //一個(gè)嘗試復(fù)用的獲取位置的"組件"
        var $target = $('#' + targetId),//按鈕
            $text = $('#' + textId);//顯示文本
        $target.on('click', function () {
            $text.html('獲取中');
            var data = '北京市';//balabala很多邏輯,偽代碼,獲取得到位置中
            if (data) {
                $text.html(data);
            } else
                $text.html('獲取失敗');
        });
    }
})(window, 'linkFly');

  這一段代碼,我們暫且認(rèn)可它已經(jīng)構(gòu)成一個(gè)”組件”。
上面的代碼就是典型的一個(gè)方法搞定所有事情,一旦填充上內(nèi)部的邏輯就會(huì)變得生活不能自理,而一旦增加需求,例如獲取位置返回的數(shù)據(jù)格式需要加工,那么就要去里面尋找處理數(shù)據(jù)的代碼然后修改。

  我們分離一下邏輯,得到代碼如下:

(function (window, namespace) {
    var $ = window.jQuery,
        $target,
        $text,
        states= ['獲取中', '獲取失敗'];
    function done(address) {//獲取位置成功
        $text.html(address);
    }
    function fail() {
        $text.html(states[1]);
    }
    function checkData(data) {
        //檢查位置信息是否正確
        return !!data;
    }
    function loadPosition() {
        var data = '北京市';//獲取位置中
        if (checkData(data)) {
            done(data);
        } else
            fail();
    }
    var init = function () {
        $target.on('click', function () {
            $text.html(states[0]);
            loadPosition();
        });
    };
    window[namespace] = function (targetId, textId) {
        $target = $('#' + targetId);
        $text = $('#' + textId);
        initData();
        setData();
    }
})(window, 'linkFly');

 函數(shù)不應(yīng)該過(guò)分依賴外部環(huán)境

  上面的代碼中,我們已經(jīng)把整個(gè)組件,切割成了各種函數(shù)(注意這里我說(shuō)的是函數(shù),不是方法),這里常出現(xiàn)一個(gè)新的問(wèn)題:函數(shù)過(guò)分依賴不可控的變量。

  變量$target和$text身為環(huán)境中的全局變量,從組件初始化便賦值,而我們切割后的代碼大多數(shù)的操作方法都依賴$text,尤其是$text和done()、fail()之間曖昧的關(guān)系,一旦$text相關(guān)的結(jié)構(gòu)、邏輯改變,那么我們的代碼將會(huì)進(jìn)行不小的改動(dòng)。

  和頁(yè)面/DOM相關(guān)的都是不可信賴的(例如$target和$text),一旦頁(yè)面結(jié)構(gòu)發(fā)生改變,它的行為很大程度上也會(huì)隨之改變。而函數(shù)也不應(yīng)該依賴外部的環(huán)境。
在不可控的變量上,我們應(yīng)該解開(kāi)函數(shù)和依賴變量上的關(guān)系,讓函數(shù)變得更加專注自己區(qū)域的邏輯,更加的純粹。簡(jiǎn)單的說(shuō):函數(shù)所依賴的外部變量,都應(yīng)該通過(guò)參數(shù)傳遞到函數(shù)內(nèi)部。
新的代碼如下:

(function (window, namespace) {
    var $ = window.jQuery;
    //檢查位置信息是否正確
    function checkData(data) {
        return !!data;
    }
    //獲取位置中
    function loadPosition(done, fail) {
        var data = '北京市';//獲取位置中
        if (checkData(data)) {
            done(data);
        } else
            fail();
    }
    window[namespace] = function (targetId, textId) {
       var  $target = $('#' + targetId),
            $text = $('#' + textId);
        var states = ['獲取中', '獲取失敗'];
        $target.on('click', function () {
            $text.html(states[0]);
            loadPosition(function (address) {//獲取位置成功
                $text.html(address);
            }, function () {//獲取位置失敗
                $text.html(states[1]);
            });
        });
    }
})(window, 'linkFly');

 語(yǔ)義化和復(fù)用

  變量states是一個(gè)數(shù)組,它描述的行為難以閱讀,每次看到states[0]都有一種分分鐘想捏死原作者的沖動(dòng),因?yàn)槲覀兛偸且涀∽兞縮tates的值,在代碼上,我們應(yīng)該盡可能讓它可以很好的被閱讀。

  另外,上面的代碼中$text.html就是典型的代碼重復(fù),我們?cè)僖淮蔚男薷拇a,請(qǐng)注意這一次修改的代碼中,我們所抽離的changeStateText()的代碼位置,它并沒(méi)有被提升到上一層環(huán)境中(也就是整個(gè)大閉包的環(huán)境)。

(function (window, namespace) {
    var $ = window.jQuery;
    function checkData(data) {
        return !!data;
    }
    function loadPosition(done, fail) {
        var data = '北京市';//獲取位置中
        if (checkData(data)) {
            done(data);
        } else
            fail();
    }
    window[namespace] = function (targetId, textId) {
        var $target = $('#' + targetId),
            $text = $('#' + textId),
            changeEnum = { LOADING: '獲取中', FAIL: '獲取失敗' },
            changeStateText = function (text) {
                $text.html(text);
            };
        $target.on('click', function () {
            changeStateText(changeEnum.LOADING);
            loadPosition(function (address) {
                changeStateText(address);
            }, function () {
                changeStateText(changeEnum.FAIL);
            });
        });
    }
})(window, 'linkFly');

  提及語(yǔ)義化,我們必須要知道當(dāng)前整個(gè)代碼的邏輯和語(yǔ)義:

  在這整個(gè)組件中,所有的函數(shù)模塊可以分為:工具和工具提供者。

  上一層環(huán)境(整個(gè)大閉包)在我們的業(yè)務(wù)中扮演著工具的身份,它的任務(wù)是締造一套和獲取位置邏輯相關(guān)的工具,而在window[namespace])函數(shù)中,則是工具提供者的身份,它是唯一的入口,負(fù)責(zé)提供組件完整的業(yè)務(wù)給工具的使用者。
這里的$text.html()在邏輯上并不屬于工具,而是屬于工具提供者使用工具后所得到的反饋,所以changeStateText()函數(shù)置于工具提供者window[namespace]()中。

 組件應(yīng)該關(guān)注邏輯,行為只是封裝

  到此為止,我們分離了函數(shù),并讓這個(gè)組件擁有了良好的語(yǔ)義。但這時(shí)候來(lái)了新的需求:當(dāng)沒(méi)有獲取到位置的時(shí)候,需要進(jìn)行一些其他的操作。這時(shí)候會(huì)發(fā)現(xiàn),我們需要window[namespace]()上加上新的參數(shù)。

  當(dāng)我們加上新的參數(shù)之后,又被告知新的需求:當(dāng)獲取位置失敗了之后,需要修改一些信息,然后再次嘗試獲取位置信息。

  不過(guò)幸好,我們的代碼已經(jīng)把大部分的邏輯抽離到了工具提供者中了,對(duì)整個(gè)工具的邏輯影響并不大。

  同時(shí)我們?cè)倏纯创a就會(huì)發(fā)現(xiàn)我們的組件除了工具提供者之外,沒(méi)有方法(依賴在對(duì)象上的函數(shù))。也就是說(shuō),我們的組件并沒(méi)有對(duì)象。

  我見(jiàn)過(guò)很多人的代碼總是喜歡打造工具提供者,而忽略了工具的本質(zhì)。迎合上面的增加的需求,那么我們的工具提供者將會(huì)變得越來(lái)越重,這時(shí)候我們應(yīng)該思考到:是不是應(yīng)該把工具提供出去?

  讓我們回到最初的需求——僅僅只是一個(gè)獲取位置的組件,沒(méi)錯(cuò),它的核心業(yè)務(wù)就是獲取位置——它不應(yīng)該被組件化。它的本質(zhì)應(yīng)該是個(gè)工具對(duì)象,而不應(yīng)該和頁(yè)面相關(guān),我們從一開(kāi)始就不應(yīng)該關(guān)注頁(yè)面上的變化,讓我們重構(gòu)代碼如下:

(function (window, namespace) {
    var Gps = {
        load: function (fone, fail) {
            var data = '北京市';//獲取位置偽代碼
            this.check(data) ?
                done(data, Gps.state.OK) :
                fail(Gps.state.FAIL);
        },
        check: function (data) {
            return !!data;
        },
        state: { OK: 1, FAIL: 0 }
    };
    window[namespace] = Gps;
})(window, 'Gps');

  在這里,我們直接捏死了工具提供者,我們直接將工具提供給外面的工具使用者,讓工具使用者直接使用我們的工具,這里的代碼無(wú)關(guān)狀態(tài)、無(wú)關(guān)頁(yè)面。

  至此,重構(gòu)完成。

 形成自己風(fēng)格的代碼

  之所以講這個(gè)是因?yàn)榇蠹叶加凶约旱木幊田L(fēng)格。有些人的編程風(fēng)格就是開(kāi)篇那種代碼的…

  我覺(jué)得形成自己的編程風(fēng)格,是建立在良好代碼的和結(jié)構(gòu)/語(yǔ)義上的。否則只會(huì)讓你的代碼變得越來(lái)越難讀,越來(lái)越難寫(xiě)。

  ****

  單var和多var

  我個(gè)人是喜歡單var風(fēng)格的,不過(guò)我覺(jué)得代碼還是盡可能在使用某一方法/函數(shù)使用前進(jìn)行var,有時(shí)候甚至于為了單var而變得喪心病狂:由于我又過(guò)分的喜愛(ài)函數(shù)表達(dá)式聲明,函數(shù)表達(dá)式聲明并不會(huì)在var語(yǔ)句中執(zhí)行,于是偶爾會(huì)出現(xiàn)這種邊聲明邊執(zhí)行的代碼,為了不教壞小朋友就不貼代碼了(我不會(huì)告訴你們其實(shí)是我找不到了)。

  對(duì)象屬性的屏蔽

  下面的代碼演示了兩種對(duì)象的構(gòu)建,后一種通過(guò)閉包把內(nèi)部屬性隱藏,同樣,兩種方法都實(shí)現(xiàn)了無(wú)new化,我個(gè)人…是不喜歡看見(jiàn)很多this的..但還是推薦前者。

(function () {
    //第一種,曝露了_name屬性
    var Demo = function () {
        if (!(this instanceof Demo))
            return new Demo();
        this._name = 'linkFly';
    };
    Demo.prototype.getName = function () {
        return this._name;
    }

    //第二種,多一層閉包意味內(nèi)存消耗更大,但是屏蔽了_name屬性
    var Demo = function () {
        var name = 'linkFly';
        return {
            getName: function () {
                return name;
            }
        }
    }
});

  巧用變量置頂[hoisting]

  巧用函數(shù)聲明的變量置頂特性意味著處女座心態(tài)的你放棄單var,但卻可以讓你的函數(shù)在代碼結(jié)構(gòu)上十分清晰,例如下面的代碼:

(function () {
    var names = [];
    return function (name) {
        addName(name);
    }
    function addName(name) {
        if (!~names.indexOf(name))//如果存在則不添加
            names.push(name);
        console.log(names);// ["linkFly"]
    }
}())('linkFly');

  if和&&

  這種代碼,在幾個(gè)群里都見(jiàn)過(guò)討論:

(function () {
    var key = 'linkFly',
        cache = { 'linkFly': 'http://www.cnblogs.com/silin6/' },
        value;
    //&&到底
    key && cache && cache[key] && (value = cache[key]);
    //來(lái)個(gè)if
    if (key && cache && cache[key])
        value = cache[key];
})();

  大概就想到這么些了,我突然發(fā)現(xiàn)我不太推薦的代碼,都是我寫(xiě)的代碼,囧。如果各位也還有更多有趣的代碼,希望各位看官能掏出來(lái)讓小弟見(jiàn)識(shí)見(jiàn)識(shí)。

  哈爾濱品用軟件有限公司致力于為哈爾濱的中小企業(yè)制作大氣、美觀的優(yōu)秀網(wǎng)站,并且能夠搭建符合百度排名規(guī)范的網(wǎng)站基底,使您的網(wǎng)站無(wú)需額外費(fèi)用,即可穩(wěn)步提升排名至首頁(yè)。歡迎體驗(yàn)最佳的哈爾濱網(wǎng)站建設(shè)。