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
是因为:
我想利用它的变量提升实现函数主逻辑前置,进而从上到下,层层逻辑递进。再一次出现这两个次:逻辑简洁、好维护。
总结
你问:有没有高水平的代码来让面试官眼前一亮?
我答:只有好读又简洁,稳定易维护的代码,没有高水平的代码一说。
你问:说好的代码品味呢?
我答:都藏在每一个细节的处理上:)
地址
原文地址:https://www.rayjune.me/2018/03/13/see-code-taste-from-shuffle/
作者:RayJune https://github.com/rayjune
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。