JavaScript 面向对象

JavaScript 面向对象

1. 面向对象介绍

1.1 面向过程

POP(Process-oriented programming)

image-20230501160647827

1.2 面向对象

OOP (Object Oriented Programming)

image-20230501160711668

image-20230501160722869

1.3 对比

image-20230501160801333

2. ES6 中的类和对象

2.1 前言

image-20230501160818027

2.2 对象

image-20230501160848634

  • 创建对象的三种方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Restudy() {
//以下代码是对对象的复习

//1.字面量创建对象
var ldh = {
name: '刘德华',
age: 18
}
console.log(ldh);

// 2.new object创建对象
var zxy = new Object()
zxy.name = '张学友'
zxy.age = 19
console.log(zxy);

//3.构造函数创建对象
function Star(name, age) {
this.name = name;
this.age = age;
}
var lm = new Star('黎明', 20)//实例化对象
console.log(lm);
}

2.3 类class

2.31 解读

image-20230501161230949

2.32 创建类

image-20230501161244618

2.33 类 constructor 构造函数

image-20230501161257450

2.34 类创建添加属性和方法

image-20230501161447965

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 类与对象
// 1.创建类 class 创建一个明星类,通过class关键字创建类
class star {
// 类 constructor 构造函数
// constructor() 方法是类的构造函数(默认方法),"用于传递参数,返回实例对象",
// 通过 new 命令生成对象实例时,自动调用该方法。
// 如果没有显示定义, 类内部会自动给我们创建一个constructor()
constructor(uname, age) {
this.uname = uname;
this.age = age;
}//------------------------------------------->注意,方法与方法之间不需要添加逗号
sing(song) {//类里添加方法
console.log(this.uname + '唱' + song);
}
}
// 2.利用类创建对象 new 实例化对象
var cl = new star('成龙', 20)
cl.sing('北京欢迎你')
var zhj = new star('周华健', 21)
zhj.sing('123')
console.log(cl);
console.log(zhj);

// 语法规范:创建类,类名大写,类名后不加括号,new实例化对象时才加括号,括号里传参,类里的构造函数不加function

注意:

  1. 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写
  2. 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
  3. constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数
  4. 多个函数方法之间不需要添加逗号分隔
  5. 生成实例 new 不能省略
  6. 语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function

2.35 类的继承extend

  • 继承extend

image-20230501162028398

image-20230501162036725

2.36 类的访问super

  • 调用父类的构造函数

image-20230501162214402

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
// 对像计算案例
// 类的继承

// 1.书写父类
class Dadd {
constructor(x, y) {
this.x = x
this.y = y
}
methodd() {
console.log('父类里的方法:' + (this.x + this.y));
}
}

// 2.子类继承父类 extends
class Sonn extends Dadd {
constructor(x, y) {
// 使用super关键字调用父类中的构造函数,将实例化的数值传到父类的构造函数里
super(x, y)//必须在this之前调用

// 此时实例化的数值时是传到了子类的构造里,不可取
this.x = x
this.y = y
}
}

// 3.实例化
var child = new Sonn(1, 2);
var child2 = new Sonn(1, 20);
child.methodd();
child2.methodd();

  • 调用父类的普通函数

image-20230501162432587

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
// super关键字调用父类普通函数

// 1.class声明一个父类
class Father {
sing() {
return '我是父类里的‘唱’'
}
dance() {
return '我是父类里的‘跳’'
}
rap() {
return '我是父类里的‘rap’'
}
}

// 2.class声明一个子类extends继承父类
class Son extends Father {
sing() {
// console.log('我是子类里的‘唱’');
console.log(super.sing());//super关键字调用父类普通函数
}
dance() {
console.log('我是子类里的‘跳’');
}
rap() {
console.log('我是子类里的‘rap’');
}
}

// 实例化对象
var ikun = new Son()
ikun.sing()//返回子类里的sing()方法

// 继承中的属性或者方法查找原则: 就近原则
// 1. 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
// 2. 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
  • 子类继承父类的同时扩展自己的方法
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
// 父类有自己的方法
// 1.class声明一个父类
class Father {
constructor(x,y){
this.x = x;
this.y = y;
}
sum(){
console.log('父类里的sum():'+(this.x+this.y));
}
}

// 子类继承父类的同时扩展自己的方法
// 2.class声明一个子类extends继承父类
class Son extends Father {
// 子类自己扩展的方法
constructor(x,y){
// 使用super关键字调用父类中的构造函数,将实例化的数值传到父类的构造函数里
super(x, y)//必须在this之前调用

this.x=x;
this.y=y;
}
subtract(){
console.log('子类里的subtract():'+(this.x-this.y));
}
}

