現在寫代碼比以前好多了,代碼的格式都有 eslint、prettier、babel(寫新版語法) 這些來保證,然而,技術手段再高端都不能解決代碼可讀性(代碼能否被未來的自己和同事看懂)的問題,因爲這個問題只有人自己才能解決。我們寫代碼要寫到下圖中左邊這樣基本上就功德圓滿了。
(1)變量數量的定義
拒絕:濫用變量
let kpi = 4; // 定義好了之後再也沒用過
function example() {
var a = 1;
var b = 2;
var c = a+b;
var d = c+1;
var e = d+a;
return e;
}
正確姿勢:: 數據只使用一次或不使用就無需裝到變量中
let kpi = 4; // 沒用的就刪除掉,不然過三個月自己都不敢刪,怕是不是那用到了
function example() {
var a = 1;
var b = 2;
return 2a+b+1;
}
(2) 變量的命名
拒絕:自我感覺良好的縮寫
let fName = 'jackie'; // 看起來命名挺規範,縮寫,駝峯法都用上,ESlint 各種檢測規範的工具都通過,But,fName 是啥?這時候,你是不是想說 What are you 弄啥呢?
let lName = 'willen'; // 這個問題和上面的一樣
正確姿勢:無需對每個變量都寫註釋,從名字上就看懂
let firstName = 'jackie'; // 怎麼樣,是不是一目瞭然。少被噴了一次
let lastName = 'willen';
(3) 特定的變量
拒絕:無說明的參數
if (value.length < 8) { // 爲什麼要小於 8,8 表示啥?長度,還是位移,還是高度?Oh,my God!!
....
}
正確姿勢:添加變量
const MAX_INPUT_LENGTH = 8;
if (value.length < MAX_INPUT_LENGTH) { // 一目瞭然,不能超過最大輸入長度
....
}
(4)變量的命名
拒絕:命名過於囉嗦
let nameString;
let theUsers;
YES: 做到簡潔明瞭
let name;
let users;
(5)使用說明性的變量 (即有意義的變量名)
正確姿勢:長代碼不知道啥意思
const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(
address.match(cityZipCodeRegex)[1], // 這個公式到底要幹嘛,對不起,原作者已經離職了。自己看代碼
address.match(cityZipCodeRegex)[2], // 這個公式到底要幹嘛,對不起,原作者已經離職了。自己看代碼
);
YES:用變量名來解釋長代碼的含義
const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);
(6)避免使用太多的全局變量
拒絕:在不同的文件不停的定義全局變量
name.js
window.name = 'a';
hello.js
window.name = 'b';
time.js
window.name = 'c'; // 三個文件的先後加載順序不同,都會使得 window.name 的值不同,同時,你對 window.name 的修改了都有可能不生效,因爲你不知道你修改過之後別人是不是又在別的說明文件中對其的值重置了。所以隨着文件的增多,會導致一團亂麻。
正確姿勢:少用或使用替代方案
你可以選擇只用局部變量。通過 (){}的方法。
如果你確實用很多的全局變量需要共享,你可以使用 vuex,redux 或者你自己參考 flux 模式寫一個也行。
(7) 變量的賦值
拒絕:對於求值獲取的變量,沒有兜底。
const MIN_NAME_LENGTH = 8;
let lastName = fullName[1];
if(lastName.length > MIN_NAME_LENGTH) { // 這樣你就給你的代碼成功的埋了一個坑,你有考慮過如果 fullName = ['jackie'] 這樣的情況嗎?這樣程序一跑起來就爆炸。要不你試試。
....
}
正確姿勢:對於求值變量,做好兜底。
const MIN_NAME_LENGTH = 8;
let lastName = fullName[1] || ''; // 做好兜底,fullName[1] 中取不到的時候,不至於賦值個 undefined, 至少還有個空字符,從根本上講,lastName 的變量類型還是 String,String 原型鏈上的特性都能使用,不會報錯。不會變成 undefined。
if(lastName.length > MIN_NAME_LENGTH) {
....
}
其實在項目中有很多求值變量,對於每個求值變量都需要做好兜底。
let propertyValue = Object.attr || 0; // 因爲 Object.attr 有可能爲空,所以需要兜底。
但是,賦值變量就不需要兜底了。
let a = 2; // 因爲有底了,所以不要兜着。
let myName = 'Tiny'; // 因爲有底了,所以不要兜着。
二、函數相關
(1)函數命名
拒絕:從命名無法知道返回值類型
function showFriendsList() {....} // 現在問,你知道這個返回的是一個數組,還是一個對象,還是 true or false。你能答的上來否?
正確姿勢:對於返回 true or false 的函數,最好以 should/is/can/has 開頭
function shouldShowFriendsList() {...}
function isEmpty() {...}
function canCreateDocuments() {...}
function hasLicense() {...}
(2) 功能函數最好爲純函數
拒絕: 不要讓功能函數的輸出變化無常
function plusAbc(a, b, c) { // 這個函數的輸出將變化無常,因爲 api 返回的值一旦改變,同樣輸入函數的 a,b,c 的值,但函數返回的結果卻不一定相同。
var c = fetch('../api');
return a+b+c;
}
正確姿勢:功能函數使用純函數,輸入一致,輸出結果永遠唯一
function plusAbc(a, b, c) { // 同樣輸入函數的 a,b,c 的值,但函數返回的結果永遠相同。
return a+b+c;
}
(3)函數傳參
拒絕:傳參無說明
page.getSVG(api, true, false); // true 和 false 啥意思,一目不了然
正確姿勢: 傳參有說明
page.getSVG({
imageApi: api,
includePageBackground: true, // 一目瞭然,知道這些 true 和 false 是啥意思
compress: false,
})
(4)動作函數要以動詞開頭
拒絕: 無法辨別函數意圖
function emlU(user) {
....
}
正確姿勢:動詞開頭,函數意圖就很明顯
function sendEmailToUser(user) {
....
}
(5)一個函數完成一個獨立的功能,不要一個函數混雜多個功能
這是軟件工程中最重要的一條規則,當函數需要做更多的事情時,它們將會更難進行編寫、測試、理解和組合。當你能將一個函數抽離出只完成一個動作,他們將能夠很容易的進行重構並且你的代碼將會更容易閱讀。如果你嚴格遵守本條規則,你將會領先於許多開發者。
拒絕:函數功能混亂,一個函數包含多個功能。最後就像能以一當百的老師傅一樣,被亂拳打死(亂拳(功能複雜函數)打死老師傅(老程序員))
function sendEmailToClients(clients) {
clients.forEach(client => {
const clientRecord = database.lookup(client)
if (clientRecord.isActive()) {
email(client)
}
})
}
正確姿勢: 功能拆解
function sendEmailToActiveClients(clients) { // 各個擊破,易於維護和複用
clients.filter(isActiveClient).forEach(email)
}
function isActiveClient(client) {
const clientRecord = database.lookup(client)
return clientRecord.isActive()
}
(6)優先使用命令式編程
拒絕: 使用 for 循環編程
for(i = 1; i <= 10; i++) { // 一看到 for 循環讓人頓生不想看的情愫
a[i] = a[i] +1;
}
正確姿勢:使用命令式編程
let b = a.map(item => ++item) // 怎麼樣,是不是很好理解,就是把 a 的值每項加一賦值給 b 就可以了。現在在 javascript 中幾乎所有的 for 循環都可以被 map,filter,find,some,any,forEach 等命令式編成取代。
(7) 函數中過多的採用 if else ..
拒絕: if else 過多
if (a === 1) {
...
} else if (a ===2) {
...
} else if (a === 3) {
...
} else {
...
}
正確姿勢: 可以使用 switch 替代或用數組替代
switch(a) {
case 1:
....
case 2:
....
case 3:
....
default:
....
}
Or
let handler = {
1: () => {....},
2: () => {....}.
3: () => {....},
default: () => {....}
}
handler[a]() || handler['default']()
儘量使用 ES6
(1)儘量使用箭頭函數
拒絕:採用傳統函數
function foo() {
// code
}
正確姿勢:使用箭頭函數
let foo = () => {
// code
}
(2)連接字符串
拒絕:採用傳統 + 號
var message = 'Hello ' + name + ', it\'s ' + time + ' now'
正確姿勢:採用模板字符
var message = `Hello ${name}, it's ${time} now`
(3) 使用解構賦值
拒絕:使用傳統賦值
var data = { name: 'dys', age: 1 };
var name = data.name;
var age = data.age;
var fullName = ['jackie', 'willen'];
var firstName = fullName[0];
var lastName = fullName[1];
正確姿勢:使用結構賦值
const data = {name:'dys', age:1};
const {name, age} = data; // 怎麼樣,是不是簡單明瞭
var fullName = ['jackie', 'willen'];
const [firstName, lastName] = fullName;
(4)儘量使用類 class
拒絕: 採用傳統的函數原型鏈實現繼承
典型的 ES5 的類 (function) 在繼承、構造和方法定義方面可讀性較差,當需要繼承時,優先選用 class。代碼太多,就省略了。
正確姿勢:採用 ES6 類實現繼承
class Animal {
constructor(age) {
this.age = age
}
move() {
/* ... */
}
}
class Mammal extends Animal {
constructor(age, furColor) {
super(age)
this.furColor = furColor
}
liveBirth() {
/* ... */
}
}
class Human extends Mammal {
constructor(age, furColor, languageSpoken) {
super(age, furColor)
this.languageSpoken = languageSpoken
}
speak() {
/* ... */
}
}
注:除了上述這些人爲習慣之外,就像前面提到的,對於機械性的,你可以使用 Babel、Eslint、Prettier 這些工具來保證代碼的格式一致。
有句話叫做“方法不對,努力白費”所有的前端大神都有自己的學習方法,而學web前端的學習也基本一致,而對於一個什麼都不懂的初學者,根本不會知道該怎麼學,這也是造成失敗的最直接原因。所以學web前端一定要有人指點。如果你處在迷茫期,找不到方向。可以加入我們的前端學習交流qun:731771211 。有任何不明白的東西隨時來問我。
點擊:加入