class类学习 — 封装elementUI的dialog组件

前言

在ES6之前,准确来说JavaScript语言只有对象,没有类的概念。生成实例对象的传统方法是通过构造函数,与传统的面向对象语言(比如 C++ 和 Java)差异很大,ES6 提供了更接近传统语言的写法,引入了 class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。但需要清楚的是ES6中class只是构造函数的一种语法糖,并非新鲜玩意,class能实现的,我们通过ES5构造函数同样可以实现。

一、构造函数与class写法的部分区别

【1.1】构造函数

  function Person(name, age) {
    this.name = name;
    this.age = age;
  }
  Person.prototype.sayHi = function () {
    console.log("您好");
  };
  let person = new Person("dingFY", 20);
  console.log(person.name) // dingFY
  console.log(person.age) // 20
  person.sayHi() // 您好

【1.2】类

  class Person {
    constructor(name, age) {
      this.name = name;
      this.age = age;
    };
    sayHi() {
      console.log("您好");
    };
  };
  let person = new Person('dingFY', 20);
  console.log(person.name) // dingFY
  console.log(person.age) // 20
  person.sayHi() // 您好

【1.3】 主要区别

  1. 构造函数名Person在class写法时变成了类名,但调用方式不变,依然通过new关键字创建实例
  2. 构造函数中this相关操作,在class写法时归纳到了constructor方法中,也就是说ES5的构造函数Person对应ES6的Person类中的constructor构造方法
  3. ES5中原型上的方法sayHi在ES6 class写法中直接写在了内部,同时省略了function关键字
// 对于方法添加,下面这两种写法是等效的
// ES5
function Parent() {};
Parent.prototype = {
    constructor: function () {},
    sayName: function () {},
    sayAge: function () {}
};

// ES6
class Parent {
    constructor() {}
    sayName() {}
    sayAge() {}
};

// constructor()、sayName()、sayAge()这三个方法,其实都是定义在Parent.prototype上面
// 因此,在类的实例上面调用方法,其实就是调用原型上的方法。

二、类的基础用法

【2.1】类定义

  // 匿名类
  let Example = class {
    constructor(a) {
      this.a = a;
    }
  }
  // 命名类
  let Example = class Example {
    constructor(a) {
      this.a = a;
    }
  }

【2.2】类声明

// 类声明
class Example {
  constructor(a) {
    this.a = a;
  }
}

// 不可重复声明
// class Example {}
// class Example {} 
// 报错:Uncaught SyntaxError: Identifier 'Example' has already been declared

// let Example = class {}
// class Example {} 
// 报错:Uncaught SyntaxError: Identifier 'Example' has already been declared

【2.3】注意

  • class不存在变量提升,所以需要先定义再使用。因为ES6不会把类的声明提升到代码头部,但是ES5就不一样,ES5存在变量提升,可以先使用,然后再定义。
//ES5可以先使用再定义,存在变量提升
new A();
function A(){}

//ES6不能先使用再定义,不存在变量提升 会报错
new B(); // Uncaught ReferenceError: Cannot access 'B' before initialization
class B{}
  • 在类中声明方法的时候,千万不要给该方法加上function关键字,方法之间也不要用逗号分隔,否则会报错
  class Example {
    constructor(a) {
      this.a = a;
    }
    getName() {}      // 不需要逗号
    function getAge() // 报错
  }
  • 类的内部所有定义的方法,都是不可枚举的,这与ES5构造函数行为不一致
  class Example {
      constructor(x, y) {}
      getName() {}
  }
  console.log(Object.keys(Example.prototype)) // []
  console.log(Object.getOwnPropertyNames(Example.prototype))  // ["constructor","getName"]
  •  constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。
class Example {}
// 等同于
class Example {
  constructor() {}
}
  • 通过class类创建实例必须使用new关键字,不使用会报错,这点与构造函数不同,在ES5中其实我们不使用new也能调用构造函数创建实例,虽然这样做不符合规范。
  // ES6
  class Person {
    constructor() {}
  };
  let person = Person(); // 报错

  // ES5
  let Person = function (name) {
    this.name = name;
  };
  let person = Person(); // 不符合规范
  • 与 ES5 一样,类的所有实例共享一个原型对象

  class Person {
    constructor() {}
  };

  let p1 = new Person()
  let p2 = new Person()
  console.log(p1.__proto__ === p2.__proto__) // true
  • ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。name属性总是返回紧跟在class关键字后面的类名。

class Person{}
Person.name // "Point"
  • 类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式

三、取值函数(getter)和存值函数(setter)

与 ES5 一样,在“类”的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为

