【青训营】- 跟着月影学JavaScript之如何写好JS

1,802 阅读5分钟

前面已经分享了该如何写好JS的一些方式技巧,本篇就来做个最后的总结,看看写代码最应该关注什么?哪些写法是不可取,可以改进的。

前言

『前期回顾』

跟着月影学JavaScript之怎么封装组件

跟着月影学JavaScript之过程抽象+

先从一段代码开始

以下的这段代码好不好?为什么?(可以先思考1秒🤔)

image-20210818223643209

👇

👇

👇

👇

👇

👇

其实这是spriteJs 框架的一段真实的代码,可以翻到131行看一下

image-20210818225230859

spriteJs 是一款由 360 奇舞团开源的跨终端 canvas 绘图框架,可以基于 canvas 快速绘制结构化 UI、动画和交互效果,是跨平台的2D绘图对象模型库,它能够支持web、node、桌面应用和微信小程序的图形绘制和实现各种动画效果。

如果从优雅性来说,这段代码看上去并不够简洁,但是为什么要这么写呢?

首先这是一个图形库,这段代码主要是负责,在渲染之前计算我们图层transform矩阵的逻辑代码,也就是说在每一帧的渲染之前都要去计算。

在这种情况下,任何一种其他写法的性能都不如这样直接展开写的好。这样展开来写,就不需要用任何的循环或其他的逻辑,只需要从索引中直接取出内容来比较,这样性能是最高的。

但是在一般的代码中,对性能没有这么敏感的话,是不需要这样写的,可以用其他的一些写法,来让我们的代码短一点。

为什么要说这个案例?

我们的所有代码风格都应该是根据场合来的,如果你在一些框架或库中看到一些比较死板,不是那么优雅的写法,那就应该要结合该框架或库来看,这样设计或许都是有理由的。

写代码最应该关注什么?

我们应该考虑以下几点

  • 风格(同一套代码应该要统一)
  • 效率(写代码的时候应该多考虑代码的效率,尽量写效率高的代码)
  • 约定
  • 使用场景(也需要结合场景,也的时候也不应该为了追求极致的效率,而牺牲代码的可读性)
  • 设计

当年的Left-pad事件

leftpad就是一个简单的字符串处理函数

  • 当字符串的长度没有达到我们要的长度时,就在它的左边补若干个字符,把它的长度补齐
module.exports = leftpad;
function leftpad (str, len, ch) {
  str = String(str);
  var i = -1;
  if (!ch && ch !== 0) ch = ' ';
  len = len - str.length;//期望的长度-字符串的长度 = 要拼接的长度
  while (++i < len) {//通过循环,把用来替代的字符添加到字符串前面去
    str = ch + str;
  }
  return str;
}

事件本身的槽点

  • NPM模块粒度

  • 代码风格

  • 代码质量/效率

优化上面的代码

  • 代码更简洁
  • 效率提升

image-20210818232852721

关于repeat👉Repeat polyfill / MDN

一些关键词

  • 时间复杂度
  • 快速幂

一段瞎科普:【Left-pad事件】就是当年 NPM 圈发生的“一个十几行代码的模块引发的血案”。left-pad工具模块被作者 Azer从 NPM 上撤下,所有直接或者间接依赖这个模块的 NPM 包就都忧伤的挂掉了,其中就包括 babel 、React等。

再插一段小八卦:作者为什么要删包?事情是这样的,Azer 写了一个工具叫 kik 发布在 npm 上,这天有个同名的公司律师找上门要求他删掉,Azer 不从,这律师就找上 npm,npm 把包的管理权限转给了这家公司——当然,Azer 就怒了,从 npm 上解放了所有自己发布的包。就是酱😖

交通灯状态切换

来实现一个切换多个交通灯状态切换的功能

image-20210818233851361

版本一

没有封装、不可扩展,不美观(不及格)

image-20210818234100085

怎么去优化?

首先应该要对数据做一个封装,数据封装之前应该先对数据进行抽象,抽象以后把一些信息给隐藏起来

image-20210818234909233

我们要实现的交通灯,本质是

  • 我们要接收一组状态
  • 然后根据这组状态来进行某些指令(如改变元素class)
  • 然后去做状态切换

版本二

数据做了封装,状态切换操作也做了封装

image-20210818235834721

关键词

  • 数据抽象

版本三

上面做的是数据抽象,在上一篇中,我们有提到一个【过程抽象】的概念,我们也可以通过【过程抽象】来实现。

image-20210819001006985

解析

  • 切换状态本身它就是一个【轮询操作】,所以我们可以把前面的【start方法】抽象出一个【轮询方法】
  • 【轮询方法】中传入一个【函数操作】列表
  • 然后依次取出当前要操作的函数
  • 然后执行这个函数

关键词

  • 过程抽象
  • 高阶函数

版本四

还可以利用async...await来实现,首先交通灯拆解开来 主要就是两种操作

  • 等待
  • 状态切换

image-20210819001512716

关键词

  • 命令式写法

以上写法虽然各有优化,但是都还有可以改进的地方

判断是否是4的幂

4的0次方 = 1

4的1次方 = 4

4的2次方 = 16

....

4的4次方 = 64

....

版本一

中规中矩

image-20210819002619293

关键词

  • 因式分解
  • 时间复杂度

版本二

image-20210819002824605

版本三

4的幂,则它转化为二进制之后

  • 首页肯定是1
  • 后面都是0
  • 并且0的个数都是偶数

image-20210819003419407

image-20210819003351712

关键词

  • 常数复杂度
  • 按位与

版本四

  function isPower0fFour(num){
    num = parseInt(num).toString(2);//转成二进制的字符串

    return /^1(?:00)*$/.test(num);//用正则表达式匹配一下
  }

洗牌

正确性

如果按以下写法,是否正确?

image-20210819004353886

可以测试一下,如果这个洗牌是公平的,那把如果洗10000000次,把得到的数组的每个对应位置的数字想加,得到的和应该是差不多的

image-20210819004659109

image-20210819004811280

但是通过结果可以看到,越前面的越小

image-20210819004902073

改进一

image-20210821194933340

image-20210819005149845

image-20210821194226708

改进二

  const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

  function * draw(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]];
      yield c[i - 1];
    }
  }

  const result = draw(cards);
  console.log([...result]);

后面的几个案例出现了一些算法的概念,不过算法不是我们当前的主题,所以等以后在细说吧!

结语

字节青训营

本节课的讲师:字节前端/掘金开发负责人—月影

如以上有错误的地方,请在评论区中指出!


小可爱看完点个赞再走吧!😗