【字节跳动青训营】- 如何写好JS?

各司其职

  • HTML/CSS/JS各司其职

  • 应当避免不必要的由JS直接操作样式

  • 可以用class来表示状态

  • 纯展示类交互寻求零JS方案(可以用CSS实现)

例如:实现一个网页的“夜间模式”

请添加图片描述

方法一:

const btn = document.getElementById('modeBtn');
btn.addEventListener('click', (e) => {
  const body = document.body;
  if(e.target.innerHTML === '') {
    body.style.backgroundColor = 'black';
    body.style.color = 'white';
    e.target.innerHTML = '';
  } else {
    body.style.backgroundColor = 'white';
    body.style.color = 'black';
    e.target.innerHTML = '';
  }
});

缺点

  • 程序用了js直接操作样式

  • 代码显得复杂繁琐、且复用性差

改进:

  • 可以用class来表示状态

方法二:

const btn = document.getElementById('modeBtn');
btn.addEventListener('click', (e) => {
  const body = document.body;
  if(body.className !== 'night') {
    body.className = 'night';
  } else {
    body.className = '';
  }
});

优点:

  • 相比方法一没有用js直接操作样式

改进:

  • 可以用纯css实现此效果,让代码更简洁

方法三:(推荐)

<input id="modeCheckBox" type="checkbox">
  <div class="content">
    <header>
      <label id="modeBtn" for="modeCheckBox"></label>
      <h1>深夜食堂</h1>
    </header>
    <main>
        ······
    </main>
   </div> 
#modeCheckBox:checked + .content {
  background-color: black;
  color: white;
  transition: all 1s;
}

优点:

  • 是非常好的零js展示方案

  • 完全满足了HTML/CSS/JS各司其职的书写理念


组件封装

组件:指Web页面上抽出来的一个个包含模板(HTML)、功能(JS)、样式(CSS)的单元

组件设计的原则:封装性、正确性、扩展性、复用性

实现组件的步骤:

如轮播图为例:轮播图

结构设计:HTML

轮播图是一个典型的列表结构,可以使用无序列表

  • 元素来实现

<div id="my-slider" class="slider-list">
  <ul>
    <li class="slider-list__item--selected">
      <img src="https://p5.ssl.qhimg.com/t0119c74624763dd070.png"/>
    </li>
    <li class="slider-list__item">
      <img src="https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg"/>
    </li>
    <li class="slider-list__item">
      <img src="https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg"/>
    </li>
    <li class="slider-list__item">
      <img src="https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg"/>
    </li>
  </ul>
</div>

展现效果:CSS

  • 使用CSS绝对定位将图片重叠在同一个位置

  • 轮播图切换的状态使用修饰符(modifier)

  • 轮播图切换动画使用CSS transition

#my-slider{
  position: relative;
  width: 790px;
}

.slider-list ul{
  list-style-type:none;
  position: relative;
  padding: 0;
  margin: 0;
}

.slider-list__item,
.slider-list__item--selected{
  position: absolute;
  transition: opacity 1s;
  opacity: 0;
  text-align: center;
}

.slider-list__item--selected{
  transition: opacity 1s;
  opacity: 1;
}

行为设计

API(功能)

API设计应保证原子操作,职责单一,满足灵活性。

  • Slider

    • +getSelectedItem( ) 当前选中被展现出的图

    • +getSelectedItemIndex( ) 当前选中被展现出的图的索引

    • +slideTo( ) 跳转并展示某个图

    • +slideNext( ) 切换下一张图

    • +slidePrevious( ) 切换上一张图

class Slider{
  constructor(id){
    this.container = document.getElementById(id);
    this.items = this.container
    .querySelectorAll('.slider-list__item, .slider-list__item--selected');
  }
  getSelectedItem(){
    const selected = this.container
      .querySelector('.slider-list__item--selected');
    return selected
  }
  getSelectedItemIndex(){
    return Array.from(this.items).indexOf(this.getSelectedItem());
  }
  slideTo(idx){
    const selected = this.getSelectedItem();
    if(selected){ 
      selected.className = 'slider-list__item';
    }
    const item = this.items[idx];
    if(item){
      item.className = 'slider-list__item--selected';
    }
  }
  slideNext(){
    const currentIdx = this.getSelectedItemIndex();
    const nextIdx = (currentIdx + 1) % this.items.length;
    this.slideTo(nextIdx);
  }
  slidePrevious(){
    const currentIdx = this.getSelectedItemIndex();
    const previousIdx = (this.items.length + currentIdx - 1)
      % this.items.length;
    this.slideTo(previousIdx);  
  }
}
//展示第一张图
const slider = new Slider('my-slider');
slider.slideTo(1);
Event(控制流)

轮播图下面的控制按钮

 <a class="slide-list__next"></a>
  <a class="slide-list__previous"></a>
  <div class="slide-list__control">
    <span class="slide-list__control-buttons--selected"></span>
    <span class="slide-list__control-buttons"></span>
    <span class="slide-list__control-buttons"></span>
    <span class="slide-list__control-buttons"></span>
  </div>

使用自定义事件来解耦

  const detail = {index: idx}
  const event = new CustomEvent('slide', {bubbles:true, detail})
  this.container.dispatchEvent(event)

组件封装优点:

  • 复用性强

  • 可读性好

  • 加强自己的代码管理能力

过程抽象

  • 用来处理局部细节控制的一些方法

  • 函数式编程思想的基础应用

函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperative programming)函数式编程逻辑式编程,常见的面向对象编程是也是一种命令式编程。

命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令),一句话,命令式程序就是一个冯诺依曼机指令序列

而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式

函数式编程的本质

函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。

比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。相同的输入总是得到相同的输出,复用性好

1、表达式化

函数式编程的思维就是首先转变我们传统的编程观念。去可变量,去循环,把命令式改成声明式

命令式:

请添加图片描述

声明式

请添加图片描述

2、高阶逻辑

用了函数式,思维就要从循环、赋值这些低阶逻辑转移成高阶的思考问题。函数式又叫声明式,也就是需要做什么操作,只需要说一下就行,而非写个遍历,做个状态判断.

用函数式就不需要考虑这样,你不知道函数式的列表是怎么遍历的,中间向两边? 从后往前?这也是为何函数式适合并发的原因之一,你想知道列表中大于3的数有多少,只要,list.count(_ > 3) 而不是写循环,你可以直接写你的业务,不要拘泥于细节,有点像sql, 你需要什么告诉电脑就行,你或许会问,count foreach filter 这些函数怎么来的? 因为有了他们你才不需要写循环,他们把你留在高阶逻辑中。

3、组合子逻辑 或又叫 自底向上的设计

面向对象是自顶向下的设计,函数式是自底向上的设计,也就是先定义最基本的操作,然后不断组合,不断堆积以满足你的所有需要,如sql定义了select, from, where…这几个组合子,来满足你的查询需求,同理函数式语言会提供foreach, map等组合子(操作)来满足你的需求,所以你必须自下而上的设计你的代码结构,并且满足你的需求,当你只用组合子写代码时,你会发现你写的全是高阶逻辑

总结:

函数式思维,其实就是组合子逻辑,用简单的几个函数组合来构建复杂逻辑,始终以高阶的角度去表达问题,而非依赖副作用。