面向对象

实例 1:选项卡的制作

干巴巴的说知识点,味同嚼蜡啊,所以还是举个栗子,动动手,理解的更好一些~

这个实例的目的:把原先面向过程的思想转变为面向对象的思想

起步:制作一个选项卡

先写一个选项卡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<body>
<div class="tab1">
<button style="background: red">按钮一</button>
<button>按钮二</button>
<button>按钮三</button>
<p>内容一</p>
<p style="display: none">内容二</p>
<p style="display: none">内容三</p>
</div>
</body>
<script>
// 这是原先面向过程的逻辑
let btns = document.querySelectorAll(".tab1 button");
let ps = document.querySelectorAll(".tab1 p");
btns.forEach((value, key) => {
value.onclick = function () {
ps.forEach((v, k) => {
if (key == k) {
btns[k].style.background = "red";
ps[k].style.display = "block";
} else {
btns[k].style.background = "";
ps[k].style.display = "none";
}
})
}
});
</script>

多个选项卡

乡亲们注意了,项目经理进村了,带着新的需求进村啦!!

新需求:如果是多个选项卡呢,5 个、10 个、100 个?我们不会一个一个复制吧?

新需求:第一个选项卡添加下一页功能,第二个选项卡添加轮播功能

思想:如果是多个选项卡复用,可以用封装调用的方法,就是把 Tab 封装一下,以后直接调用

而添加独特的功能时,我们这里有三个场景

  • 如果是所有 选项卡共性的功能:封装
  • 如果是 部分有 的功能(比方说 100 个选项卡里有 50 个):传配置参数,然后判断
  • 如果是 独一无二的 功能:单独处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function Tab(btns, ps, isNextPre = false, isAutoPlay = false) {
// 所有选项卡 共有的功能;
btns.forEach((value, key) => {
value.onclick = function () {
psFor(key);
};
});
// 功能:下一页
if (isNextPre) {
let num = 0;
document.querySelector(".nextPre").onclick = function () {
num++;
num = num > 2 ? 0 : num;
psFor(num);
};
}

// 功能:控制自动轮播
if (isAutoPlay) {
// 第二个选项卡的
document.querySelector(".autoPlay").onclick = function () {
let num = 0;
setInterval(() => {
num++;
num = num > 2 ? 0 : num;
psFor(num);
}, 1000);
};
}
function psFor(key) {
ps.forEach((v, k) => {
if (key == k) {
btns[k].style.background = "red";
ps[k].style.display = "block";
} else {
btns[k].style.background = "";
ps[k].style.display = "none";
}
});
}
}

let btns = document.querySelectorAll(".tab1 button");
let ps = document.querySelectorAll(".tab1 p");
Tab(btns, ps, true);
let btns2 = document.querySelectorAll(".tab2 button");
let ps2 = document.querySelectorAll(".tab2 p");
Tab(btns2, ps2, false, true);

提取功能

在上一个例子的基础上,我们做一些优化

优化:把独特的功能给提取出来,而不是放到共有的函数里

如何优化呢?用返还对象的方法(对象里可以包含函数,属性等值),我们可以在外部单独处理,例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function Tab(btns, ps, isNextPre = false, isAutoPlay = false) {
// 所有选项卡 共有的功能;
btns.forEach((value, key) => {
value.onclick = function () {
psFor(key);
};
});
function psFor(key) {
ps.forEach((v, k) => {
if (key == k) {
btns[k].style.background = "red";
ps[k].style.display = "block";
} else {
btns[k].style.background = "";
ps[k].style.display = "none";
}
});
}
let obj = {};
// 这里把 eleLength 属性和 psFor 方法挂到对象上,然后返回对象
obj.psFor = psFor;
obj.eleLength = btns.length;
return obj;
}

let btns = document.querySelectorAll(".tab1 button");
let ps = document.querySelectorAll(".tab1 p");
let tab1 = Tab(btns, ps); // 这里 tab1 接收 Tab 执行后返还的对象
let num = 0;
// 新添加的功能1:下一页
document.querySelector(".nextPre").onclick = function () {
num++;
num = num > tab1.eleLength - 1 ? 0 : num;
tab1.psFor(num);
};

