计数器

-- 插件的封装和使用

课程概要

本课程主要讲解计数器,在电商网页中,我们经常通过计数器来加减商品,同时在产品页、购物车页、订单页都有可能用到,如果每个页面都把功能和逻辑都写一次,那么多个页面都会出现相同的代码。本课程中,我们会使用 JavaScript Class 类的知识点,来封装技术起为插件,让其可以多处调用。通过本课程,完成计数器插件,大家将对类的使用有更深入的了解。

主要包含以下功能:

  • 可以初始化内容
  • 点击减号数值减一
  • 点击加号数值加一
  • 可以从外部获取、设置数值
  • 可以从外部递增、递减数值
  • 可以从外部监听数值的变化

知识点

  1. 类的定义
// 1.类声明
class Rectangle {

}

// 2.类表达式(匿名类)
let RectangleNick = class {

};
  1. 构造函数

constructor 方法是一个特殊的方法,其用于创建和初始化使用 class 创建的一个对象。在创建时候执行,可以接收初始化传入的参数。

class Rectangle {
  // constructor
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }

  // Method
  calcArea() {
    return this.height * this.width;
  }
}
const square = new Rectangle(10, 10);

console.log(square.calcArea());
// 100
  1. 创建子类

extends 关键字在类声明或类表达式中用于创建一个类作为另一个类的一个子类。super 关键字用于调用对象的父对象上的函数,同时也可以传入参数。

class Parent {
  // constructor
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }

  // Method
  calcArea() {
    return this.height * this.width;
  }
}

class Rectangle extends Parent {
  constructor(height, width) {
    super(height, width)
  }
}

const square = new Rectangle(10, 10);

console.log(square.calcArea());

实现步骤

  1. HTML & CSS
  2. 构建 Count 类,并定义初始化接收的数据以及静态方法
  3. 创建 HTML 结构
  4. 绑定事件
  5. 实例化使用

HTML & CSS

  1. HTML
<div class="count-wrapper">
  <div class="count-number-container" id="count_1">
    <span class="count-number-btn_decrease">-</span>
    <span class="count-number-btn_increase">+</span>
    <div class="count-number-content">
      <input class="count-number-input" type="text" disabled name="count" value="0">
    </div>
  </div>

  <div class="count-number-container" id="count_2">
    <span class="count-number-btn_decrease">-</span>
    <span class="count-number-btn_increase">+</span>
    <div class="count-number-content">
      <input class="count-number-input" type="text" disabled name="count" value="0">
    </div>
  </div>
</div>
  1. CSS
.count-number-container{
  position: relative;
  display: inline-block;
  width: 180px;
  line-height: 38px;
}

.count-number-btn_decrease,
.count-number-btn_increase {
  position: absolute;
  z-index: 1;
  top: 1px;
  width: 40px;
  height: auto;
  text-align: center;
  background: #f5f7fa;
  color: #606266;
  cursor: pointer;
  font-size: 13px;
  user-select:none;
}

.count-number-btn_decrease {
  left: 1px;
  border-radius: 4px 0 0 4px;
  border-right: 1px solid #dcdfe6;
}

.count-number-btn_increase {
  right: 1px;
  border-radius: 0 4px 4px 0;
  border-left: 1px solid #dcdfe6;
}

.count-number-input{
  box-sizing: border-box;
  display: inline-block;
  width: 100%;
  height: 40px;
  padding: 0 50px;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
  background-color: #fff;
  font-size: inherit;
  line-height: 40px;
  color: #606266;
  text-align: center;
  outline: none;
  transition: border-color .2s cubic-bezier(.645,.045,.355,1);
}

构建 Count 类

  1. 构建 Count 类
  2. 定义初始化接收的数据
  3. 定义静态工具方法
// 1. 构建 Count 类
class Count{
  constructor(props) {
    // 2. 定义初始化接收的数据
    this.state = {
      id: props.id,
      value: props.value || 0,
      change: props.change,
      _countNumberInput:null,
    };

    // 3. 定义静态工具方法
    this._$ = selector => document.querySelector(selector);//用于选择DOM
    this._createElement = type => document.createElement(type);//用于创建DOM
    this._setContent = (elem, content) => elem.textContent = content//用于文本赋值
    this._appendChild = (container, node) => container.appendChild(node);//用于增加子节点
  }
}

创建 HTML 结构

我们在书写 CSS 的时候已经把结构防止在 HTML 中,但我们做插件的目的是为了方便,因此我们可以把接口在插件中生成,这样页面使用的时候,就不需要要写这么多的结构,只要写一个 div#IDSelecter 就可以了。

  1. 删除 HTML 中计数器的字元素
  2. 在 Count 中定义 _addHTML 方法
  3. 创建计数器子元素的 HTML 元素属性,并插入到页面
  4. 在 创建 input 元素时候,把元素保存在 state 中,以备之后使用
  5. 在 Count._init 中调用 _addHTML