class Person {
    constructor() {
        // ...
    }
    get name() {
        return 'getter';
    }
    set name(value) {
        console.log('setter: ' + value);
    }
}

let son = new Person();

son.name = 'dingFY' // setter: dingFY
console.log(son.name) // getter

四、静态属性和静态方法

【4.1】静态属性

静态属性指的是 Class 本身的属性,写法是在实例属性的前面,加上static关键字

class Person {
    static age = 18;
    constructor() {
        console.log(Person.age); 
    }
}
let person = new Person() // 18

【4.2】静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是只能在类内部使用或者直接通过类来调用,这就称为“静态方法”。如果静态方法包含this关键字,这个this指的是类,而不是实例。父类的静态方法,可以被子类继承。

  class Foo {
    static classMethod() {
      console.log('test')
    }
  }

  // 类调用静态方法
  Foo.classMethod() // 'test'

  // 实例调用静态方法
  let foo = new Foo();
  foo.classMethod() // TypeError: foo.classMethod is not a function
  class Foo {
    static classMethod() {
      console.log('test')
    }
  }

  // 父类的静态方法可以被子类继承
  class Bar extends Foo {}
  Bar.classMethod() // 'test'

五、Class 的继承

  • class 通过 extends 关键字实现类的继承,子类可以继承父类的所有属性和方法。
class Person {}
class Son extends Person {}
  • 子类 constructor 方法中必须有 super ,且必须出现在 this 之前,否则新建实例时会报错
    class Person {
        constructor(age) {
            this.age = age
        }
        sayHi() {
            return '您好'
        }
    }

    class Son extends Person {
        constructor(age, name) {
            super(age) // 子类继承必须要有super(), 且必须在this前面
            this.name = name
        }
        sayHi() {
            console.log(super.sayHi() + `,我是${this.name}`)
        }
    }

    let son = new Son(18, 'dingFY');
    console.log(son.age) // 18
    console.log(son.name) // dingFY
    son.sayHi() // 您好,我是dingFY

六、实例

【6.1】封装弹框组件

  // dialog组件封装
  class Dialog {
    constructor(options) {
      // 合并默认配置和用户传的配置参数;
      this.opts = Object.assign({
        width: "30%",
        height: "200px",
        title: "标题",
        content: "内容",
        dragable: false, //是否可拖拽
        maskable: true, //是否有遮罩
        isCancel: true, //是否有取消按钮
        success() {
          console.log('点击确定按钮处理逻辑'); //
        },
        cancel() {
          console.log('点击取消按钮处理逻辑');
        }
      }, options);
      this.init(); // 自动调用
    }
    init() {
      // 渲染视图
      this.renderView();
      // 捕获弹框点击事件
      this.dialogHtml.onclick = e => {
        switch (e.target.className) {
          case 'k-close': // 关闭按钮
            this.close();
            this.opts.cancel();
            break;
          case 'k-cancel': // 取消按钮
            this.close();
            this.opts.cancel();
            break;
          case 'k-primary': // 确定按钮
            this.close();
            this.confim();
            break;
        }
      }
    }

    // 确定按钮操作
    confim(value) {
      this.opts.success(value);
    }

    //关闭对话框;
    close() {
      this.dialogHtml.querySelector(".k-wrapper").style.display = "none";
      this.dialogHtml.querySelector(".k-dialog").style.display = "none";
    }

    // 显示对话框;
    open() {
      // 是否显示遮罩层
      if (this.opts.maskable) {
        this.dialogHtml.querySelector(".k-wrapper").style.display = "block";
      }
      // 是否可拖拽
      if (this.opts.dragable) {
        let dialog = this.dialogHtml.querySelector(".k-dialog")
        let drag = new Drag(dialog);
      }
      this.dialogHtml.querySelector(".k-dialog").style.display = "block";
    }

    // 渲染视图
    renderView() {
      this.dialogHtml = document.createElement("div");
      this.dialogHtml.innerHTML = `<div class="k-wrapper"></div>
                <div class="k-dialog"  style="width:${this.opts.width};height:${this.opts.height}">
                    <div class="k-header">
                        <span class="k-title">${this.opts.title}</span><span class="k-close">X</span>
                    </div>
                    <div class="k-body">
                        <span>${this.opts.content}</span>
                    </div>
                    <div class="k-footer">
                        ${this.opts.isCancel ? '<span class="k-cancel">取消</span>' : ''}
                        <span class="k-primary">确定</span>
                    </div>
                </div>`;
      document.querySelector("body").appendChild(this.dialogHtml);
    }
  }