let btns2 = document.querySelectorAll(".tab2 button");
let ps2 = document.querySelectorAll(".tab2 p");
let tab2 = Tab(btns2, ps2);
let num2 = 0;
// 新添加功能2:自动轮播
document.querySelector(".autoPlay").onclick = function () {
let num2 = 0;
setInterval(() => {
num2++;
num2 = num2 > tab2.eleLength - 1 ? 0 : num2;
tab2.psFor(num2);
}, 1000);
};

至此,一个工厂模式的选项卡,暂时告一段落,接下来介绍些基础的知识~


工厂模式

有的人就要问了:啥是工厂模式啊?

这里的工厂啊,就是我们把原料放到工厂里,然后它咕嘟咕嘟的加工,之后吐出来一个产品!

我们要做的呢,就是设计出这样的一个工厂!

面向对象的思路:抽象事物 --> 分析属性和方法

以下是一个 tab 选项卡的工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 工厂模式
function Tab() {
// 添加原料
let obj = {};

// 加工原料
obj.tablength = 1;
obj.psFor = function () {
console.log("psFor....");
};

// 出厂
return obj;
}

// 通过工厂,实例化对象
let tab1 = Tab();
let tab2 = Tab();

其实工厂模式,我们是不常用的,因为它有以下缺点:

  • 对象识别问题,不知道实例化的对象是从那个工厂出来的
  • 性能问题:占内存,如下代码可证实
1
2
3
4
5
let tab1 = Tab();
let tab2 = Tab();
console.log("tab1.psFor===tab2.psFor", tab1.psFor === tab2.psFor); // false
// 并不是在内存中的同一个地址,如果很多的话,很占内存
// 在内存上并不需要生成很多一样的

那么如何解决这个问题呢?

解决的方法是:使用构造函数,把方法放到公共空间,原型 prototype 上
讲解原型的话又会引申出另一个知识点, new 运算符

new 运算符

我们先看一个例子

1
2
3
4
5
function test() {
console.log("test..");
}
test(); // test..
new test(); // test..

其中 new test 并没有写(),但是 test 函数还是执行了,猜测一下应该是 new 帮我们执行的。

这里不深入讨论 new,说个总结,new 运算符的功能有如下几点:

  1. 执行函数;
  2. 自动创建一个空对象;
  3. 把创建的对象指向另外一个对象;
  4. 把空对象和函数里的 this 衔接起来;(this 指向实例化对象)
  5. 隐式返还 this;

简化工厂模式 —> 构造函数

从工厂模式到 new 构造函数,其实简单了不少

1
2
3
4
5
function Tab() {
// let obj = {}; // 2. 构造函数自动创建空对象
this.name = "张三"; // 3. 构造函数把创建的对象指向另外一个对象;
// return obj; // 5. 构造函数隐式返还 this;
}

用了构造函数,就会提供一个原型 prototype

从工厂模式到构造函数的主要的思路就是:

  • 属性放到构造函数中
  • 方法放到 prototype 原型上,(prototype 是公共空间,如果把方法放到构造函数中,就会在创建的时候又创建一个空间,消耗内存,所以把方法放到公共空间。)

以下是两个模式的例子:

1
2
3
4
5
6
7
8
9
10
11
12
// 工厂模式
function Tab() {
let obj = {};
obj.tablength = 1;
obj.psFor = function () {
console.log("psFor....");
};
return obj;
}
let tab1 = Tab();
let tab2 = Tab();
console.log(tab1.psFor === tab2.psFor); // false
1
2
3
4
5
6
7
8
9
10
11
// 构造函数
function Tab() {
this.name = "张三";
}
Tab.prototype.psFor = function () {
console.log("psFor...");
};
let tab1 = new Tab();
let tab2 = new Tab();
console.log(tab1.psFor === tab2.psFor); // true
console.log(tab1.__proto__ === Tab.prototype); // true

