溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何寫出高質(zhì)量的javascript代碼

發(fā)布時(shí)間:2020-06-15 15:56:24 來源:億速云 閱讀:138 作者:元一 欄目:web開發(fā)

目的

在我們平時(shí)的工作開發(fā)中,大多數(shù)都是大人協(xié)同開發(fā)的公共項(xiàng)目;在我們平時(shí)開發(fā)中代碼codeing的時(shí)候我們考慮代碼的可讀性、復(fù)用性和擴(kuò)展性。

干凈的代碼,既在質(zhì)量上較為可靠,也為后期維護(hù)、升級奠定了良好基礎(chǔ)。

我們從以下幾個(gè)方面進(jìn)行探討:

變量

1、變量命名

一般我們在定義變量是要使用有意義的詞匯命令,要做到見面知義

//bad code
const yyyymmdstr = moment().format('YYYY/MM/DD');
//better code
const currentDate = moment().format('YYYY/MM/DD');

2、可描述

通過一個(gè)變量生成了一個(gè)新變量,也需要為這個(gè)新變量命名,也就是說每個(gè)變量當(dāng)你看到他第一眼你就知道他是干什么的。

//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的循環(huán)中我們在命名時(shí)要直接

//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、避免無意義的前綴

例如我們只創(chuàng)建一個(gè)對象是,沒有必要再把每個(gè)對象的屬性上再加上對象名

//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、默認(rèn)值

//bad code
function createMicrobrewery(name) {
  const breweryName = name || 'Hipster Brew Co.';
  // ...
}

//better code
function createMicrobrewery(name = 'Hipster Brew Co.') {
  // ...
}

函數(shù)

1、參數(shù)

一般參數(shù)多的話要使用ES6的解構(gòu)傳參的方式

//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、單一化處理

一個(gè)方法里面最好只做一件事,不要過多的處理,這樣代碼的可讀性非常高

//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、對象設(shè)置默認(rèn)屬性

//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、避免副作用

函數(shù)接收一個(gè)值返回一個(gè)新值,除此之外的行為我們都稱之為副作用,比如修改全局變量、對文件進(jìn)行 IO 操作等。

當(dāng)函數(shù)確實(shí)需要副作用時(shí),比如對文件進(jìn)行 IO 操作時(shí),請不要用多個(gè)函數(shù)/類進(jìn)行文件操作,有且僅用一個(gè)函數(shù)/類來處理。也就是說副作用需要在唯一的地方處理。

副作用的三大天坑:隨意修改可變數(shù)據(jù)類型、隨意分享沒有數(shù)據(jù)結(jié)構(gòu)的狀態(tài)、沒有在統(tǒng)一地方處理副作用。

//bad code
// 全局變量被一個(gè)函數(shù)引用
// 現(xiàn)在這個(gè)變量從字符串變成了數(shù)組,如果有其他的函數(shù)引用,會發(fā)生無法預(yù)見的錯(cuò)誤。
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 中,基本類型通過賦值傳遞,對象和數(shù)組通過引用傳遞。以引用傳遞為例:

假如我們寫一個(gè)購物車,通過 addItemToCart()方法添加商品到購物車,修改 購物車數(shù)組。此時(shí)調(diào)用 purchase()方法購買,由于引用傳遞,獲取的 購物車數(shù)組正好是最新的數(shù)據(jù)。

看起來沒問題對不對?

如果當(dāng)用戶點(diǎn)擊購買時(shí),網(wǎng)絡(luò)出現(xiàn)故障, purchase()方法一直在重復(fù)調(diào)用,與此同時(shí)用戶又添加了新的商品,這時(shí)網(wǎng)絡(luò)又恢復(fù)了。那么 purchase()方法獲取到 購物車數(shù)組就是錯(cuò)誤的。

為了避免這種問題,我們需要在每次新增商品時(shí),克隆 購物車數(shù)組并返回新的數(shù)組。

//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 中,永遠(yuǎn)不要污染全局,會在生產(chǎn)環(huán)境中產(chǎn)生難以預(yù)料的 bug。舉個(gè)例子,比如你在 Array.prototype上新增一個(gè) diff方法來判斷兩個(gè)數(shù)組的不同。而你同事也打算做類似的事情,不過他的 diff方法是用來判斷兩個(gè)數(shù)組首位元素的不同。很明顯你們方法會產(chǎn)生沖突,遇到這類問題我們可以用 ES2015/ES6 的語法來對 Array進(jìn)行擴(kuò)展。

