设计模式-面向对象编程

前言

面向对象编程(Object-oriented Programming,OOP)是一种程序设计范式,它将对象作为程序的基本单元,将程序和数据封装其中,以提高程序的重用性、灵活性和扩展性。

面向对象编程:

  • 避免更多的全局变量
  • 避免方法复制造成资源消耗
  • 利于重复使用

JS 中的对象

在学习面向对象之前,我们首先要了解一下 JS 中的对象的灵活性。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
// 用对象收编变量
var CheckObjet = {
checkName: function(){},
checkEmail: function(){}
}

// 函数也是一种对象(但方法不可复制)
var CheckObjet = function(){}
CheckObjet.checkName = function(){}
CheckObjet.checkEmail = function(){}

// 真假对象(调用返回新对象,但创建的对象和 CheckObjet 没有关系)
var CheckObjet = function(){
return {
checkName: function(){},
checkEmail: function(){}
}
}

// 类(通过 new 生成新对象)
var CheckObjet = function(){
this.checkName = function(){}
this.checkEmail = function(){}
}

// 类(原型,绑在 this 上的方法每次实例化都会被复制一份,有时候这么做造成的消耗很奢侈,所以我们可以将方法写在 prototype 上,如下简易写法)

面向对象编程

1. 简易写法

两种方式不能混用,在后面为对象的原型对象复制新对象时,会覆盖之前的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方式一
var CheckObject = function() { };
CheckObject.prototype.a = function() { };
CheckObject.prototype.b = function() { };

// 方式二
var CheckObject = function() { };
CheckObject.prototype = {
a: function() { },
b: function() { }
}

// 调用
var obj = new CheckObject()
obj.a();
obj.b();

2. 链式调用

方法最后返回 this 可以实现链式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1.对象的写法
var obj = {
a: function() { return this },
b: function() { return this }
}
obj.a().b()

// 2.类的写法
var CheckObject = function() { };
CheckObject.prototype = {
a: function() { return this },
b: function() { return this }
}
var obj = new CheckObject()
obj.a().b()

3. 链式添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1.函数形式
Function.prototype.addMethod = function(name, fn) {
this[name] = fn;
return this;
}
var methods = function() { };
methods.addMethod('a', function() { // 链式添加
return this;
}).addMethod('b', function() {
return this;
})
methods.a().b(); // 链式调用

// 2.类的形式
Function.prototype.addMethod = function(name, fn) {
this.prototype[name] = fn;
return this;
}
var Methods = function() { };
Methods.addMethod('a', function() {}).addMethod('b', function() {})
var m = new Methods();
m.a();

4. 类的创建

原型链图解
一个类可能包括:

  • 私有变量和私有方法 — 外界访问不到;
  • 对象共有属性、方法 — 在类创建对象时,对象自身都拥有一份且可以在外部访问到
  • 特权方法 — 不但可以访问这些对象的共有属性和方法,而且还能访问到类(创建时)或对象自身的私有属性和私有方法
  • 构造器 — 在对象创建时通过使用这些特权方法我们可以初始化实例对象的一些属性
  • 类静态共有属性、方法 — 类外面通过点语法添加的属性和方法,新创建的对象中无法访问。
  • 共有属性、方法 — 类通过prototype创建的属性和方法,在类实例的对象中可以通过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
27
28
var Book = function(id, name, price) {
// 私有变量
var num = 1;
// 私有方法
function checkId() {};
// 特权方法
this.getName = function() {};
this.setName = function() {};
this.getPrice = function() {};
this.setPrice = function() {};
// 对象共有属性
this.id = id;
// 对象共有方法
this.copy = function() {};
// 构造器
this.setName(name);
this.setPrice(price);
}
// 类静态共有属性(对象不能访问)
Book.isChinese = true;
// 类静态共有方法(对象不能访问)
Book.resetTime = function() { };
Book.prototype = {
// 共有属性
isChinese: true,
// 共有方法
resetTime: function() { }
}