构造函数有两个要求

  1. 首字符大写(约定俗成的)
  2. 属性放在构造函数方法放在原型上,原型提供公共的空间(如果方法放到构造函数中,他也会占用内存,跟工厂模式就没有什么区别了)

实例化对象也是由两部分组成:构造函数+原型,原型和构造函数里公用一个this ,也就是指向实例化的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Tab(name) {
this.name = name;
// this.hobby = function(){
// console.log(this.name);
// }

// 可以把上面的代码解除注释
// 然后看一下 tab1.hobby === tab2.hobby的结果
}

Tab.prototype.hobby = function () {
console.log(this.name);
};
let tab1 = new Tab("张三");
tab1.hobby(); //张三
let tab2 = new Tab("李四");
tab2.hobby(); //李四

console.log(tab1.hobby === tab2.hobby); // true

每个原型上都有一个预定义属性:constructor,这个属性指向构造函数

构造器有啥用呢:可以判断对象类型

1
2
3
4
5
6
7
let str = "1212fdsf";
if (str.constructor === String) {
console.log("字符串");
} else {
console.log("不是");
}
// 字符串

仿写 new

上面提到了 new 的几个功能,接下来尝试手写一个 new
注意两点: 修改指针修改原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Tab() {
this.name = "张三";
}

function myNew(constructor, ...arg) {
let obj = {}; // 创建对象
constructor.call(obj, ...arg); // 修改指针
obj.__proto__ = constructor.prototype; // 修改原型
return obj;
}

Tab.prototype.psFor = function () {
console.log("psFor...");
};

let tab1 = myNew(Tab);
console.log(tab1);
// tab1.psFor();

至此第一个实例结束了。


实例 2:方块拖动(面向过程=>面向对象)

接着第二个实例,样式和 dom 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<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>
.mydiv1 {
width: 100px;
height: 100px;
background: red;
position: absolute;
}
.mydiv2 {
width: 100px;
height: 100px;
background: blue;
position: absolute;
left: 300px;
}
</style>
</head>
<body>
<div class="mydiv1"></div>
<div class="mydiv2"></div>
</body>

面向过程的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 面向过程;
mydiv1.onmousedown = (e) => {
let ev = e || window.event;
let x = ev.clientX - mydiv1.offsetLeft;
let y = ev.clientY - mydiv1.offsetTop;
mydiv1.onmousemove = (e) => {
let ev = e || window.event;
let xx = ev.clientX;
let yy = ev.clientY;
mydiv1.style.left = xx - x + "px";
mydiv1.style.top = yy - y + "px";
};
mydiv1.onmouseup = function () {
mydiv1.onmousemove = "";
};
};

我们要转换为面向对象,思路如下:

  • 首先分析属性:div 元素
  • 接着分析方法:鼠标按下、鼠标抬起、鼠标拖动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 面向对象;
function Drag(ele) {
this.ele = ele;
this.downFn();
}
// 功能拆分的越细,配置的越灵活
Drag.prototype.downFn = function () {
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();
};
};
Drag.prototype.moveFn = function (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);
};
};
Drag.prototype.setStyle = function (leftNum, topNum) {
this.ele.style.left = leftNum + "px";
this.ele.style.top = topNum + "px";
};

Drag.prototype.upFn = function () {
this.ele.onmouseup = () => {
this.ele.onmousemove = "";
};
};
let mydiv1 = document.querySelector(".mydiv1");
let mydiv2 = document.querySelector(".mydiv2");
let drag1 = new Drag(mydiv1);
let drag2 = new Drag(mydiv2);

扩充功能的方法:继承

基础

如果 Son 想要继承 Dad 中的属性,可以通过 applycallbind 这些方法继承过来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Dad(height) {
this.name = "张三";
this.age = 20;
this.height = height;
this.money = "$1000000";
}
function Son(height) {
// 这里的this,指向的是 Son实例化对象
Dad.call(this, height);
// Dad.apply(this,[height])
// Dad.bind(this)(height);
}
let newSon = new Son("178cm");
console.log(newSon);

其中关键的一步Dad.call(this,xxx) 如何理解这个?