// 1. 删除 HTML 中计数器的字元素
<div class="count-wrapper">
  <div class="count-number-container" id="count_1"></div>
  <div class="count-number-container" id="count_2"></div>
</div>
class Count{
  constructor(props) {
    this.state = {
      id: props.id,
      value: props.value || 0,
      change: props.change,
      _countNumberInput:null,
    };

    this._$ = selector => document.querySelector(selector);//用于选择DOM
    this._createElement = type => document.createElement(type);//用于创建DOM
    this._setContent = (elem, content) => elem.textContent = content//用于文本赋值
    this._appendChild = (container, node) => container.appendChild(node);//用于增加子节点

    // 5. 在 Count.\_init 中调用 \_addHTML
    this._addHTML();
  }

  // 2. 在 Count 中定义 \_addHTML 方法
  _addHTML(){

    // 3. 创建计数器子元素的 HTML 元素属性,并插入到页面
    let $ = this._$;
    let idContainer = $(`#${this.state.id}`);
    let countNumberContainer = this._createElement('div');
    let countNumberContent   = this._createElement('div');
    let countNumberBtnDecrease = this._createElement('span');
    let countNumberBtnIncrease = this._createElement('span');
    let countNumberInput = this._createElement('input');
    countNumberContent.setAttribute("class","count-number-content");
    countNumberContainer.setAttribute("class","count-number-container");
    countNumberBtnDecrease.setAttribute("class","count-number-btn_decrease");
    countNumberBtnIncrease.setAttribute("class","count-number-btn_increase");
    countNumberInput.setAttribute("class","count-number-input");
    countNumberInput.setAttribute('type','text');
    countNumberInput.value = this.state.value;
    countNumberInput.disabled = true;
    this._setContent(countNumberBtnIncrease,'+');
    this._setContent(countNumberBtnDecrease,'-');
    this._appendChild(countNumberContent,countNumberInput);
    this._appendChild(countNumberContainer,countNumberBtnDecrease);
    this._appendChild(countNumberContainer,countNumberBtnIncrease);
    this._appendChild(countNumberContainer,countNumberContent);
    this._appendChild(idContainer,countNumberContainer);
    
    // 4. 在 创建 input 元素时候,把元素保存在 state 中,以备之后使用
    this.state._countNumberInput = countNumberInput;
  }
}

绑定事件

  1. 创建设置元素的方法
  2. 创建获取元素的方法
  3. 创建递增方法
  4. 创建递减方法
  5. 在 _addHTML 方法中绑定递增、递减事件
class Count{
  ...,

  _addHTML {
    ...,

    // 5. 绑定事件
    countNumberBtnDecrease.addEventListener('click',this.decrease.bind(this));
    countNumberBtnIncrease.addEventListener('click',this.increase.bind(this));
  }

  // 1. 创建设置元素的方法
  setValue(val){
    let isNumber = (typeof val === 'number' );
    if(isNumber){
      this.state.value = val;
      this.state._countNumberInput.value = val;
      typeof this.state.change === 'function' && this.state.change(val)
    }else{
      console.log('参数必须为数字')
    }
  }

  // 2. 创建获取元素的方法
  getValue(){
    return this.state.value
  }

  // 3. 创建递增方法
  decrease(){
    let value = this.state.value - 1;
    this.setValue(value)
  }

  // 4. 创建递减方法
  increase(){
    let value = this.state.value + 1;
    this.setValue(value)
  }
}

实例化使用

  1. 创建 PAGE 单例
  2. 在 PAGE.data 创建 count 属性存储实例化后 count 对象
  3. 创建 PAGE.initCount 方法 用于实例化两个计数器,并传入不同的默认值
  4. 调用 PAGE.init
// 1. 创建 PAGE 单例
const PAGE = {
  // 2. 在 PAGE.data 创建 count 属性存储实例化后 count 对象
  data: {
    count_1: null,
    count_2: null
  },
  init:function(){
    this.initCount();
  },
  // 3. 创建 PAGE.initCount 方法 用于实例化两个计数器,并传入不同的默认值
  initCount:function(){
    PAGE.data.count_1 = new Count({
      id: 'count_1',
      value: 1,
      change:function(val){
        console.log(val)
      }
    })

    PAGE.data.count_2 = new Count({
      id: 'count_2',
      value: 10,
      change:function(val){
        console.log(val)
      }
    })
  }
}

// 4. 调用 PAGE.init
PAGE.init();