闭包实现:

  • 此时增加了静态私有变量、方法 —
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
var Book = (function() {
// 静态私有变量
var bookNum = 0;
// 静态私有方法
function checkBook(name) { };

function _book (id, name, price) {
// 私有变量
var num = 1;
// 私有方法
function checkId() {};
// 特权方法
this.getName = function() {};
this.setName = function() {};
this.getPrice = function() {};
this.setPrice = function() {};
// 对象共有属性
this.id = id;
// 对象共有方法
this.copy = function() {};
// 构造器
this.setName(name);
this.setPrice(price);
}
_book.prototype = {
// 共有属性
isChinese: true,
// 共有方法
resetTime: function() { }
}
return _book;
})();

5. 类的安全模式

防止在某些情况下忘记使用new关键字造成错误

1
2
3
4
5
6
7
8
9
10
var Book = function(id, name, price) {
if (this instanceof Book) {
this.id = id;
this.copy = function() { };
} else {
return new Book(id, name, price);
}
}
var book = Book('1', 'JS指南', '30'); // 或者
var book = new Book('1', 'JS指南', '30');

6. 类的继承

每个类有三个部分:

  • 一是构造函数内的 — 供实例化对象复制用的;
  • 二是构造函数外的 — 直接通过点语法添加,供类使用的;
  • 三是类的原型中的 — 实例化对象可以通过其原型链间接访问到,供实例化对象共用的。

继承方式:

继承方式 描述 优点 缺点
类式继承 父类的实例赋值给子类 prototype 父类的共有属性被继承,如果共有属性是引用类型,则一个改变会影响所有
构造函数式继承 SuperClass.call(this) 父类的原型方法和属性不会被继承
组合式继承 结合构造函数式和类式继承 结合了两者的有点,过滤了缺点 父类构造函数执行了两遍
原型式继承 与 Object.create 有点类似 与类式继承一样的缺点
寄生式继承 是对原型式继承继承的二次封装,可以添加新的属性和方法 与类式继承一样的缺点
寄生组合式继承 row 2 col 2 row 2 col 2
1
2
3
4
5
6
7
8
9
10
// 定义父类
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function() { };

// 定义子类
function Child(name, time) {
this.time = time;
}

类式继承

1
Child.prototype = new Parent();

构造函数继承

1
2
3
4
 function Child(name, time) {
Parent.call(this, name);
this.time = time;
}

原型式继承

1
2
3
4
5
6
function inheritObject(obj) {
function F() { };
F.prototype = obj;
return new F();
}
Child.prototype = inheritObject(Parent.prototype)

寄生式继承

1
2
3
4
5
6
function inheritPrototype(chil , par) {
var p = inheritObject(par.prototype); // 复制一份父类的原型副本保存在变量中
p.constructor = chil; // 修正因为重写子类原型导致子类constructor属性被修改
chil.prototype = p; // 设置子类原型
}
inheritPrototype(Child, Parent);

寄生组合式继承

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
// 定义父类
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function() { };

// 定义子类
function Child(name, time) {
// 【构造函数式继承】
Parent.call(this, name);
this.time = time;
}
// 【寄生式继承父类原型】
inheritPrototype(Child, Parent);
Child.prototype.getTime = function() { };

function inheritPrototype(chil , par) {
var p = inheritObject(par.prototype); // 复制一份父类的原型副本保存在变量中
p.constructor = chil; // 修正因为重写子类原型导致子类constructor属性被修改
chil.prototype = p; // 设置子类原型
}

// 【原型式继承】
function inheritObject(obj) {
function F() { };
F.prototype = obj;
return new F();
}

7. 复制继承(多继承)

  • 多继承,即将多个对象的属性复制到目标对象上。不过这里只是浅拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var mix = function(){
var i = 1,
len = arguments.length,
target = arguments[0],
arg;
// 遍历被继承的对象
for(; i<len; i++){
// 缓存当前对象
arg = arguments[i];
// 遍历被继承对象的属性
for(var property in arg){
target[property] = arg[property]
}
}
}

8. 多种调用方式 - 多态

比如一个 add 方法,如果不传参则返回 10;传入一个参数则返回 10+参数;如果传入两个参数则返回两参数之和

1
2
3
4
5
6
7
8
9
10
11
12
function add(){
var arg = arguments,
len = arguments.length;
switch(len){
case 0:
return 10;
case 1:
return 10 + arg[0];
case 2:
return arg[0] + arg[1];
}
}
0%