我是这么记得,方法.call(要继承的对象,参数)
按照这个例子,也就是说构造函数中的 Dad里的方法啊,属性啊,拿过来指向了 Son 实例化对象了。
这里会有点难理解,但是理解这里很有必要!

但是父亲原型上的方法能不能继承呢?

答案是可以的,以下就是一个简单方法,不过有 bug,下文会讲到为什么会有 bug,以及如何修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Dad(height) {
this.name = "张三";
this.age = 20;
this.height = height;
this.money = "$1000000";
}
Dad.prototype.hobby = function () {
console.log("喜欢高尔夫");
};
function Son(height) {
Dad.call(this, height);
}
let newSon = new Son("178cm");
newSon.hobby(); // newSon.hobby is not a function

传递地址

有个很简单的方法,添加个Son.prototype = Dad.prototype;
但是这样又会带来传址问题(prototype 是对象),也就是说,修改子类的属性,会影响到父类,反之同理。

知道复杂数据类型是传递地址这个原因了,问题解决也就有方向了,

解决方法 1:组合继承

要消除这种影响,可以用到组合继承,注意原型中的预定义属性会被覆盖掉,所以还要写回来

1
2
3
4
5
6
let Link = function () {};
Link.prototype = Dad.prototype;
Son.prototype = new Link();

// Son.prototype 预定义属性constructor;
Son.prototype.constructor = Son;

方法一实战:拖拽色块(扩充功能)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<script>
// 面向对象;
function Drag(ele) {
this.ele = ele;
this.downFn();
}
Drag.prototype.downFn = function () {
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();
}
}
Drag.prototype.moveFn = function (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);
}
}
Drag.prototype.setStyle = function (leftNum, topNum) {
this.ele.style.left = leftNum + "px";
this.ele.style.top = topNum + "px";
}
Drag.prototype.upFn = function () {
this.ele.onmouseup = () => {
this.ele.onmousemove = "";
}
}
let mydiv1 = document.querySelector(".mydiv1");
let drag1 = new Drag(mydiv1);

// 继承父类
function LimitDrag(ele) {
Drag.call(this, ele);
}

// 创建搭桥牵线的
let Link = function () { };
Link.prototype = Drag.prototype;
LimitDrag.prototype = new Link();
LimitDrag.prototype.constructor = LimitDrag;

LimitDrag.prototype.setStyle = function (leftNum, topNum) {
leftNum = leftNum < 0 ? 0 : leftNum;
topNum = topNum < 0 ? 0 : topNum;
this.ele.style.left = leftNum + "px";
this.ele.style.top = topNum + "px";
}
let mydiv2 = document.querySelector(".mydiv2");
let drag2 = new LimitDrag(mydiv2);
</script>
解决方法 2:深拷贝
  • 判断是不是数组 Array.isArray()
  • 需要留意的是对象下的null也要判断。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function deepCopy(obj) {
// 深拷贝的时候注意数组和对象
let newObj = Array.isArray(obj) ? [] : {};
for (let i in obj) {
// for in 会循环原型上的属性
if (obj.hasOwnProperty(i)) {
if (typeof obj[i] === "object") {
if (obj[i] === null) {
newObj[i] = null;
} else {
newObj[i] = deepCopy(obj[i]);
}
} else {
newObj[i] = obj[i];
}
}
}
return newObj;
}

然后调用一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let obj = {
name: "张三",
age: 20,
arr: [1, 2, 3],
obj3: {},
test: undefined,
fn: function () {
console.log("fn...");
},
test2: null,
};
let obj2 = deepCopy(obj);
obj2.age = 30;
console.log(obj, obj2);

在这里插入图片描述

补充知识点:序列化(缺陷)

序列化复制对象,会丢失函数和值为undefined的参数,所以用的时候要小心。

1
2
3
4
5
6
7
8
9
10
11
let obj = {
name: "张三",

test: undefined,
fn: function () {
console.log("fn...");
},
};
let obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj, obj2);
// {name: "张三", test: undefined, fn: ƒ} {name: "张三"}
补充知识点:for in

for in 会循环原型上的属性,我们深复制的时候是不需要复制原型上的属性的。

