JavaScript面向对象编程及设计模式
一、面向对象编程
1、简述
2、面向对象编程特点
3、封装
4、this
5、call和apply
6、new
7、继承
8、多态
JavaScript面向对象编程及设计模式
一、面向对象编程
1、简述
面向对象是一种程序的设计思想,与之对应的编程思想叫做面向过程
**例如:**比如我想要用代码描述一个场景,有一只叫做xiaoA的猫,吃了一个苹果,又吃了一条鱼,然后有一只叫做xiaoB的猫,吃了一根香蕉
// 面向过程
function xiaoAEatApple() {}
function xiaoAEatFish() {}
function xiaoBEatBanana() {}
xiaoAEatApple();
xiaoAEatFish();
xiaoBEatBanana();
1
2
3
4
5
6
7
// 面向对象
function Cat(name) {
this.name = name
}
Cat.prototype.eat = function(something) {}
let xiaoA = new Cat('xiaoA')
let xiaoB = new Cat('xiaoB')
xiaoA.eat('apple')
xiaoA.eat('fish')
xiaoB.eat('banana')
1
2
3
4
5
6
7
8
9
10
2、面向对象编程特点
面向对象注重于抽象事务,而面向过程注重于叙述事务
面向对象逻辑清晰有条理,而面向过程比较方面
JS通过函数和原型,模拟了传统面向对象编程中类的概念实现了面向对象的编程模式
面向对象的编程思想,主要为了实现3件事,封装,继承和多态
3、封装
<!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>
<script>
// 普通方法
// let carA = {
// name: 'xiaoA',
// eat() {
// console.log('xiaoA eat something')
// }
// }
// let carB = {
// name: 'xiaoB',
// eat() {
// console.log('xiaoB eat something')
// }
// }
// let carC = {
// name: 'xiaoC',
// eat() {
// console.log('xiaoC eat something')
// }
// }
// 代码重复量多,需要封装
// 封装
// 使用工厂模式封装
// function createCat(name) {
// let obj = {}
// obj.name = name;
// obj.eat = () => {
// console.log(name + 'eat something')
// }
// return obj;
// }
// let catA = createCat(xiaoA)
// let catB = createCat(xiaoB)
// let catC = createCat(xiaoC)
// 使用面向对象的方式进行封装
function CreateCat(name) { // 构造函数
this.name = name;
this.eat = () => {
console.log(this.name + 'eat something')
}
}
let catA = new CreateCat('xiaoA')
let catB = new CreateCat('xiaoB')
let catC = new CreateCat('xiaoC')
</script>
</head>
<body>
</body>
</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
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
4、this
// this
<!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>
<script>
// this
// 在函数执行的时候会在函数内部创建两个变量,arguments, this
// arguments是存储着实参的一个类数组变量
// this 指向函数的执行上下文 (谁调用这个函数, this就指向谁)
function aaa (a, b) {
console.log(arguments)
}
aaa(1, 2, 3, 4)
// 数组 var arr = {1, 2, 3, 4}
// 类数组对象 var arrObj = {0: 1, 1: 2, 2: 3, 3: 4, length: 4}
function bbb() {
console.log(this)
}
var objA = {
b: bbb,
c: {
d: bbb,
}
}
bbb(); // this 指向 window
objA.b() // this 指向 objA
objA.c.d(); // this 指向 objA.c
</script>
</head>
<body>
</body>
</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
37
38
39
40
41
42
5、call和apply
// call-apply
<!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>
<script>
// call apply 用来动态改变this的指向
// function aaa() {
// console.log(this)
// }
// var objA = {
// b: aaa
// }
// aaa() // this 指向window
// objA.b() // this 指向objA
// aaa.call(objA); // call 函数将aaa内的this 从指向 window 改成了 指向objA
// objA.b.call(window); // call 函数将aaa内的this 从指向 objA 改成了 指向window
function aaa(name, age) {
// console.log(this)
this.name = name
this.age = age
}
var objA = {
b: aaa
}
aaa.call(objA, 'xiaoA', 23); // call 函数将aaa内的this 从指向 window 改成了 指向objA
console.log(objA.name, objA.age); // 打印 xiaoA 23
objA.b.call(window, ['xiaoB', 30]); // call 函数将aaa内的this 从指向 objA 改成了 指向window
console.log(window.name, window.age)
objA.b.call(window, 'xiaoB', 30); // call 函数将aaa内的this 从指向 objA 改成了 指向window
console.log(window.name, window.age)
objA.b.apply(window, ['xiaoB', 30]); // call 函数将aaa内的this 从指向 objA 改成了 指向window
console.log(window.name, window.age)
</script>
</head>
<body>
</body>
</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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
6、new
new 做了哪些操作
1.创建一个空对象
2.将构造函数的prototype属性赋值给新对象的__proto__属性
3.将构造函数的this指向新对象
4.执行构造函数的代码
5.将新对象返回
<!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>
<script>
function CreateCat(name) {
this.name = name
}
let catA = new CreateCat('xiaoA')
console.log(catA.name)
/*
new 做了哪些操作
1.创建一个空对象
2.将构造函数的prototype属性赋值给新对象的__proto__属性
3.将构造函数的this指向新对象
4.执行构造函数的代码
5.将新对象返回
*/
// 闭包,自执行函数
var catB = (function() {
var obj = {}
obj.__proto__ = CreateCat.prototype;
CreateCat.call(obj, 'xiaoB');
return obj
})()
console.log(catB.name)
</script>
</head>
<body>
</body>
</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
37
38
39
7、继承
在声明函数的时候,会自动创建一个prototype属性,我们管他叫做原型, 一般用来存放实例公用的方法
prototype:
是子类继承的父类的属性,也就是当调用子类构造函数时,总的来说,这里只能是继承一个具体的对象,不能是一个类(es6后会有所改变)
prototype内容:
__proto__内容
是类继承父类之后,子类对象中的父类属性,里面的属性是可以通过子类对象直接取的,不需要用__proto__。
这里检索逻辑是:
先检索子类的直接属性是否存在,然后再检索__proto__,然后就是俄罗斯套娃,一层一层套下去,到最后还是没有就返回 undefined
<!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>
<script>
/*
new 做了哪些操作
1.创建一个空对象
2.将构造函数的prototype属性赋值给新对象的__proto__属性
3.将构造函数的this指向新对象
4.执行构造函数的代码
5.将新对象返回
*/
// 在声明函数的时候,会自动创建一个prototype属性,我们管他叫做原型, 一般用来存放实例公用的方法
function CreateCat(name) {
this.name = name;
}
// console.log('prototype:', CreateCat.prototype)
CreateCat.prototype.eat = function(something) {
console.log(this.name + ' eat ' + something)
}
var catA = new CreateCat('xiaoA')
catA.eat('fish')
/*
carA:
1. {} // 第一步创建新对象
2. {
__proto__: CreateCat.prototype
}
3. {
__proto__: CreateCat.prototype,
name: 'xiaoA'
}
4.执行构造函数的代码
5.return
*/
// 在JS里规定,访问对象属性的时候,如果对象下面没有这个属性,则去他下面的__proto__去寻找,如果没有,就一直向下寻找直到没有__proto__为止
console.log(catA)
// 类式继承
function A(name) {
this.name = name;
this.list = [1, 2, 3]
}
A.prototype.getName = () => {
console.log(this.name);
}
function SubA(name) {
this.subName = 'sub' + this.name;
}
SubA.prototype = new A()
var sa1 = new SubA('sa1');
console.log(sa1.list, sa1.name); // 打印出 [1, 2, 3] undefined
/*
new A() -> {
name: undefined,
list: [1,2,3],
__proto__: {
getName: fn,
constructor....
}
}
new SubA('sa1') -> {
subName: 'sub sa1',
__proto__: {
name: undefined,
list: [1,2,3],
__proto__: {
getName: fn,
constructor....
}
}
}
// 类式继承的问题
1.这种方法不支持父构造函数带参数
2.父构造函数里的方法和属性都会变成共有属性
*/
var sa1 = new SubA('sa1');
var sa2 = new SubA('sa2');
A.prototype.getName = function() {
console.log('fixed getName')
}
A.prototype.newFn = function() {
console.log('new Fn')
}
sa1.getName(); // 打印 fixed getName
sa2.newFn(); // 打印 new Fn
// 构造函数继承
function A(name) {
this.name = name;
this.list = [1, 2, 3]
}
A.prototype.getName = () => {
console.log(this.name);
}
function SubA(name) {
A.call(this, name)
this.subName = 'sub' + this.name;
}
var sa1 = new SubA('xiaoA');
console.log(sa1.name, sa1.subName); // 打印出 xiaoA subxiaoA
sa1.getName(); // 报错
/*
new SubA('xiaoA'); -> {
__proto__: {
constructor....
},
name: 'xiaoA',
list: [1, 2, 3],
subName: 'sub xiaoA'
}
// 构造函数继承问题
1.不能继承父构造函数的原型方法
*/
// 组合式继承
function A(name) {
this.name = name;
this.list = [1, 2, 3]
}
A.prototype.getName = () => {
console.log(this.name);
}
function SubA(name) {
A.call(this, name)
this.subName = 'sub' + this.name;
}
SubA.prototype = new A()
var sa1 = new SubA('xiaoA');
console.log(sa1.name, sa1.subName); // 打印出 xiaoA subxiaoA
sa1.getName(); // xiaoA
/*
new A() -> {
name: undefined,
list: [1, 2, 3],
__proto__: {
getName: fn
}
}
new SubA('xiaoA'); -> {
name: 'xiaoA',
list: [1, 2, 3],
subName: 'sub xiaoA',
__proto__: {
name: undefined,
list: [1, 2, 3],
__proto__: {
getName: fn
}
},
}
// 组合式继承问题
1.__proto__里的属性没有用
2.执行了两次父构造函数
问题点在于: SubA.prototype = new A()
*/
// 4.寄生组合式继承
function A(name) {
this.name = name;
this.list = [1, 2, 3]
}
A.prototype.getName = () => {
console.log(this.name);
}
function SubA(name) {
A.call(this, name)
this.subName = 'sub' + this.name;
}
// SubA.prototype = new A() // 优化这一句代码
function inheritPrototype(subClass, superClass) { // 子构造函数 父构造函数
function F() {};
F.prototype = superClass.prototype;
subClass.prototype = new F()
subClass.prototype.constructor = subClass; // 可有可无
}
inheritPrototype(SubA, A)
var sa1 = new SubA('xiaoA');
console.log(sa1.name, sa1.subName); // 打印出 xiaoA subxiaoA
sa1.getName(); // xiaoA
</script>
</head>
<body>
</body>
</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
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
8、多态
多态: 表示不同对象调用相同方法会产生不同结果
<!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>
<script>
// 多态: 表示不同对象调用相同方法会产生不同结果
function Base() {}
Base.prototype.initial = function() {
this.init()
}
function SubA() {
this.init = function () {
console.log('subA init')
}
}
function SubB() {
this.init = function () {
console.log('subB init')
}
}
SubA.prototype = new Base();
SubB.prototype = new Base();
var subA = new SubA()
var subB = new SubB()
subA.initial();
subB.initial();
</script>
</head>
<body>
</body>
</html>