代码示例

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Count</title>
  <style type="text/css">
    .count-number-container{
      position: relative;
      display: inline-block;
      width: 180px;
      line-height: 38px;
    }

    .count-number-btn_decrease,
    .count-number-btn_increase {
      position: absolute;
      z-index: 1;
      top: 1px;
      width: 40px;
      height: auto;
      text-align: center;
      background: #f5f7fa;
      color: #606266;
      cursor: pointer;
      font-size: 13px;
      user-select:none;
    }

    .count-number-btn_decrease {
      left: 1px;
      border-radius: 4px 0 0 4px;
      border-right: 1px solid #dcdfe6;
    }

    .count-number-btn_increase {
      right: 1px;
      border-radius: 0 4px 4px 0;
      border-left: 1px solid #dcdfe6;
    }

    .count-number-input{
      box-sizing: border-box;
      display: inline-block;
      width: 100%;
      height: 40px;
      padding: 0 50px;
      border-radius: 4px;
      border: 1px solid #dcdfe6;
      background-color: #fff;
      font-size: inherit;
      line-height: 40px;
      color: #606266;
      text-align: center;
      outline: none;
      transition: border-color .2s cubic-bezier(.645,.045,.355,1);
    }
  </style>
</head>
<body>
  <div class="count-wrapper">
    <div class="count-number-container" id="count_1">
      <!-- <span class="count-number-btn_decrease">-</span>
      <span class="count-number-btn_increase">+</span>
      <div class="count-number-content">
        <input class="count-number-input" type="text" disabled name="count" value="0">
      </div> -->
    </div>

    <div class="count-number-container" id="count_2">
      <!-- <span class="count-number-btn_decrease">-</span>
      <span class="count-number-btn_increase">+</span>
      <div class="count-number-content">
        <input class="count-number-input" type="text" disabled name="count" value="0">
      </div> -->
    </div>
  </div>

  <script type="text/javascript">


  class Count{
    constructor(props) {
      this.state = {
        id: props.id,
        value: props.value || 0,
        change: props.change,
        _countNumberInput:null,
      };

      this._$ = selector => document.querySelector(selector);//用于选择DOM
      this._createElement = type => document.createElement(type);//用于创建DOM
      this._setContent = (elem, content) => elem.textContent = content//用于文本赋值
      this._appendChild = (container, node) => container.appendChild(node);//用于增加子节点

      //启动流程
      this._init();
    }

    _init(){
      this._addHTML();
    }

    _addHTML(){
      let $ = this._$;
      let idContainer = $(`#${this.state.id}`);
      let countNumberContainer = this._createElement('div');
      let countNumberContent   = this._createElement('div');
      let countNumberBtnDecrease = this._createElement('span');
      let countNumberBtnIncrease = this._createElement('span');
      let countNumberInput = this._createElement('input');
      countNumberContent.setAttribute("class","count-number-content");
      countNumberContainer.setAttribute("class","count-number-container");
      countNumberBtnDecrease.setAttribute("class","count-number-btn_decrease");
      countNumberBtnIncrease.setAttribute("class","count-number-btn_increase");
      countNumberInput.setAttribute("class","count-number-input");
      countNumberInput.setAttribute('type','text');
      countNumberInput.value = this.state.value;
      countNumberInput.disabled = true;
      this._setContent(countNumberBtnIncrease,'+');
      this._setContent(countNumberBtnDecrease,'-');
      this._appendChild(countNumberContent,countNumberInput);
      this._appendChild(countNumberContainer,countNumberBtnDecrease);
      this._appendChild(countNumberContainer,countNumberBtnIncrease);
      this._appendChild(countNumberContainer,countNumberContent);
      this._appendChild(idContainer,countNumberContainer);
      this.state._countNumberInput = countNumberInput;

      // 绑定事件
      countNumberBtnDecrease.addEventListener('click',this.decrease.bind(this));
      countNumberBtnIncrease.addEventListener('click',this.increase.bind(this));
    }

    // 获取元素
    getValue(){
      return this.state.value
    }

    // 设置元素
    setValue(val){
      let isNumber = (typeof val === 'number' );
      if(isNumber){
        this.state.value = val;
        this.state._countNumberInput.value = val;
        typeof this.state.change === 'function' && this.state.change(val)
      }else{
        console.log('参数必须为数字')
      }
    }

    // 减少
    decrease(){
      let value = this.state.value - 1;
      this.setValue(value)
    }

    // 增加
    increase(){
      let value = this.state.value + 1;
      this.setValue(value)
    }
  }


  const PAGE = {
    data: {
      count_1: null,
      count_2: null
    },
    init:function(){
      this.initCount();
    },
    initCount:function(){
      PAGE.data.count_1 = new Count({
        id: 'count_1',
        value: 1,
        change:function(val){
          console.log(val)
        }
      })

      PAGE.data.count_2 = new Count({
        id: 'count_2',
        value: 10,
        change:function(val){
          console.log(val)
        }
      })
    }
  }

  PAGE.init();
  </script>
</body>
</html>