前言
在我們平時的工作開發中,大多數都是大人協同開發的公共專案;在我們平時開發中程式碼codeing的時候我們考慮程式碼的可讀性
、複用性
和擴充套件性
。
乾淨的程式碼,既在質量上較為可靠,也為後期維護、升級奠定了良好基礎。
我們從以下幾個方面進行探討:
變數
1、變數命名
一般我們在定義變數是要使用有意義的詞彙命令,要做到見面知義
//bad code
const yyyymmdstr = moment().format('YYYY/MM/DD');
//better code
const currentDate = moment().format('YYYY/MM/DD');
2、可描述
通過一個變數生成了一個新變數,也需要為這個新變數命名,也就是說每個變數當你看到他第一眼你就知道他是幹什麼的。
//bad code
const ADDRESS = 'One Infinite Loop, Cupertino 95014';
const CITY_ZIP_CODE_REGEX = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(ADDRESS.match(CITY_ZIP_CODE_REGEX)[1], ADDRESS.match(CITY_ZIP_CODE_REGEX)[2]);
//better code
const ADDRESS = 'One Infinite Loop, Cupertino 95014';
const CITY_ZIP_CODE_REGEX = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [, city, zipCode] = ADDRESS.match(CITY_ZIP_CODE_REGEX) || [];
saveCityZipCode(city, zipCode);
3、形參命名
在for、forEach、map的迴圈中我們在命名時要直接
//bad code
const locations = ['Austin', 'New York', 'San Francisco'];
locations.map((l) => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// 需要看其他程式碼才能確定 'l' 是幹什麼的。
dispatch(l);
});
//better code
const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((location) => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch(location);
});
4、避免無意義的字首
例如我們只建立一個物件是,沒有必要再把每個物件的屬性上再加上物件名
//bad code
const car = {
carMake: 'Honda',
carModel: 'Accord',
carColor: 'Blue'
};
function paintCar(car) {
car.carColor = 'Red';
}
//better code
const car = {
make: 'Honda',
model: 'Accord',
color: 'Blue'
};
function paintCar(car) {
car.color = 'Red';
}
5、預設值
//bad code
function createMicrobrewery(name) {
const breweryName = name || 'Hipster Brew Co.';
// ...
}
//better code
function createMicrobrewery(name = 'Hipster Brew Co.') {
// ...
}
函式
1、引數
一般引數多的話要使用ES6的解構傳參的方式
//bad code
function createMenu(title, body, buttonText, cancellable) {
// ...
}
//better code
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
//better code
createMenu({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
});
2、單一化處理
一個方法裡面最好只做一件事,不要過多的處理,這樣程式碼的可讀性非常高
//bad code
function emailClients(clients) {
clients.forEach((client) => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}
//better code
function emailActiveClients(clients) {
clients
.filter(isActiveClient)
.forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
3、物件設定預設屬性
//bad code
const menuConfig = {
title: null,
body: 'Bar',
buttonText: null,
cancellable: true
};
function createMenu(config) {
config.title = config.title || 'Foo';
config.body = config.body || 'Bar';
config.buttonText = config.buttonText || 'Baz';
config.cancellable = config.cancellable !== undefined ? config.cancellable : true;
}
createMenu(menuConfig);
//better code
const menuConfig = {
title: 'Order',
// 'body' key 缺失
buttonText: 'Send',
cancellable: true
};
function createMenu(config) {
config = Object.assign({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}, config);
// config 就變成了: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);
4、避免副作用
函式接收一個值返回一個新值,除此之外的行為我們都稱之為副作用,比如修改全域性變數、對檔案進行 IO 操作等。
當函式確實需要副作用時,比如對檔案進行 IO 操作時,請不要用多個函式/類進行檔案操作,有且僅用一個函式/類來處理。也就是說副作用需要在唯一的地方處理。
副作用的三大天坑:隨意修改可變資料型別、隨意分享沒有資料結構的狀態、沒有在統一地方處理副作用。
//bad code
// 全域性變數被一個函式引用
// 現在這個變數從字串變成了陣列,如果有其他的函式引用,會發生無法預見的錯誤。
var name = 'Ryan McDermott';
function splitIntoFirstAndLastName() {
name = name.split(' ');
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];
//better code
var name = 'Ryan McDermott';
var newName = splitIntoFirstAndLastName(name)
function splitIntoFirstAndLastName(name) {
return name.split(' ');
}
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];
在 JavaScript 中,基本型別通過賦值傳遞,物件和陣列通過引用傳遞。以引用傳遞為例:
假如我們寫一個購物車,通過 addItemToCart()
方法新增商品到購物車,修改 購物車陣列
。此時呼叫 purchase()
方法購買,由於引用傳遞,獲取的 購物車陣列
正好是最新的資料。
看起來沒問題對不對?
如果當使用者點選購買時,網路出現故障, purchase()
方法一直在重複呼叫,與此同時使用者又新增了新的商品,這時網路又恢復了。那麼 purchase()
方法獲取到 購物車陣列
就是錯誤的。
為了避免這種問題,我們需要在每次新增商品時,克隆 購物車陣列
並返回新的陣列。
//bad code
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};
//better code
const addItemToCart = (cart, item) => {
return [...cart, {item, date: Date.now()}]
};
5、全域性方法
在 JavaScript 中,永遠不要汙染全域性,會在生產環境中產生難以預料的 bug。舉個例子,比如你在 Array.prototype
上新增一個 diff
方法來判斷兩個陣列的不同。而你同事也打算做類似的事情,不過他的 diff
方法是用來判斷兩個陣列首位元素的不同。很明顯你們方法會產生衝突,遇到這類問題我們可以用 ES2015/ES6 的語法來對 Array
進行擴充套件。
//bad code
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};
//better code
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}
6、避免型別檢查
JavaScript 是無型別的,意味著你可以傳任意型別引數,這種自由度很容易讓人困擾,不自覺的就會去檢查型別。仔細想想是你真的需要檢查型別還是你的 API 設計有問題?
//bad code
function travelToTexas(vehicle) {
if (vehicle instanceof Bicycle) {
vehicle.pedal(this.currentLocation, new Location('texas'));
} else if (vehicle instanceof Car) {
vehicle.drive(this.currentLocation, new Location('texas'));
}
}
//better code
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location('texas'));
}
如果你需要做靜態型別檢查,比如字串、整數等,推薦使用 TypeScript,不然你的程式碼會變得又臭又長。
//bad code
function combine(val1, val2) {
if (typeof val1 === 'number' && typeof val2 === 'number' ||
typeof val1 === 'string' && typeof val2 === 'string') {
return val1 + val2;
}
throw new Error('Must be of type String or Number');
}
//better code
function combine(val1, val2) {
return val1 + val2;
}
複雜條件判斷
我們編寫js程式碼時經常遇到複雜邏輯判斷的情況,通常大家可以用if/else或者switch來實現多個條件判斷,但這樣會有個問題,隨著邏輯複雜度的增加,程式碼中的if/else/switch會變得越來越臃腫,越來越看不懂,那麼如何更優雅的寫判斷邏輯
1、if/else
點選列表按鈕事件
/**
* 按鈕點選事件
* @param {number} status 活動狀態:1 開團進行中 2 開團失敗 3 商品售罄 4 開團成功 5 系統取消
*/
const onButtonClick = (status)=>{
if(status == 1){
sendLog('processing')
jumpTo('IndexPage')
}else if(status == 2){
sendLog('fail')
jumpTo('FailPage')
}else if(status == 3){
sendLog('fail')
jumpTo('FailPage')
}else if(status == 4){
sendLog('success')
jumpTo('SuccessPage')
}else if(status == 5){
sendLog('cancel')
jumpTo('CancelPage')
}else {
sendLog('other')
jumpTo('Index')
}
}
從上面我們可以看到的是通過不同的狀態來做不同的事情,程式碼看起來非常不好看,大家可以很輕易的提出這段程式碼的改寫方案,switch出場:
2、switch/case
/**
* 按鈕點選事件
* @param {number} status 活動狀態:1 開團進行中 2 開團失敗 3 商品售罄 4 開團成功 5 系統取消
*/
const onButtonClick = (status)=>{
switch (status){
case 1:
sendLog('processing')
jumpTo('IndexPage')
break
case 2:
case 3:
sendLog('fail')
jumpTo('FailPage')
break
case 4:
sendLog('success')
jumpTo('SuccessPage')
break
case 5:
sendLog('cancel')
jumpTo('CancelPage')
break
default:
sendLog('other')
jumpTo('Index')
break
}
}
這樣看起來比if/else清晰多了,細心的同學也發現了小技巧,case 2和case 3邏輯一樣的時候,可以省去執行語句和break,則case 2的情況自動執行case 3的邏輯。
3、存放到Object
將判斷條件作為物件的屬性名,將處理邏輯作為物件的屬性值,在按鈕點選的時候,通過物件屬性查詢的方式來進行邏輯判斷,這種寫法特別適合一元條件判斷的情況。
const actions = {
'1': ['processing','IndexPage'],
'2': ['fail','FailPage'],
'3': ['fail','FailPage'],
'4': ['success','SuccessPage'],
'5': ['cancel','CancelPage'],
'default': ['other','Index'],
}
/**
* 按鈕點選事件
* @param {number} status 活動狀態:1開團進行中 2開團失敗 3 商品售罄 4 開團成功 5 系統取消
*/
const onButtonClick = (status)=>{
let action = actions[status] || actions['default'],
logName = action[0],
pageName = action[1]
sendLog(logName)
jumpTo(pageName)
}
4、存放到Map
const actions = new Map([
[1, ['processing','IndexPage']],
[2, ['fail','FailPage']],
[3, ['fail','FailPage']],
[4, ['success','SuccessPage']],
[5, ['cancel','CancelPage']],
['default', ['other','Index']]
])
/**
* 按鈕點選事件
* @param {number} status 活動狀態:1 開團進行中 2 開團失敗 3 商品售罄 4 開團成功 5 系統取消
*/
const onButtonClick = (status)=>{
let action = actions.get(status) || actions.get('default')
sendLog(action[0])
jumpTo(action[1])
}
這樣寫用到了es6裡的Map物件,是不是更爽了?Map物件和Object物件有什麼區別呢?
- 一個物件通常都有自己的原型,所以一個物件總有一個"prototype"鍵。
- 一個物件的鍵只能是字串或者Symbols,但一個Map的鍵可以是任意值。
- 你可以通過size屬性很容易地得到一個Map的鍵值對個數,而物件的鍵值對個數只能手動確認。
程式碼風格
常量大寫
//bad code
const DAYS_IN_WEEK = 7;
const daysInMonth = 30;
const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];
function eraseDatabase() {}
function restore_database() {}
class animal {}
class Alpaca {}
//better code
const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;
const SONGS = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const ARTISTS = ['ACDC', 'Led Zeppelin', 'The Beatles'];
function eraseDatabase() {}
function restoreDatabase() {}
class Animal {}
class Alpaca {}
先宣告後呼叫
//bad code
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
lookupPeers() {
return db.lookup(this.employee, 'peers');
}
lookupManager() {
return db.lookup(this.employee, 'manager');
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getManagerReview() {
const manager = this.lookupManager();
}
getSelfReview() {
// ...
}
}
const review = new PerformanceReview(employee);
review.perfReview();
//better code
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
lookupPeers() {
return db.lookup(this.employee, 'peers');
}
getManagerReview() {
const manager = this.lookupManager();
}
lookupManager() {
return db.lookup(this.employee, 'manager');
}
getSelfReview() {
// ...
}
}
const review = new PerformanceReview(employee);
review.perfReview();