// 实例化对象
var son=new Son(5,1)
son.sum()
son.subtract()

2.36 使用类注意事项

  1. 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的

  2. 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)

  3. 如果子类想要继承父类的方法,同时在自己内部扩展自己的方法,利用super 调用父类的构造函数,super 必须在子类this之前调用

    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
     // 父类有加法方法
    class Father {
    constructor(x, y) {
    this.x = x;
    this.y = y;
    }
    sum() {
    console.log(this.x + this.y);
    }
    }
    // 子类继承父类加法方法 同时 扩展减法方法
    class Son extends Father {
    constructor(x, y) {
    // 利用super 调用父类的构造函数 super 必须在子类this之前调用,放到this之后会报错
    super(x, y);
    this.x = x;
    this.y = y;

    }
    subtract() {
    console.log(this.x - this.y);
    }
    }
    var son = new Son(5, 3);
    son.subtract(); //2
    son.sum();//8

    以上代码运行结果为:

    img7

  4. 时刻注意this的指向问题,类里面的共有的属性和方法一定要加this使用

    constructor中的this指向的是new出来的实例对象

    自定义的方法,一般也指向的new出来的实例对象

    绑定事件之后this指向的就是触发事件的事件源

  5. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象

    img2

    image-20230504221145430

3. 面向对象案例

3.1 案例要求

  1. 点击 tab栏,可以切换效果.
  2. 点击 + 号, 可以添加 tab 项和内容项.
  3. 点击 x 号, 可以删除当前的tab项和内容项.
  4. 双击tab项文字或者内容项文字可以修改里面的文字内容

3.2 实现页面

image-20230501163406334

3.3 案例分析

3.31 Tab对象

image-20230501163449236

3.32 切换功能

参考Dom自定义属性操作和排他思想

3.33 添加功能

image-20230501163504035

3.34 删除功能

image-20230501163514957

3.35 编辑功能

image-20230501163544213

3.4 代码结构

3.41 Html

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="index.css">
<script src="index.js"></script>
</head>

<body>
<main>
<h4>Js面对对象 动态添加标签页</h4>
<div class="tabbox">
<nav>
<ul>
<li class="liactive"><span >测试1</span><span class="close">X</span></li>
<li><span>测试2</span><span class="close">X</span></li>
<li><span>测试3</span><span class="close">X</span></li>
</ul>
<div class="add">
<span>+</span>
</div>
</nav>
<div class="tab_content">
<section class="conactive">测试1</section>
<section>测试2</section>
<section>测试3</section>
</div>
</div>
</main>
</body>

</html>

3.42 CSS

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
// out:index.css
* {
margin: 0;
padding: 0;
}

@font-face {
font-family: 'solar';
src: url('./YeZiGongChangChuanQiuShaXingKai-2.ttf');
}

main {
width: 800px;
margin: 150px auto;

h4 {
text-align: center;
padding: 20px 0;
}

.tabbox {
border: 1px solid rgb(209, 79, 28);

nav {
width: 100%;
height: 50px;
border-bottom: 1px solid #ccc;

ul {
list-style: none;

li {
float: left;
text-align: center;
line-height: 50px;
width: 100px;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
box-sizing: border-box;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;


span:first-child {
padding: 0 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

input {
width: 80%;
height: 25px;
}
}

.close {
position: absolute;
right: 0;
background-color: black;
color: white;
width: 15px;
height: 15px;
line-height: 15px;
font-size: 10px;
cursor: pointer;
border-bottom-left-radius: 100%;
}
}

li.liactive {
box-sizing: border-box;
border-bottom: 1px solid rgb(255, 255, 255)
}
}

.add {
float: right;
width: 20px;
height: 20px;
text-align: center;
line-height: 20px;
margin: 15px;
border: 1px solid #ccc;
cursor: pointer;
}
}

.tab_content {
width: 100%;
height: 400px;

section {
display: none;
margin: 10px;
height: 95%;
font-family: 'solar';

input {
text-align: start;
width: 100%;
height: 100%;
}
}

section.conactive {
display: block;
}
}
}
}

3.42 JS

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// 3.1功能需求
// 3.2案例准备

// 1. 获取到标题元素
// 2. 获取到内容元素
// 3. 获取到删除的小按钮 x号
// 4. 新建js文件,定义类,添加需要的属性方法(切换,删除,增加,修改)
// 5. 时刻注意this的指向问题

