实现继承的几种方式

ECMAScript 中只支持实现继承,而且主要依靠的是原型链来实现。

扩展原型对象实现继承

  • 描述:直接在构造函数的prototype属性上添加方法
  • 解决的问题:解决了直接将方法设置在构造函数上时,实例化每个对象这些方法都会开辟新空间,造成内存严重浪费的问题
  • 缺点:如果将所有的方法都直接设置到原型对象上,代码冗余
1
2
3
4
5
6
7
8
9
10
11
/* 一、 扩展原型对象实现继承 */
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.personFn = function () {
console.log("personFn is run...");
};
var p1 = new Person("z3", 13);
console.log("============== 一、 扩展原型对象实现继承 ==================");
p1.personFn();

替换原型对象实现继承

  • 描述:将构造函数的原型对象用新对象替换,再往新的对象中添加新方法
  • 解决的问题:扩展原型对象使得代码冗余
  • 缺点:所有的方法和属性都被实例共享
  • 注意:还原构造器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 二、 替换原型对象实现继承 */
function Animal(color, type) {
this.color = color;
this.type = type;
}
Animal.prototype = {
constructor: Animal,
animalFn: function () {
console.log("animalFn is run...");
}
};
var animal = new Animal("white", "cat");
console.log("======================== 二、 替换原型对象实现继承 =========================");
animal.animalFn();

另一种原型继承(动态原型模式) 特点:让代码封装到一起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 二、 另一种原型继承(动态原型模式) 特点:让代码封装到一起 */
function Paper(color, size){
this.color = color;
this.size = size;
// 动态原型方法
if(typeof this.write !== "function"){
Paper.prototype.write = function () {
console.log("color: " + this.color + ", size: " + this.size);
}
}
}
var paper = new Paper("orange", "16k");
console.log("================= 二、另一种原型继承(动态原型模式)====================");
paper.write();

混入继承: 已知两个对象,一个对象继承另一个对象的功能,for..in

  • 描述: jQuery的extend()方法原理是混入继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 三、 混入继承: 已知两个对象,一个对象继承另一个对象的功能,for..in */
function mixin(target, source) {
for (var key in source) {
target[key] = source[key];
}
return target;
}
var obj1 = {
name: "Amiy",
age: 18,
pray: function () {
console.log("name: " + this.name + ", age: " + this.age + ", sex: " + this.sex + "; obj2.pray is run...");
}
};
var obj2 = {
sex: false
};
mixin(obj2, obj1);
console.log("=================== 三、 混入继承: 已知两个对象,一个对象继承另一个对象的功能,for..in ===================");
obj2.pray();

原型+混入继承:混入继承的应用

  • 描述:在一个对象的原型对象上扩展另一个对象的属性和方法
  • jQuery.fn.extend() 利用的原理是原型+混入继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 四、 原型+混入继承:混入继承的应用 */
function Cat() {
}
var cat = new Cat();
var o2 = {
o2Attr1: "o2Attr1",
o2Attr2: "o2Attr2",
o2Method1: function () {
console.log("o2Method1 is run...");
}
};
mixin(Cat.prototype, o2);
console.log("======== 四、 原型+混入继承:混入继承的应用 =========");
cat.o2Method1();

经典继承:已知一个对象knownObj,需要创建一个新对象,这个新对象继承自已知的对象knownObj

  • 注意: ES5 Object.create()方法的实现原理就是经典继承
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
/* 五、 经典继承:已知一个对象knownObj,需要创建一个新对象,这个新对象继承自已知的对象knownObj */
function myCreate(knownObj) {
function F() {
}
F.prototype = knownObj;
return new F();
}
var knownObj = {
knownObjAttr1: "knownObjAttr1",
knownObjMethod1: function () {
console.log("knownObjMethod1 is run...");
}
};
var o = myCreate(knownObj);
console.log("=============== 五、 经典继承:已知一个对象knownObj,需要创建一个新对象,这个新对象继承自已知的对象knownObj ==================");
o.knownObjMethod1();
/* 在旧浏览器下实现继承 */
if (typeof Object.create !== "function") {
Object.create = function (obj) {
function F() {
}
F.prototype = obj;
return new F();
};
}

借用构造函数实现继承

  • 特点: 不会继承原型对象上的方法,因为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
