JS设计模式---4.继承

场景: 我们需要目前有一个超类 Person,现在需要一个 Author 类来继承超类的所有方法及属性,并且拥有自己的方法和属性

类式继承

1
2
3
4
5
6
7
// Person 超类
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
}
  • 原型链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//  继承超类的类 Author
function Author(name, books) {
// 在使用new运算符时,系统会先创建一个空对象,然后调用构造函数,此过程中空对象处于作用域链最前端
// 这里我们调用超类的构造函数,就需要手懂模拟这个过程。此时this代表空对象,name为参数
Person.call(this, name);
this.books = books;
}
Author.prototype = new Person(); // 使Author的原型指向Person的实例 此时原型的构造函数(constructor)被重置
Author.prototype.constructor = Author; // 重定向Author原型的构造函数 不定义的话此构造函数为空,那么将会向上查找,指向Person
Author.prototype.getBooks = function () {
return this.books;
}
var author = [];
author[0] = new Author('Dustin Diaz', ['JavaScript Design Patterns']);
author[1] = new Author('Ross Harmes', ['JavaScript Design Patterns']);
console.log(author[1].getName()) // Ross Harmes
console.log(author[1].getBooks()) // ['JavaScript Design Patterns']
  • extend 函数
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
// extend函数
function extend(subClass, superClass) {
var F = function () {}; // 先创造一个空对象
F.prototype = superClass.prototype; // 使空对象的原型指向超类的原型
subClass.prototype = new F(); // 使当前类的原型指向F的实例
subClass.prototype.constructor = subClass; // 重定向当前类的原型的构造函数

// superclass 用于直接访问超类的方法
// 使用场景 在既想重定义超类的方法而又想访问其在超类中的实现时 栗子在下面
subClass.superclass = superClass.prototype;
// 判断构造器指向
if (superClass.prototype.constructor == Object.prototype.constructor) {
superClass.prototype.constructor = superClass
}
}
// Author 类
function Author(name, books) {
Author.superclass.constructor.call(this, name)
this.books = books
}
extend(Author, Person)
var xhui = new Author('xhui', ['123'])
console.log(xhui.getName()) //xhui
// 利于supercalss来重定义超类的getName方法
Author.prototype.getName = function () {
var name = Author.superclass.getName.call(this)
return `${name}123123`
}
console.log(xhui.getName()) //xhui123123

原型式继承

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
// clone 函数
function clone(object) {
function F(params) {};
F.prototype = object;
return new F();
}
// Person 超类
var Person = {
name: 'default name',
getName: function () {
return this.name
}
}
var Author = clone(Person);
Author.books = [];
Author.getBooks = function () {
return this.books;
}
var author;
author = clone(Author);
console.log(author.getName()) //default name
author.name = 'xhui';
author.books = ['js设计模式']
console.log(author.getName()) // 'xhui'
console.log(author.getBooks()) // ['js设计模式']

继承而来的成员的读和写具有不对等性。 在类式继承中,Author 的每一份实例都有自己的 books 数据副本。但是在原型式继承中大不相同, 一个克隆并非其原型对象的一份独完全立的副本,它只是一个以那个对象为原型对象的空对象。
克隆刚被创建时,author.name 其实是一个反指最初的 Person.name 的链接。对于从原型对象继承而来的成员,其读和写具有内在的不对等性。在你读取 author.name 时,如果你没有为其赋值,那么得到的是其原型对象的同名属性值。而你在为 author.name 赋值时,其实是在为 author 定义一个新属性。

掺元类

我们平时总会定义一个包含各种通用方法的类,然后用它来扩充其他类。这种包含各种通用方法的类就是掺元类。

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
// argument  辅助函数
function argument(receivingClass, givingClass) {
if (arguments[2]) {
// 如果有第三个参数 则为扩充类扩充名为第三个参数的方法
for (var i = 2; i < arguments.length; i++) {
receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]

}
} else {
// 如果只有两个参数 为扩充类扩充掺元类所有的方法
for (methodName in givingClass.prototype) {
if (!receivingClass.prototype[methodName]) {
receivingClass.prototype[methodName] = givingClass.prototype[methodName]
}
}
}
}
// 掺元类
var Mixin = function () {};
Mixin.prototype = {
serialize: function () {
var output = [];
for (key in this) {
output.push(`key:${this[key]}`)
}
return output
}
}

function Author(name, books) {
this.name = name;
this.books = books;
}
argument(Author, Mixin) // argument(Author, Mixin, 'serialize')
var author = new Author('xhui', ['js设计模式']);
console.log(author.serialize())

结果为
1544515452.jpg

举个栗子

书上栗子有点长懒得写了。。。