分離你的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,
status= ['獲取中', '獲取失敗'];
function done(address) {//獲取位置成功
$text.html(address);
}
function fail() {
$text.html(status[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(status[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 status = ['獲取中', '獲取失敗'];
$target.on('click', function () {
$text.html(status[0]);
loadPosition(function (address) {//獲取位置成功
$text.html(address);
}, function () {//獲取位置失敗
$text.html(status[1]);
});
});
}
})(window, 'linkFly');
語義化和復用
變量status是一個數組,它描述的行為難以閱讀,每次看到status[0]都有一種分分鍾想捏死原作者的沖動,因為我們總是要記住變量status的值,在代碼上,我們應該盡可能讓它可以很好的被閱讀。
另外,上面的代碼中$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];
})();
大概就想到這么些了,我突然發現我不太推薦的代碼,都是我寫的代碼,囧。如果各位也還有更多有趣的代碼,希望各位看官能掏出來讓小弟見識見識。
看完不要忘記推薦喲