1. 程式人生 > >js(面試題)

js(面試題)

shuffle:顧名思義,將陣列隨機排序,常在開發中用作實現隨機功能。

我們來看看一個 shuffle 可以體現出什麼程式碼品味

錯誤舉例

function shuffle(arr) {
   arr.sort(function () {
      return Math.random() - 0.5;
   });
}

// ES6
const shuffle = (arr) => {
  arr.sort(() => Math.random() - 0.5);
}

請老鐵千萬不要這樣寫,這體現了兩個錯誤:

  1. 你的這段程式碼一定是從網上抄/背下來的,面試官不想考這種能力
  2. 很遺憾,這是錯誤的,並不能真正地隨機打亂陣列。

Why? Check:https://blog.oldj.net/2017/01/23/shuffle-an-array-in-javascript/comment-page-1/#comment-1466

思考

下面來到了第一反應:思考問題。

陣列隨機化 -> 要用到 Math.random -> 看來每個元素都要 random 一下 -> 處理 arr.length 要用到 Math.floor -> 需要用到 swap

第一版

由此有了第一版程式碼:

function shuffle(arr) {
  var i;
  var randomIndex;
 
  for (i = arr.length; i > 0
; i--) { randomIndex = Math.random() * i; swap(arr, i, randomIndex); } }
  • 為什麼用 randomIndex 不用 j? -> 更有意義的變數命名
  • 為什麼要把 i 和 randomIndex 的宣告放在最前方? -> ES5 裡的變數提升(ES6 裡有沒有變數提升?沒有,不僅 constlet 都沒有,連 class 也沒有)
  • 為什麼第 3 行和第 5 行中留一個空格?將宣告的變數和函式體分開,一目瞭然的邏輯使程式碼更加清晰易維護

什麼,JavaScript 中木有 swap 函式?

寫一個,使邏輯更加清晰 & 重複利用:

function swap(arr, indexA, indexB) {
  var temp;
 
  temp = arr[indexA];
  arr[indexA] = arr[indexB];
  arr[indexB] = temp;
}

第二版

一點點小的改動:

function shuffle(arr) {
  arr.forEach(function (curValue, index) {
    var randomIndex = Math.random() * index;

    swap(arr, index, randomIndex);
  });
}

arr.forEach 替代原本的 for 迴圈。

不希望有人質疑:JS 由於函式呼叫棧空間有限,用 for 迴圈不是比 forEach 效率更高嗎?

拿出這段話壓壓驚:

”We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.”-- Donald Knuth

JavaScript 天生支援函數語言程式設計(functional programing),放下腦海中的 CPP-OOP,請好好珍惜它。

有了 High-order function & First-class function 的存在,編寫程式碼的邏輯愈發清晰簡潔好維護

第三版

且慢,同學不寫一個 ES6 版本的嗎?

const shuffle = arr => {
  arr.forEach((element, index) => {
    const randomIndex = Math.floor(Math.random() * (index + 1))
 
    swap(arr, index, randomIndex)
  })
}

使用 ES6 的箭頭函式(arrow function),邏輯的表達更為簡潔清晰好維護。(我會告訴你箭頭函式還因為本身繫結的是外部的 this,解決了一部分 this 繫結的問題嘛。注意我沒有說全部)。

進階

何不用 ES6 重寫一下 swap 函式?

const swap = (arr, indexA, indexB) => {
  [arr[indexA], arr[indexB]] = [arr[indexB], arr[indexA]]
}

怎麼樣,ES6 的物件解構賦值(Destructuring)燃不燃?好用不好用?

但如果單獨寫一個 swap 函式,這樣寫沒毛病,如果 shuffleswap 一起寫呢:

const shuffle = arr => {
  arr.forEach((element, index) => {
    const randomIndex = Math.floor(Math.random() * (index + 1))
 
    swap(arr, index, randomIndex)
  })
}

const swap = (arr, indexA, indexB) => {
  [arr[indexA], arr[indexB]] = [arr[indexB], arr[indexA]]
}

出現呼叫錯誤,const 宣告的變數沒有變數提升,在 shuffle 呼叫 swap 的時候 swap 還木有出生呢~!

So 這樣?

const swap = (arr, indexA, indexB) => {
  [arr[indexA], arr[indexB]] = [arr[indexB], arr[indexA]]
}

const shuffle = arr => {
  arr.forEach((element, index) => {
    const randomIndex = Math.floor(Math.random() * (index + 1))
 
    swap(arr, index, randomIndex)
  })
}

老鐵沒毛病。但主要邏輯 shuffle 放在後,次要邏輯 swap 放在前有沒有不妥?

最終解答

function shuffle(arr) {
  arr.forEach((element, index) => {
    const randomIndex = Math.floor(Math.random() * (index + 1))
 
    swap(arr, index, randomIndex)
  })
}
 
function swap(arr, indexA, indexB) {
  [arr[indexA], arr[indexB]] = [arr[indexB], arr[indexA]]
}

為啥用 ES5 的方式來寫 function,AirBnb 的 ES6 規範建議不是用 const + 箭頭函式來替代傳統的 ES5 function 宣告式嗎?

子曰:

  • 程式設計規範是人定的,而你是有選擇的
  • 軟體開發不是遵循教條,程式碼世界本沒有標準答案

我用傳統 ES5 function 是因為:

我想利用它的變數提升實現函式主邏輯前置,進而從上到下,層層邏輯遞進。再一次出現這兩個次:邏輯簡潔好維護

總結

  • 你問:有沒有高水平的程式碼來讓面試官眼前一亮?

  • 我答:只有好讀又簡潔,穩定易維護的程式碼,沒有高水平的程式碼一說。

  • 你問:說好的程式碼品味呢?

  • 我答:都藏在每一個細節的處理上:)

地址

作者:RayJune https://github.com/rayjune

來源:掘金