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】 主要区别
- 构造函数名Person在class写法时变成了类名,但调用方式不变,依然通过new关键字创建实例
- 构造函数中this相关操作,在class写法时归纳到了constructor方法中,也就是说ES5的构造函数Person对应ES6的Person类中的constructor构造方法
- 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 一样,在“类”的内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为
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书籍资料
本文地址:https://blog.csdn.net/qq_38128179/article/details/111085282