/* 六: 借用构造函数实现继承 */
function Computer(color, type) {
this.color = color;
this.type = type;
this.run = function () {
console.log("run method is run... ");
}
}
Computer.prototype.start = function () {
console.log("Computer.prototype.start is run...");
};
/**
* 下面的借用构造函数实现继承就是对这段代码的优化
* function LenovoNoteBook(color, type, interfaceCount) {
* this.color = color;
* this.type = type;
* this.interfaceCount = interfaceCount;
* }
*/
function LenovoNoteBook(color, type, interfaceCount) {
Computer.call(this, color, type);
this.interfaceCount = interfaceCount;
}
var lenovo = new LenovoNoteBook("write", "Y470", 8);
console.log("========= 六: 借用构造函数实现继承 ========");
console.log("color: " + lenovo.color + ", type: " + lenovo.type + ", interfaceCount: " + lenovo.interfaceCount);
lenovo.run();
// lenovo.start();

混合继承(经典继承+借用构造函数)

  • 特点:解决借用构造函数继承不能够继承原型对象上的方法的问题
  • 缺点:继承了两次构造函数(模板)
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 BaseCar(type, modelNumber, color) {
this.type = type;
this.modelNumber = modelNumber;
this.color = color;
}
// 原型对象上的方法
BaseCar.prototype = {
constructor: BaseCar,
canSell: function (price) {
var leastPrice = 2500000;
if (leastPrice <= price) {
return "恭喜恭喜,您可以卖车";
} else {
return "您还差:" + (leastPrice - price) + "元就可以买车了";
}
}
};
/**
* Car 构造函数,调用call()实现继承BaseCar中的方法
* @param type
* @param modelNumber
* @param color
* @constructor
*/
function Car(type, modelNumber, color) {
BaseCar.call(this, type, modelNumber);
this.color = color;
this.bmwStart = function () {
console.log("Bmv car is starting...");
}
}
// 经典继承
Car.prototype = new BaseCar();
var bmwCar = new Car("Bmw", "B34212", "write");
console.log("=================== 七、 混合继承(经典继承+借用构造函数) 白贺翔视频 =====================");
console.log(bmwCar.canSell(2400000));

解决混合继承的缺点

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
/* 八、 解决混合继承的缺点 */
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.sayHello = function () {
console.log("父的构造函数上的静态sayHello方法 运行");
};
}
People.prototype = {
constructor: People,
sayHello: function () {
console.log("父的原型对象中的sayHello方法:name: " + this.name + ", age: " + this.age + ", sex: " + this.sex + ", HelloWorld !!!");
}
};
function Boy(name, age, sex, studyNumber) {
// 子类中保存了父类的原型对象
// 借用构造函数实现继承
Boy.superClass.constructor.call(this, name, age, sex);
this.studyNumber = studyNumber;
}
myExtend(Boy, People);
function myExtend(subConstructor, supConstructor) {
// 1. 用一个空函数进行中转
var F = new Function();
// 2. 保存父的原型对象
F.prototype = supConstructor.prototype;
// 3. 实现经典继承
subConstructor.prototype = new F();
// 4. 还原子构造函数原型对象的构造器
subConstructor.prototype.constructor = subConstructor;
// 5. 保存父的原型对象,一方面方便解耦,另一方面可以轻松获得原型对象(添加静态方法)
subConstructor.superClass = supConstructor.prototype;
// 6. 判断父类型的原型对象构造器,加保险
if (supConstructor.prototype.constructor !== supConstructor) {
supConstructor.prototype.constructor = supConstructor;
}
}
// 7. 利用保存的父类原型对象实现父类子类有重载的方法
Boy.prototype = {
constructor: Boy,
sayHello: function () {
console.log("子的原型对象中的sayHello方法");
}
};
// 注意: 子类的原型对象上添加方法必须在实现继承之后
var boy = new Boy("Ping", 22, true, 345234);
console.log("================ 八、 解决混合继承的缺点,封装函数 ExtJs 底层 白贺翔 ===================");
boy.sayHello(); // 父的构造函数上的静态sayHello方法 运行
Boy.prototype.sayHello(); // 子的原型对象中的sayHello方法
Boy.superClass.sayHello.call(boy); // 父的原型对象中的sayHello方法:name: Ping, age: 22, sex: true, HelloWorld !!!
感谢您的支持!