歡迎光臨
每天分享高質量文章

編寫更加穩定/可讀的Javascript代碼

每個人都有自己的編程風格,也無可避免的要去感受別人的編程風格——修改別人的代碼。”修改別人的代碼”對於我們來說的一件很痛苦的事情。因為有些代碼並不是那麼容易閱讀、可維護的,讓另一個人來修改別人的代碼,或許最終只會修改一個變數,調整一個函式的呼叫時機,卻需要花上1個小時甚至更多的時間來閱讀、縷清別人的代碼。本文一步步帶你重構一段獲取位置的”組件”——提升你的javascript代碼的可讀性和穩定性。

本文內容如下:

  • 分離你的javascript代碼

  • 函式不應該過分依賴外部環境

  • 語意化和復用

  • 組件應該關註邏輯,行為只是封裝

  • 形成自己的風格的代碼

分離你的javascript代碼

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

(function (window, namespace) {

var $ = window.jQuery;

window[namespace] = function (targetId, textId) {

//一個嘗試復用的獲取位置的”組件”

var $target = $(‘#’ + targetId),//按鈕

$text = $(‘#’ + textId);//顯示文本

$target.on(‘click’, function () {

$text.html(‘獲取中’);

var data = ‘北京市’;//balabala很多邏輯,偽代碼,獲取得到位置中

if (data) {

$text.html(data);

} else

$text.html(‘獲取失敗’);

});

}

})(window, ‘linkFly’);

這一段代碼,我們暫且認可它已經構成一個”組件”。

上面的代碼就是典型的一個方法搞定所有事情,一旦填充上內部的邏輯就會變得生活不能自理,而一旦增加需求,例如獲取位置傳回的資料格式需要加工,那麼就要去裡面尋找處理資料的代碼然後修改。

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

(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’);

函式不應該過分依賴外部環境

上面的代碼中,我們已經把整個組件,切割成了各種函式(註意這裡我說的是函式,不是方法),這裡常出現一個新的問題:函式過分依賴不可控的變數。

變數$target和$text身為環境中的全域性變數,從組件初始化便賦值,而我們切割後的代碼大多數的操作方法都依賴$text,尤其是$text和done()、fail()之間曖昧的關係,一旦$text相關的結構、邏輯改變,那麼我們的代碼將會進行不小的改動。

和頁面/DOM相關的都是不可信賴的(例如$target和$text),一旦頁面結構發生改變,它的行為很大程度上也會隨之改變。而函式也不應該依賴外部的環境。

在不可控的變數上,我們應該解開函式和依賴變數上的關係,讓函式變得更加專註自己區域的邏輯,更加的純粹。簡單的說:函式所依賴的外部變數,都應該通過引數傳遞到函式內部。

新的代碼如下:

(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’);

語意化和復用

變數states是一個陣列,它描述的行為難以閱讀,每次看到states[0]都有一種分分鐘想捏死原作者的衝動,因為我們總是要記住變數states的值,在代碼上,我們應該盡可能讓它可以很好的被閱讀。

另外,上面的代碼中$text.html就是典型的代碼重覆,我們再一次的修改代碼,請註意這一次修改的代碼中,我們所抽離的changeStateText()的代碼位置,它並沒有被提升到上一層環境中(也就是整個大閉包的環境)。

(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’);

提及語意化,我們必須要知道當前整個代碼的邏輯和語意:

在這整個組件中,所有的函式模塊可以分為:工具和工具提供者。

上一層環境(整個大閉包)在我們的業務中扮演著工具的身份,它的任務是締造一套和獲取位置邏輯相關的工具,而在window[namespace])函式中,則是工具提供者的身份,它是唯一的入口,負責提供組件完整的業務給工具的使用者。

這裡的$text.html()在邏輯上並不屬於工具,而是屬於工具提供者使用工具後所得到的反饋,所以changeStateText()函式置於工具提供者window[namespace]()中。

組件應該關註邏輯,行為只是封裝

到此為止,我們分離了函式,並讓這個組件擁有了良好的語意。但這時候來了新的需求:當沒有獲取到位置的時候,需要進行一些其他的操作。這時候會發現,我們需要window[namespace]()上加上新的引數。

當我們加上新的引數之後,又被告知新的需求:當獲取位置失敗了之後,需要修改一些信息,然後再次嘗試獲取位置信息。

不過幸好,我們的代碼已經把大部分的邏輯抽離到了工具提供者中了,對整個工具的邏輯影響並不大。

同時我們再看看代碼就會發現我們的組件除了工具提供者之外,沒有方法(依賴在物件上的函式)。也就是說,我們的組件並沒有物件。

我見過很多人的代碼總是喜歡打造工具提供者,而忽略了工具的本質。迎合上面的增加的需求,那麼我們的工具提供者將會變得越來越重,這時候我們應該思考到:是不是應該把工具提供出去?

讓我們回到最初的需求——僅僅只是一個獲取位置的組件,沒錯,它的核心業務就是獲取位置——它不應該被組件化。它的本質應該是個工具物件,而不應該和頁面相關,我們從一開始就不應該關註頁面上的變化,讓我們重構代碼如下:

(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’);

在這裡,我們直接捏死了工具提供者,我們直接將工具提供給外面的工具使用者,讓工具使用者直接使用我們的工具,這裡的代碼無關狀態、無關頁面。

至此,重構完成。

形成自己風格的代碼

之所以講這個是因為大家都有自己的編程風格。有些人的編程風格就是開篇那種代碼的…

我覺得形成自己的編程風格,是建立在良好代碼的和結構/語意上的。否則只會讓你的代碼變得越來越難讀,越來越難寫。

****

單var和多var


我個人是喜歡單var風格的,不過我覺得代碼還是盡可能在使用某一方法/函式使用前進行var,有時候甚至於為了單var而變得喪心病狂:由於我又過分的喜愛函式運算式宣告,函式運算式宣告並不會在var陳述句中執行,於是偶爾會出現這種邊宣告邊執行的代碼,為了不教壞小朋友就不貼代碼了(我不會告訴你們其實是我找不到了)。

物件屬性的屏蔽

下麵的代碼演示了兩種物件的構建,後一種通過閉包把內部屬性隱藏,同樣,兩種方法都實現了無new化,我個人…是不喜歡看見很多this的..但還是推薦前者。

(function () {

//第一種,曝露了_name屬性

var Demo = function () {

if (!(this instanceof Demo))

return new Demo();

this._name = ‘linkFly’;

};

Demo.prototype.getName = function () {

return this._name;

}

//第二種,多一層閉包意味記憶體消耗更大,但是屏蔽了_name屬性

var Demo = function () {

var name = ‘linkFly’;

return {

getName: function () {

return name;

}

}

}

});

巧用變數置頂[hoisting]


巧用函式宣告的變數置頂特性意味著處女座心態的你放棄單var,但卻可以讓你的函式在代碼結構上十分清晰,例如下麵的代碼:

(function () {

var names = [];

return function (name) {

addName(name);

}

function addName(name) {

if (!~names.indexOf(name))//如果存在則不添加

names.push(name);

console.log(names);// [“linkFly”]

}

}())(‘linkFly’);

if和&&

這種代碼,在幾個群里都見過討論:

(function () {

var key = ‘linkFly’,

cache = { ‘linkFly’: ‘http://www.cnblogs.com/silin6/’ },

value;

//&&到底

key && cache && cache[key] && (value = cache[key]);

//來個if

if (key && cache && cache[key])

value = cache[key];

})();

大概就想到這麼些了,我突然發現我不太推薦的代碼,都是我寫的代碼,囧。如果各位也還有更多有趣的代碼,希望各位看官能掏出來讓小弟見識見識。

來自:linkFly的博客

鏈接:http://www.cnblogs.com/silin6/p/4273511.html

赞(0)

分享創造快樂