//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 是無類型的,意味著你可以傳任意類型參數(shù),這種自由度很容易讓人困擾,不自覺的就會去檢查類型。仔細(xì)想想是你真的需要檢查類型還是你的 API 設(shè)計(jì)有問題?

//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'));
}

如果你需要做靜態(tài)類型檢查,比如字符串、整數(shù)等,推薦使用 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;
}

復(fù)雜條件判斷

我們編寫js代碼時(shí)經(jīng)常遇到復(fù)雜邏輯判斷的情況,通常大家可以用if/else或者switch來實(shí)現(xiàn)多個(gè)條件判斷,但這樣會有個(gè)問題,隨著邏輯復(fù)雜度的增加,代碼中的if/else/switch會變得越來越臃腫,越來越看不懂,那么如何更優(yōu)雅的寫判斷邏輯

1、if/else

點(diǎn)擊列表按鈕事件

/**
 * 按鈕點(diǎn)擊事件
 * @param {number} status 活動狀態(tài):1 開團(tuán)進(jìn)行中 2 開團(tuán)失敗 3 商品售罄 4 開團(tuán)成功 5 系統(tǒng)取消
 */
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')
  }
}

從上面我們可以看到的是通過不同的狀態(tài)來做不同的事情,代碼看起來非常不好看,大家可以很輕易的提出這段代碼的改寫方案,switch出場:

2、switch/case

/**
 * 按鈕點(diǎn)擊事件
 * @param {number} status 活動狀態(tài):1 開團(tuán)進(jìn)行中 2 開團(tuán)失敗 3 商品售罄 4 開團(tuán)成功 5 系統(tǒng)取消
 */
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清晰多了,細(xì)心的同學(xué)也發(fā)現(xiàn)了小技巧,case 2和case 3邏輯一樣的時(shí)候,可以省去執(zhí)行語句和break,則case 2的情況自動執(zhí)行case 3的邏輯。

3、存放到Object

將判斷條件作為對象的屬性名,將處理邏輯作為對象的屬性值,在按鈕點(diǎn)擊的時(shí)候,通過對象屬性查找的方式來進(jìn)行邏輯判斷,這種寫法特別適合一元條件判斷的情況。

const actions = {
  '1': ['processing','IndexPage'],
  '2': ['fail','FailPage'],
  '3': ['fail','FailPage'],
  '4': ['success','SuccessPage'],
  '5': ['cancel','CancelPage'],
  'default': ['other','Index'],
}
/**
 * 按鈕點(diǎn)擊事件
 * @param {number} status 活動狀態(tài):1開團(tuán)進(jìn)行中 2開團(tuán)失敗 3 商品售罄 4 開團(tuán)成功 5 系統(tǒng)取消
 */
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']]
])
/**
 * 按鈕點(diǎn)擊事件
 * @param {number} status 活動狀態(tài):1 開團(tuán)進(jìn)行中 2 開團(tuán)失敗 3 商品售罄 4 開團(tuán)成功 5 系統(tǒng)取消
 */
const onButtonClick = (status)=>{
  let action = actions.get(status) || actions.get('default')
  sendLog(action[0])
  jumpTo(action[1])
}

這樣寫用到了es6里的Map對象,是不是更爽了?Map對象和Object對象有什么區(qū)別呢?

  1. 一個(gè)對象通常都有自己的原型,所以一個(gè)對象總有一個(gè)"prototype"鍵。

  2. 一個(gè)對象的鍵只能是字符串或者Symbols,但一個(gè)Map的鍵可以是任意值。

  3. 你可以通過size屬性很容易地得到一個(gè)Map的鍵值對個(gè)數(shù),而對象的鍵值對個(gè)數(shù)只能手動確認(rèn)。

代碼風(fēng)格

常量大寫

//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 {}

先聲明后調(diào)用

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

以上就是淺談寫出優(yōu)雅耐看 JS代碼 的方法的詳細(xì)內(nèi)容,更多請關(guān)注億速云其它相關(guān)文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI