如何写好javascript(下)| 青训营笔记

95 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的的第4天,今天继续学习关于javascript的知识。

交通灯案例

示例

以上代码通过定义一个立即执行函数并使用多个setTimeout()的形式实现了交通灯的切换,但是由于过多的使用了setTimeOut(),所以代码的可读性和可维护性大大降低。

数据抽象

此处对代码进行了改进,首先将每个灯抽象为数据提取出来,然后将js操作代码封装为一个start()方法,使用时进行调用并传入DOM对象和封装的交通灯数据即可。

过程抽象

此处先定义一个wait()方法用以亮灯延时;再定义一个高阶函数poll()方法用来控制交通灯的轮替,它通过筛选传入的状态数据并返回此时交通灯应展示的状态数据;然后准备一个setState()方法用来获取当前应使用的状态数据并修改对应的class属性值完成展示并触发wait()方法的延时;之后执行poll()方法传入setState()方法并绑定多个交通灯状态数据之后接收返回的值;最后使用while(1)循环执行poll()方法返回的函数完成交通灯的实现。但是此方法过于复杂,不便于理解和修改。

异步+函数式

此方法根据上方过程抽象的代码进行改进,取消了高阶函数poll()方法,仅使用wait()延时和setState()修改状态,最后执行则封装start()方法实现。

async function start(){
  while(1){
    setState('wait');
    await wait(1000);
    setState('stop');
    await wait(3000);
    setState('pass');
    await wait(3000);
  }
}
start();

洗牌算法

错误写法

const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function shuffle(cards) {
  return [...cards].sort(() => Math.random() > 0.5 ? -1 : 1);
}
const result = Array(10).fill(0);
for(let i = 0; i < 10000; i++) {
  const c = shuffle(cards);
  for(let j = 0; j < 10; j++) {
    result[j] += c[j];
  }
}

这个方法使用sort()+Math.random()进行随机排序达到洗牌的目的,但是该方法会导致随机分布不均匀,具体体现为越靠前的数字位置可能越小等问题从而导致洗牌不充分或者不均匀。

正确写法

const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function shuffle(cards) {
  const c = [...cards];
  for(let i = c.length; i > 0; i--) {
    const pIdx = Math.floor(Math.random() * i);
    [c[pIdx], c[i - 1]] = [c[i - 1], c[pIdx]];
  }
  return c;
}
const result = Array(10).fill(0);
for(let i = 0; i < 10000; i++) {
  const c = shuffle(cards);
  for(let j = 0; j < 10; j++) {
    result[j] += c[j];
  }
}

修改后的方法首先从所有牌中随机拿取一张插入到牌堆最后的位置上,依次循环完成洗牌,此时所有牌的分布概率均相同,此时则说明这次的洗牌是成功的。

生成器(抽奖)

当我们需要洗牌但是不需要抽取所有牌的时候可以清除掉代码后的循环,不让系统展示所有牌,之后使用result.next().value方法,从洗牌结果组成的集合中通过迭代器获取下一条数据即可,若需要抽取多张牌,可使用循环多次调用该方法。

小结

在这一节学习中,主要学习了一些js的写法和算法的优化方式,加深了对js中高阶函数的理解,但是还是需要自己多研究多使用才能融会贯通。