打乱数组——shuffle

javascript/jquery

浏览数:440

2019-7-21

在学习vue移动端音乐项目时,看到一个打乱数组函数,感觉很有意思就记录一下(意外发现:slice是个有趣的知识点)

原理:遍历数组,(let i = 0; i < _arr.length; i++),从0-i之间随机取一个数,与当前的arr[i]作交换,这样就把数组洗的很乱

// 返回min和max之间的一个随机数,包括min和max
function getRandomInt(min, max) { 
  return Math.floor(Math.random() * (max - min + 1) + min) // +1是保证可以取到上限值
}

// 洗牌函数
function shuffle(arr) {  
  let _arr = arr.slice()   // 下面会讲到slice的特别之处
  for (let i = 0; i < _arr.length; i++) {
    let j = getRandomInt(0, i)
    let t = _arr[i]
    _arr[i] = _arr[j]
    _arr[j] = t
  }
  return _arr
}
var arr = [0,1,2,3,4];
shuffle(arr);

打乱数组就是这么简单,下面让我们说说为什么要用slice处理一下,而不是直接用arr本身???

我们在打乱数组后,一定希望的是返回一个新的打乱过的数组,原来的数组不发生变化

因为数组是引用类型,所以不能直接操作原数组(arr)。我们需要拷贝一份,将拷贝的数组(_arr)打乱之后return出去。
那么问题来了,slice能做到操作拷贝后的数组(_arr)而不改变原数组(arr)吗?
答案是:能做到一半

因为let _arr = arr.slice()能将原数组(arr)的第一层深拷贝

举个栗子:

let obj = [
    {
        name:0,
        job:0
    },
    {
        name:1,
        job:1
    },
    {
        name:2,
        job:2
    }
];
let copy = obj.slice(0);  // 0可省略
copy[1].name = 10;  // 改变第二层,对象的属性name
console.log(obj[1].name); // 10
console.log(copy[1].name); // 10
copy[2] = {name: 20,job: 20};  // 改变第一层,将整个对象替换成别的对象(改变copy[2]对象的内存指向)
console.log(obj[2].name); // 2
console.log(copy[2].name); // 20

综上,就很好理解为什么要用let _arr = arr.slice()处理了。

注:es6的Object.assign解构赋值;Array的concat、map、filter也只会深拷贝对象(数组)的第一层。

附加一个更骚的打乱方式:

var numbers = [5, 458 , 120 , -215 , 228 , 400 , 122205, -85411];
var numbers_ = numbers.slice();
numbers_ = numbers_.sort(function(){ return Math.random() - 0.5});

作者:辛月