为了账号安全,请及时绑定邮箱和手机立即绑定

从 shuffle 看代码品味(面试题)

标签:
JavaScript

面试官:小伙子写一个 shuffle?(JavaScript)

shuffle:顾名思义,将数组随机排序,常在开发中用作实现随机功能。

我们来看看一个 shuffle 可以体现出什么代码品味。

错误举例

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

ES6

const shuffle = arr =>
  arr.sort(() => Math.random() - 0.5);

测试代码

// test
shuffle([1, 2, 3, 4, 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 - 1; i > 0; i--) {
    randomIndex = Math.floor(Math.random() * i);
    swap(arr, i, randomIndex);
  }

  return arr;
}

为什么用 randomIndex 不用 j? -> 更有意义的变量命名

为什么要把 i 和 randomIndex 的声明放在最前方? -> ES5 里的变量提升(ES6 里有没有变量提升?没有,不仅 const 和 let 都没有,连 class 也没有。但是 import 命令具有提升效果,会提升到整个模块的头部,首先执行)

为什么第 3 行和第 5 行中留一个空行 & 为什么第 8 行和第 10 行之间留一个空行?将声明的变量、函数体、return 分开。三段式结构,一目了然的逻辑,使代码更加清晰易维护
什么,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.floor(Math.random() * index);

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

  return arr;
}

用 arr.forEach 替代原本的 for 循环。(我会告诉你 【array.forEach 的返回值是 undefined】这一点容易出错嘛)

不希望有人质疑: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);
  });

  return arr;
};

使用 ES6 的箭头函数(arrow function),逻辑的表达更为简洁、清晰、好维护。(我会告诉你箭头函数还因为本身绑定的是外部的 this,解决了一部分 this 绑定的问题嘛。注意我没有说全部)。

顺便也用 ES6 重写一下 swap 函数把。简介的语法,更强大的表现力,谁用谁喜欢:

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

怎么样,ES6 的对象解构赋值(Destructuring)燃不燃?好用不好用?

进阶
光说不练假把式,我们来试用一下第三版的 shuffle 把:

// test
shuffle([1, 2, 3, 4, 5]);

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

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

  return arr;
};

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

出现调用错误,const 声明的变量没有变量提升,在调用 shuffle 和 swap 的时候他们还木有出生呢~!

So 这样?

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]];
};

// test
shuffle([1, 2, 3, 4, 5]);

老铁没毛病。但主要逻辑运行代码放在后,次要逻辑函数定义放在前有没有不妥?

这里只有 shuffle 和 swap 两个函数,或许你会觉得区别不明显,那如果代码更长呢? 没错,或许你可以进行模块拆分,但如果像 underscore 那样的代码呢。如果像博主一样写一个 indexeddb-crud 呢?(此处是硬广)

有时候我们需要一次自我审问:每次调用函数时都要确认函数声明在调用之前的工作是必须的吗?

最终解答

// test
shuffle([1, 2, 3, 4, 5]);

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

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

  return arr;
}

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

为啥用 ES5 的方式来写 function,Airbnb 的 ES6 规范建议不是用 const + 箭头函数来替代传统的 ES5 function 声明式吗?

const + 箭头式函数声明带来了什么,失去了什么:

带来了更加规范简介的函数定义,向外的一层 this 绑定

失去了更加自由的逻辑展现(调用不能放在声明之前)

子曰:

编程规范是人定的,而你是有选择的
软件开发不是遵循教条,代码世界本没有标准答案
我在这里用传统 ES5 function 是因为:

我想利用它的变量提升实现主逻辑前置,而不用去关心函数的定义位置。

进而从上到下,层层逻辑递进。再一次出现这两个次:逻辑简洁、好维护。

总结
你问:有没有高水平的代码来让面试官眼前一亮?

我答:只有好读又简洁,稳定易维护的代码,没有高水平的代码一说。

你问:说好的代码品味呢?

我答:都藏在每一个细节的处理上:)

地址

P.S. 我不会告诉你原文地址的显示效果更好喔:)

原文地址:https://www.rayjune.me/2018/03/13/see-code-taste-from-shuffle/

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

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消