阅读 162
【青训营】- 进阶 | 熟悉又陌生的 JS - 各司其责原则

【青训营】- 进阶 | 熟悉又陌生的 JS - 各司其责原则

一、前言

最近听了月影老师讲 JS 最基本的三个原则,其中举到的例子 🌰 甚是巧妙。有一些是之前做过的 demo,经过深挖,发现竟然有这么深的门道;有一些是之前很少用到的,但切实有用的方法。

月影老师讲课的内容都在 掘金小册 - 前端工程师进阶 10 日谈 [1] 中可以找到。我购买了这本小册,内容十分优质,值得深究。

前端基础三件套各自有其作用:

  • HTML 用于搭建网页结构
  • CSS 用于规定网页样式
  • JS 用于实现网页交互

本文主要记录关于 HTML、CSS、JS 各司其责的开发原则,对月影老师的 “深色浅色模式切换” 例子举一反三,通过实现一个手动切换红绿灯的 demo,体会我日常写的辣鸡代码变优雅的过程。

二、任务目标

路口有一个奇怪的交通信号灯,它只有 红灯绿灯 两种状态。不同于普通的交通信号灯,它只有一盏灯泡,不过这个灯可以显示红绿两种颜色(不能同时显示)。

这个交通信号灯只能通过人工拉闸的方式,切换红绿灯的显示。

规定闸头(“📍” 的红色一端向上为 绿灯,向下为 红灯)。

切换红绿灯时,需要同时切换红绿灯正中间显示的文字,以及左下角手闸的方向。

基础的 HTML 结构如下:

<div class="traffic">
  <span class="notice">请通行</span>
</div>

<div class="controller">📍</div>
复制代码

对应的 SCSS 代码如下:

html,
body {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 10px;
}

.traffic {
  width: 160px;
  height: 160px;
  border-radius: 50%;
  background-color: green;
  display: flex;
  justify-content: center;
  align-items: center;
  .notice {
    color: white;
    font-size: 2rem;
  }
}

.controller {
  position: absolute;
  bottom: 8rem;
  right: 3rem;
  font-size: 4rem;
  transition: all ease-out 0.4s;
  transform-origin: bottom;
  cursor: pointer;
}
复制代码

实现效果如下图。

image-20210819101301521.png

三、大家都能想到的实现方案

我们很快就能想到这样的思路:

  • 给手闸绑定点击监听事件;
  • 点击手闸时,通过操作 DOM,实现任务目标中的功能。

这种方式比较好实现。

// 获取元素实例
const trafficLight = document.querySelector('.traffic');
const notice = document.querySelector('.notice');
const controller = document.querySelector('.controller');

// 绑定点击事件
controller.onclick = function () {
  // 绿变红
  if (notice.innerHTML === '请通行') {
    trafficLight.style.backgroundColor = 'red';
    notice.innerHTML = '请等待';
    controller.style.transform = 'rotatex(180deg)';
  } else {
    // 绿变红
    trafficLight.style.backgroundColor = 'green';
    notice.innerHTML = '请通行';
    controller.style.transform = 'rotatex(0deg)';
  }
};
复制代码

实现效果如下所示:

See the Pen Bi-state Traffic Light 1 by Yiyang Sun (@syyCN) on CodePen.

这种方式使用 JS 操作元素的内容和样式,显然这两者都不是 JS 应该负责的功能。

元素内容应该由 HTML 来负责,而样式部分应该由 CSS 负责。

此外,如果有其他人接手并维护这段代码,我们无法保证所有人都能读懂这一系列 JS 操作执行的意义。

四、做一些各司其责的尝试

我们不妨让 HTML 来负责元素内容,由 CSS 负责样式,而 JS 通过控制类名,达到控制显示效果的目的。

话不多说,先上代码。

<div class="traffic green">
  <span class="notice"></span>
</div>

<div class="controller green">📍</div>
复制代码
html,
body {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 10px;
}

.traffic {
  width: 160px;
  height: 160px;
  border-radius: 50%;
  // background-color: green;
  display: flex;
  justify-content: center;
  align-items: center;
  .notice {
    color: white;
    font-size: 2rem;
  }
}

.traffic.green {
  background-color: green;
  .notice::after {
    content: '请通行';
  }
}

.traffic.red {
  background-color: red;
  .notice::after {
    content: '请等待';
  }
}

.controller {
  position: absolute;
  bottom: 10rem;
  right: 180px;
  font-size: 4rem;
  transition: all ease-out 0.4s;
  transform-origin: bottom;
  cursor: pointer;
}

.controller.green {
  transform: rotatez(0deg);
}

.controller.red {
  transform: rotatez(180deg);
}
复制代码
const controller = document.querySelector('.controller');

controller.onclick = function () {
  let trafficLight = document.querySelector('.traffic');
  // 判断当前为绿灯或红灯,进行对应切换
  if (trafficLight.classList.contains('green')) {
    trafficLight.classList.remove('green');
    trafficLight.classList.add('red');
    controller.classList.remove('green');
    controller.classList.add('red');
  } else {
    trafficLight.classList.remove('red');
    trafficLight.classList.add('green');
    controller.classList.remove('red');
    controller.classList.add('green');
  }
};
复制代码