1
2
3
4
5
6
7
8
9
let obj = {
name: "张三",
};
obj.__proto__.age = 30;
for (let i in obj) {
console.log(i);
// name
// age
}

原型链

原型其实是个对象

查找顺序

  1. 构造函数
  2. Drag.prototype
  3. Object.prototype
1
2
3
4
5
6
7
8
9
function Drag() {
// this.ele = "some value..."
}
Drag.prototype.ele = "prototype value....";
let obj = new Object();
Object.prototype.ele = "object value...";
console.log(Object.prototype.__proto__); // null
let drag1 = new Drag();
console.log(drag1.ele); // prototype value....

ES6 类

静态属性

方法和属性并不属于构造函数,而是属于实例化对象本身的。

当然也有属于构造函数本身,叫静态属性静态方法。用 static 来声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Drag {
static height = "178cm"; // es6添加静态属性
static test() {
// 静态方法
console.log("test...");
}
constructor() {
// 属性挂在构造函数
this.name = "张三";
}
hobby() {
// 方法自动放到原型上
console.log("篮球");
}
}
Drag.height = "178cm"; // es5 中添加静态属性的方法

ES6 类继承

用关键字 extend来继承,属性用 super()来拿过来

class Son extends Father

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Drag {
static height = "178cm";
static test() {
console.log("test...");
}
constructor(age) {
this.name = "张三";
this.age = age;
}
hobby() {
console.log("篮球");
}
setStyle() {
console.log("父类逻辑");
}
}
class LimitDrag extends Drag {
constructor(age) {
super(age); // 类似call一样
}
setStyle() {
super.setStyle(); // 如果我也想要父类的逻辑
console.log("子类逻辑");
}
}
// console.log(LimitDrag.height);
let drag2 = new LimitDrag(20);
drag2.setStyle();

包装对象

为什么基本数据类型(比方说 stringnumber)也会有某些方法呢,是因为系统帮我们包装了,以下是模拟系统的操作

1
2
3
4
5
6
7
function mysplit(str, method, arg) {
let temp = new String(str); // 临时对象
return temp[method](arg);
// 这里应该有销毁临时包装对象的操作,但是我没有写,要知道
}
let arr = mysplit(str, "split", " ");
console.log(arr);

判断对象类型*

constructor 能够判断实例化对象是从哪一个构造函数里出来的,也就是判断它的类型

1
2
3
4
5
6
7
function Person() {
this.name = "张三";
}

// Person.prototype.constructor;
let zhangsan = new Person();
console.log(zhangsan.constructor === Person);

instanceof 不仅判断 Array ,还去判断原型链 Object

1
2
3
let arr = [];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true

typeof

1
2
3
4
let arr = [];
let obj = {};
console.log(typeof arr); // Object
console.log(typeof obj); // Object

Object.prototype.toString.call(arr)

1
2
3
4
let res = Object.prototype.toString.call(arr);
let res2 = Object.prototype.toString.call(obj);
console.log(res);
console.log(res === "[object Array]");

组件样式模板

封装组件的两个原则

  • 对内:封闭
  • 对外:开放

合并配置推荐的方法 assign,下面是一个栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Dailog {
constructor(options) {
// 合并配置;
this.opts = Object.assign(
{
width: "30%",
height: "200px",
title: "测试标题",
content: "测试内容",
dragable: true, //是否可拖拽
maskable: true, //是否有遮罩
isCancel: false, //是否有取消
},
options
);
}
}
//用户调用
let dailog1 = new Dailog({
width: "40%",
height: "250px",
title: "测试标题",
isCancel: true,
maskable: false,
});

自定义事件

函数当成事件来执行 ,栗子如下