【6.2】封装拖拽方法

  // 拖拽方法封装
  class Drag {
    constructor(ele) {
      this.ele = ele
      this.downFn();
    }

    // 按下鼠标
    downFn() {
      this.ele.onmousedown = e => {
        let ev = e || window.event;
        let x = ev.clientX - this.ele.offsetLeft;
        let y = ev.clientY - this.ele.offsetTop;
        this.moveFn(x, y);
        this.upFn();
      }
    }

    // 移动鼠标
    moveFn(x, y) {
      this.ele.onmousemove = e => {
        let ev = e || window.event;
        let xx = ev.clientX;
        let yy = ev.clientY;
        this.setStyle(xx - x, yy - y);
      }
    }

    // 设置拖拽后元素的定位
    setStyle(leftNum, topNum) {
      leftNum = leftNum < 0 ? 0 : leftNum;
      topNum = topNum < 0 ? 0 : topNum;
      this.ele.style.left = leftNum + "px";
      this.ele.style.top = topNum + "px";
    }

    // 鼠标抬起
    upFn() {
      this.ele.onmouseup = () => {
        this.ele.onmousemove = "";
      }
    }
  }

【6.3】通过继承封装扩展弹框组件

  // 扩展Dialog, 可输入内容
  class ExtendsDialog extends Dialog {
    constructor(options) {
      super(options);
      this.renderInput();
    }
    renderInput() {
      let myInput = document.createElement("input");
      myInput.type = "text";
      myInput.classList.add("input-inner");
      this.dialogHtml.querySelector(".k-body").appendChild(myInput);
    }
    confim() {
      let value = this.dialogHtml.querySelector(".input-inner").value;
      super.confim(value);
    }
  }

【6.4】完整代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    .k-dialog {
      width: 30%;
      z-index: 2001;
      display: block;
      position: absolute;
      background: #fff;
      border-radius: 2px;
      box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
      margin: 0 auto;
      top: 15vh;
      left: 30%;
      display: none;
    }

    .k-wrapper {
      position: fixed;
      left: 0px;
      top: 0px;
      bottom: 0px;
      right: 0px;
      background: black;
      opacity: 0.4;
      z-index: 2000;
      display: none;
    }

    .k-header {
      padding: 10px 20px 30px;
    }

    .k-header .k-title {
      line-height: 24px;
      font-size: 18px;
      color: #303133;
      float: left;
    }

    .k-body {
      padding: 30px 20px;
      color: #606266;
      font-size: 14px;
    }

    .k-footer {
      padding: 10px 20px 30px;
      position: absolute;
      bottom: 0;
      right: 0;
    }

    .k-close {
      color: #909399;
      font-weight: 400;
      float: right;
      cursor: pointer;
    }

    .k-cancel {
      color: #606266;
      border: 1px solid #dcdfe6;
      text-align: center;
      cursor: pointer;
      padding: 12px 20px;
      font-size: 14px;
      border-radius: 4px;
      font-weight: 500;
      margin-right: 10px;
    }

    .k-cancel:hover {
      color: #409eff;
      background: #ecf5ff;
      border-color: #c6e2ff;
    }

    .k-primary {
      border: 1px solid #dcdfe6;
      text-align: center;
      cursor: pointer;
      padding: 12px 20px;
      font-size: 14px;
      border-radius: 4px;
      font-weight: 500;
      background: #409eff;
      color: #fff;
      margin-left: 10px;
    }

    .k-primary:hover {
      background: #66b1ff;
    }

    .k-input {
      width: 100%;
      margin-left: 20px;
      margin-bottom: 20px;
    }

    .input-inner {
      -webkit-appearance: none;
      background-color: #fff;
      background-image: none;
      border-radius: 4px;
      border: 1px solid #dcdfe6;
      box-sizing: border-box;
      color: #606266;
      display: inline-block;
      font-size: inherit;
      height: 40px;
      line-height: 40px;
      outline: none;
      padding: 0 15px;
      transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
      width: 100%;
      margin-top: 20px;
    }
  </style>
</head>

<body>
  <button class="dialog1">dialog1</button>
  <button class="dialog2">dialog2</button>