整体的思路是:

  • 预定义好 HTML 结构,以及每个 HTML 元素在不同类名之下的显示状态;
  • 通过 JS 切换 HTML 的类名,使其匹配不同的 CSS 规则,从而达到显示内容切换的目的。

项目预览如下,功能均可实现。

See the Pen Bi-state Traffic Light 2 by Yiyang Sun (@syyCN) on CodePen.

温馨提示:

  • 代码中使用了 伪类元素,因为 content 属性只在伪类元素中生效;
  • 经过这样的修改,我们代码的可读性增加了,JS 中写明了 red 以及 green 类名,分别对应不同的灯的颜色;
  • 增加切换动画的功能不用涉及 JS,只需要在 CSS 中修改;
  • 同样,如果需求变动,需要改成 “红黄灯”,也只需要改动 CSS 即可(如果你能接受 green 类名表示黄色 😂)。

这样的改进让我们获得了更高的代码可读性和可维护性,不过考虑一个问题,如果需要切换类名的元素巨多,而不是我们 demo 中的两个,那么 JS 代码又会变得冗长,从而可读性和可维护性减弱。

五、《世界上最好的设计,是没有设计》—— 雷总

听 “军” 一席话,如听一席话~

雷总讲的这个哲理,在前端领域同样适用。世界上最好的 JS 代码,是没有 JS 代码!(狗头保命 🐶)

如果不用 JS,能否实现这样的切换呢?答案当然是——可以~

HTML 中有一个元素,刚好是实现双值切换的功能,它就是 checkbox。选中与不选中,是两个状态,可以设置这两种状态对应的显示效果。

我们先来给 HTML 部分加上一个 checkbox 元素。

<div class="traffic">
  <span class="notice">请通行</span>
</div>

<div class="controller">📍</div>
<input type="checkbox" class="checkbox" />
复制代码

效果如下:

image-20210822090044941.png

接下来,使用 CSS 将 checkbox 的选中与否的状态与红绿灯显示的状态关联。

html,
body {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 10px;
}

.traffic {
  width: 160px;
  height: 160px;
  border-radius: 50%;
  background-color: green;
  display: flex;
  justify-content: center;
  align-items: center;
  .notice:after {
    content: '请通行';
    color: white;
    font-size: 2rem;
  }
}

.controller {
  position: absolute;
  bottom: 10rem;
  right: 180px;
  font-size: 4rem;
  transition: all ease-out 0.4s;
  transform-origin: bottom;
  cursor: pointer;
}

#checkbox:checked ~ .traffic {
  background-color: red;
  .notice:after {
    content: '请等待';
  }
}

#checkbox:checked ~ .controller {
  transform: rotatez(180deg);
}
复制代码

现在就可以实现通过点击 checkbox 切换红绿灯的颜色了。但是这离我们的目标还有一定的距离,我们需要点击右下角的按钮实现切换。

只需要把按钮元素改为 label 标签,再加上一个 for 属性,其值为 checkboxid 即可。

<input type="checkbox" id="checkbox" />

<div class="traffic">
  <span class="notice"></span>
</div>

<label class="controller" for="checkbox">📍</label>
复制代码

现在点击手闸,也可以实现红绿灯的切换。接下来,我们只需给 CSS 部分末尾加入以下代码,将 checkbox 元素隐藏即可。

#checkbox {
  display: none;
}
复制代码

全部代码如下:

<input type="checkbox" id="checkbox" />

<div class="traffic">
  <span class="notice"></span>
</div>

<label class="controller" for="checkbox">📍</label>
复制代码
html,
body {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 10px;
}

.traffic {
  width: 160px;
  height: 160px;
  border-radius: 50%;
  background-color: green;
  display: flex;
  justify-content: center;
  align-items: center;
  .notice:after {
    content: '请通行';
    color: white;
    font-size: 2rem;
  }
}

.controller {
  position: absolute;
  bottom: 10rem;
  right: 180px;
  font-size: 4rem;
  transition: all ease-out 0.4s;
  transform-origin: bottom;
  cursor: pointer;
}

#checkbox:checked ~ .traffic {
  background-color: red;
  .notice:after {
    content: '请等待';
  }
}

#checkbox:checked ~ .controller {
  transform: rotatez(180deg);
}

#checkbox {
  display: none;
}
复制代码

这样,我们没有使用 JS,也实现了红绿灯切换的功能。这种思路还可以应用于其他具有双值切换需求的场景,例如深色浅色模式切换、LTR 或 RTL 阅读顺序切换等。

使用这种实现方式,优势在于我们没有写 JS 代码,所以在浏览器的兼容性方面表现较好。没有 JS 代码也就不会有 bug。

不足之处在于,相比于有 JS 的版本,这样的模式代码冗长,如果遇到更加复杂的逻辑,修改不便。

六、总结

世界上没有完美的代码,只有一直追求完美的工程师。在代码优化的过程中,我们的代码能力也在进阶。

第一次参加字节跳动的训练营,非常荣幸能听到 月影老师李松峰老师 讲授的课程以及进行的答疑和交流。本次训练营注重基础,内容充实。在之后的学习中,我也需要更加注重自身基础的夯实。

感谢月影老师的 JS 课程,再次推荐月影老师的 掘金小册 - 前端工程师进阶 10 日谈

参考

[1]掘金小册 - 前端工程师进阶 10 日谈

文章分类
阅读
文章标签