好处是:当有多个人来修改代码的时候,各执行各的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class MyEvent {
constructor() {
this.handle = {};
}
addEvent(eventName, fn) {
if (typeof this.handle[eventName] === "undefined") {
this.handle[eventName] = [];
}
this.handle[eventName].push(fn);
}
trigger(eventName) {
if (!(eventName in this.handle)) {
return;
}
this.handle[eventName].forEach((v) => {
v();
});
}
removeEvent(eventName, fn) {
if (!(eventName in this.handle)) {
return;
}
for (let i = 0; i < this.handle[eventName].length; i++) {
if (this.handle[eventName][i] === fn) {
this.handle[eventName].splice(i, 1);
break;
}
}
}
}
let newEvent = new MyEvent();
// 如果有新的人来了,写了几个方法,那么新建一个实例对象,然后添加触发即可

newEvent.addEvent("myEvent", fn1);
newEvent.addEvent("myEvent", fn2);
newEvent.removeEvent("myEvent", fn2);
newEvent.trigger("myEvent");

系统自定义事件 + 传参

如果想使用系统的自定义事件的话
首先,需要在我们的类上继承 EventTarget

1
class Dailog extends EventTarget {}

然后,添加一个监听:this.addEventListener("你的自定义事件名字",要调用的事件函数名);

接着可以用 new Event() 或者 new CustomEvent() 来创建事件对象,区别是,后者可以在detail中传递参数

1
2
3
4
5
6
7
8
9
// 传递参数
let success = new CustomEvent("success",{
detail:value
});

// 接收参数
success(e) {
console.log(e.detail);
},

super 拿到父类,继承过来

举个栗子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
class Dailog extends EventTarget {
constructor(options) {
super();
// 合并配置;
this.opts = Object.assign(
{
width: "30%",
height: "200px",
title: "测试标题",
content: "测试内容",
dragable: true, //是否可拖拽
maskable: true, //是否有遮罩
isCancel: false, //是否有取消
success() {
console.log("默认点击确定了!!");
},
cancel() {
console.log("默认点击取消!!");
},
},
options
);
// console.log(opts);
this.init();
}
init() {
this.renderView();
let cancel = new Event("cancel");
this.addEventListener("cancel", this.opts.cancel);
this.addEventListener("success", this.opts.success);
this.dailogHtml.onclick = (e) => {
switch (e.target.className) {
case "k-close":
this.close();
this.dispatchEvent(cancel);
break;
case "k-cancel":
this.close();
this.dispatchEvent(cancel);
break;
case "k-primary":
this.close();
this.confim();
break;
}
};
}
confim(value) {
let success = new CustomEvent("success", {
detail: value,
});
// 如果想使用系统自带的自定义函数,那么想要触发,就需要用 dispatchEvent(自定义事件对象) 的方法
// 自定义事件对象可以用 new Event() 和 new CustomEvent 来创建
this.dispatchEvent(success);
}
//关闭 对话框;
close() {
this.dailogHtml.querySelector(".k-wrapper").style.display = "none";
this.dailogHtml.querySelector(".k-dialog").style.display = "none";
}
// 显示对话框;
open() {
if (this.opts.maskable) {
this.dailogHtml.querySelector(".k-wrapper").style.display = "block";
}
this.dailogHtml.querySelector(".k-dialog").style.display = "block";
}
renderView() {
this.dailogHtml = document.createElement("div");
this.dailogHtml.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.dailogHtml);
}
}

class ExtendsDailog extends Dailog {
constructor(options) {
super(options);
this.renderInput();
}
renderInput() {
let myInput = document.createElement("input");
myInput.type = "text";
myInput.classList.add("input-inner");
this.dailogHtml.querySelector(".k-body").appendChild(myInput);
// console.log(this.dailogHtml.querySelector(".input-inner").value);
}
confim() {
// 覆盖父组件中的confim方法
let value = this.dailogHtml.querySelector(".input-inner").value;
super.confim(value); // 继承 父组件中的方法,并给父组件中的 confim 传值
}
}

//用户调用
let dailog2 = new ExtendsDailog({
width: "40%",
height: "250px",
title: "测试标题",
isCancel: true,
maskable: false,
success(e) {
console.log("点击确定了!!", e.detail);
},
cancel() {
console.log("点击取消!!");
},
});

document.querySelector(".dailog2").onclick = function () {
dailog2.open();
};

Jquery

我整理到这里了~

JQuery 源码简单刨析,分析源码不再痛苦~