</body>
<script>
  // 拖拽方法封装
  class Drag {
    constructor(ele) {
      this.ele = ele
      this.downFn();
    }

    // 按下鼠标
    downFn() {
      this.ele.onmousedown = e => {
        let ev = e || window.event;
        let x = ev.clientX - this.ele.offsetLeft;
        let y = ev.clientY - this.ele.offsetTop;
        this.moveFn(x, y);
        this.upFn();
      }
    }

    // 移动鼠标
    moveFn(x, y) {
      this.ele.onmousemove = e => {
        let ev = e || window.event;
        let xx = ev.clientX;
        let yy = ev.clientY;
        this.setStyle(xx - x, yy - y);
      }
    }

    // 设置拖拽后元素的定位
    setStyle(leftNum, topNum) {
      leftNum = leftNum < 0 ? 0 : leftNum;
      topNum = topNum < 0 ? 0 : topNum;
      this.ele.style.left = leftNum + "px";
      this.ele.style.top = topNum + "px";
    }

    // 鼠标抬起
    upFn() {
      this.ele.onmouseup = () => {
        this.ele.onmousemove = "";
      }
    }
  }


  // dialog组件封装
  class Dialog {
    constructor(options) {
      // 合并默认配置和用户传的配置参数;
      this.opts = Object.assign({
        width: "30%",
        height: "200px",
        title: "标题",
        content: "内容",
        dragable: false, //是否可拖拽
        maskable: true, //是否有遮罩
        isCancel: true, //是否有取消按钮
        success() {
          console.log('点击确定按钮处理逻辑'); //
        },
        cancel() {
          console.log('点击取消按钮处理逻辑');
        }
      }, options);
      this.init(); // 自动调用
    }
    init() {
      // 渲染视图
      this.renderView();
      // 捕获弹框点击事件
      this.dialogHtml.onclick = e => {
        switch (e.target.className) {
          case 'k-close': // 关闭按钮
            this.close();
            this.opts.cancel();
            break;
          case 'k-cancel': // 取消按钮
            this.close();
            this.opts.cancel();
            break;
          case 'k-primary': // 确定按钮
            this.close();
            this.confim();
            break;
        }
      }
    }

    // 确定按钮操作
    confim(value) {
      this.opts.success(value);
    }

    //关闭对话框;
    close() {
      this.dialogHtml.querySelector(".k-wrapper").style.display = "none";
      this.dialogHtml.querySelector(".k-dialog").style.display = "none";
    }

    // 显示对话框;
    open() {
      // 是否显示遮罩层
      if (this.opts.maskable) {
        this.dialogHtml.querySelector(".k-wrapper").style.display = "block";
      }
      // 是否可拖拽
      if (this.opts.dragable) {
        let dialog = this.dialogHtml.querySelector(".k-dialog")
        let drag = new Drag(dialog);
      }
      this.dialogHtml.querySelector(".k-dialog").style.display = "block";
    }

    // 渲染视图
    renderView() {
      this.dialogHtml = document.createElement("div");
      this.dialogHtml.innerHTML = `<div class="k-wrapper"></div>
                <div class="k-dialog"  style="width:${this.opts.width};height:${this.opts.height}">
                    <div class="k-header">
                        <span class="k-title">${this.opts.title}</span><span class="k-close">X</span>
                    </div>
                    <div class="k-body">
                        <span>${this.opts.content}</span>
                    </div>
                    <div class="k-footer">
                        ${this.opts.isCancel ? '<span class="k-cancel">取消</span>' : ''}
                        <span class="k-primary">确定</span>
                    </div>
                </div>`;
      document.querySelector("body").appendChild(this.dialogHtml);
    }
  }


  // 当用户点击dialog1按钮创建弹框
  document.querySelector(".dialog1").onclick = function () {
    let dialog1 = new Dialog({
      width: '30%',
      height: "200px",
      title: "弹框标题",
      content: "弹框内容",
      dragable: true,
      isCancel: true,
      maskable: true,
      success() {
        console.log('点击确定按钮处理逻辑');
      },
      cancel() {
        console.log('点击确定按钮处理逻辑');
      }
    })
    dialog1.open();
  }


  // 扩展Dialog, 可输入内容
  class ExtendsDialog extends Dialog {
    constructor(options) {
      super(options);
      this.renderInput();
    }
    renderInput() {
      let myInput = document.createElement("input");
      myInput.type = "text";
      myInput.classList.add("input-inner");
      this.dialogHtml.querySelector(".k-body").appendChild(myInput);
    }
    confim() {
      let value = this.dialogHtml.querySelector(".input-inner").value;
      super.confim(value);
    }
  }

  // 当用户点击dialog2按钮创建弹框
  document.querySelector(".dialog2").onclick = function () {
    let dialog2 = new ExtendsDialog({
      width: "30%",
      height: "250px",
      title: "扩展弹框",
      content: "扩展弹框内容",
      isCancel: true,
      maskable: true,
      success(val) {
        console.log(val);
      },
      cancel() {
        console.log('点击取消');
      }
    })
    dialog2.open();
  }
</script>

</html>

【6.5】查看效果

文章每周持续更新,可以微信搜索「 前端大集锦 」第一时间阅读,回复【视频】【书籍】领取200G视频资料和30本PDF书籍资料

猜你喜欢

转载自blog.csdn.net/qq_38128179/article/details/111085282