window.addEventListener('load', function (e) {
// 抽象对象: Tab 对象
// 声明一个类
var that;
class Tab {
constructor(tabbox) {
// 获取元素
this.tabbox = document.querySelector(tabbox);
this.add = this.tabbox.querySelector('.add').children[0];
this.ul = this.tabbox.querySelector('ul');
this.content = this.tabbox.querySelector('.tab_content');
that = this
// 立即初始化渲染页面
this.init()
}

// 初始化
init() {
this.updataNode()
// init初始化操作让相关的元素绑定事件
// 标签li
for (var i = 0; i < this.lis.length; i++) {
// 设置属性值
this.lis[i].index = i
// 点击切换事件
this.lis[i].onclick = this.toggleTab
// 点击删除事件
this.remove[i].onclick = this.deleteTab
// 点击编辑事件
this.spans[i].ondblclick = this.editTab
this.content.children[i].ondblclick = this.editTab
}
// 点击添加事件
this.add.onclick = this.addTab
}

// bug解决:动态获取点击元素
updataNode() {
this.lis = this.tabbox.querySelectorAll('li');
this.sections = this.tabbox.querySelectorAll('section');
this.remove = this.tabbox.querySelectorAll('.close');
this.spans = this.tabbox.querySelectorAll('nav>ul>li span:first-child');
}

// 清除类操作
clearClass() {
for (var i = 0; i < that.lis.length; i++) {
that.lis[i].className = ''
that.sections[i].className = ''
}
}

// 1. 该对象具有切换功能
toggleTab() {
that.clearClass()
this.className = 'liactive'
// this.index获取属性值
that.sections[this.index].className = 'conactive'
}

// 2. 该对象具有添加功能
addTab() {
that.clearClass()
// (1)创建li元素和section元素
// var newLi = document.createElement('li')
var random = Math.random() * 100
var newli = '<li class="liactive"><span >测试' + parseInt(random) + '</span><span class="close">X</span></li>'
var newSection = '<section class="conactive">测试' + parseInt(random) + '</section>'

// (2)将这俩个元素追加到对应的父元素里面
// 现在高级做法: 利用 insertAdjacentHTML() 可以直接把字符串格式元素添加到父元素中
// appendChild 不支持追加字符串的子元素, insertAdjacentHTML 支持追加字符串的元素
// insertAdjacentHTML(追加的位置,‘要追加的字符串元素’)
// 追加的位置有: beforeend 插入元素内部的最后一个子节点之后
// that.lis[0].parentNode.insertAdjacentHTML('beforeend', newli)
that.ul.insertAdjacentHTML('beforeend', newli)
that.content.insertAdjacentHTML('beforeend', newSection)

// bug:这里的新添的元素不具备上面的点击功能,因为这是在页面渲染完成后添加的
that.init()
}

// 3. 该对象具有删除功能
deleteTab(e) {
// 如果只有一个li那么就返回,不执行以下代码了
if (that.lis.length <= 1) return
// 阻止点击事件冒泡到父元素的点击事件上
e.stopPropagation();
var index = this.parentNode.index
// console.log(index);
// that.ul.removeChild(that.ul.children[index])
// that.content.removeChild(that.content.children[index])
that.lis[index].remove()
that.content.children[index].remove()
that.init()

// 当我们删除的不是选中状态的li的是时候,原来的选中状态li的状态保持不变
if (document.querySelector('.liactive')) return

// 从第一个li开始删除
// 当第一个li被关闭时,后面的一个li处于选中状态
index == 0 && that.lis[index++].click()

// 当我们删除了选中状态的li的时候,让前一个li处于选中状态
index--
// 要考虑最后一个被删除时,前面li怎么处理
// 从除第一个li之外的li开始删除
that.lis[index] && that.lis[index].click()
}

// 4. 该对象具有修改功能
editTab() {
// 先获取原来span里的文本值
var str = this.innerHTML;
// 双击禁止选定文字
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
// alert(123)
this.innerHTML = '<input type="text" />'
// 获取input元素
var input = this.children[0]
// 将input里的文本给原来的span
input.value = str
// 文本框里的文字属于选定状态
input.select()
// 当我们离开文本框就把文本框里面的值给span
input.onblur = function () {
this.parentNode.innerHTML = this.value;
}
// 按下回车也可以把文本框里面的值给span
input.onkeyup = function (e) {
if (e.keyCode === 13) {
// 手动调用表单失去焦点事件 不需要鼠标离开操作
this.blur();
}
}
}
}
// 实例化类
new Tab('.tabbox')
// tab.init()
});