js(面試題)
shuffle:顧名思義,將陣列隨機排序,常在開發中用作實現隨機功能。
我們來看看一個 shuffle 可以體現出什麼程式碼品味。
錯誤舉例
function shuffle(arr) {
arr.sort(function () {
return Math.random() - 0.5;
});
}
// ES6
const shuffle = (arr) => {
arr.sort(() => Math.random() - 0.5);
}
請老鐵千萬不要這樣寫,這體現了兩個錯誤:
- 你的這段程式碼一定是從網上抄/背下來的,面試官不想考這種能力
- 很遺憾,這是錯誤的,並不能真正地隨機打亂陣列。
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 裡有沒有變數提升?沒有,不僅
const
和let
都沒有,連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
函式,這樣寫沒毛病,如果 shuffle
和 swap
一起寫呢:
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
來源:掘金