一、子类的原型对象——类式继承
把父类实例直接赋值给 prototype 属性,代码如下:
1 // 声明父类
2 function SuperClass() {
3 this.superValue = true;
4 }
5 // 为父类添加共有方法
6 SuperClass.prototype.getSuperValue = function() {
7 return this.superValue
8 }
9 // 声明子类
10 function SubClass() {
11 this.subValue = false;
12 }
13
14 // 继承父类
15 SubClass.prototype = new SuperClass();
16 // 为子类添加共有方法
17 SubClass.prototype.getSubValue = function() {
18 return this.subValue;
19 }
类的原型对象的作用是为类的原型添加共有方法,但类不能直接访问这些属性和方法,必须通过原型prototype来访问。
实例化父类对象时,新创建的对象浅复制了父类的原型对象上的属性与方法,并且把原型_proto_指向父类的原型对象。通过原型链式访问到父类原型对象上的方法与属性。
1 var instance = new SubClass();
2 console.log(instance.getSuperValue()); // true
3 console.log(instance.getSubValue()); // false
instanceof 可以通过判断对象的prototype链来检测某个对象是否是某个类的实例。
1 console.log(instance instanceof SuperClass); // true
2 console.log(instance instanceof SubClass); // true
3 console.log(SubClass instanceof SuperClass); // false
因为实现subClass继承superClass时是通过将superClass的实例赋值给subClass的原型prototype,所以说SubClass.prototype继承了superClass。
1 console.log(SubClass.prototype instanceof SuperClass) // true
这种继承方法有两个缺点:
其一:由于子类通过其原型prototype对父类进行实例化,继承了父类。则父类中的共有属性要是引用类型,就会在子类中被所有实例共用,所以一个子类的实例更改了子类原型从父类构造函数中继承来的共有属性,就会影响到其他子类。比如:
1 // 声明父类
2 function SuperClass() {
3 this.books = ['css','html','javascript'];
4 this.number = 3;
5 }
6 // 声明子类
7 function SubClass() {
8 this.subValue = false;
9 }
10
11 // 继承父类
12 SubClass.prototype = new SuperClass();
13
14 var instance = new SubClass();
15 var instance2 = new SubClass();
16 console.log(instance.books,instance.number); // ["css", "html", "javascript"] 3
17 instance2.books.push('设计模式');
18 instance2.number = 4;
19 console.log(instance2.books, instance2.number) // ["css", "html", "javascript", "设计模式"] 4
20 console.log(instance.books,instance.number) // ["css", "html", "javascript", "设计模式"] 3
其二:由于子类实现的继承是靠其原型prototype对父类的实例化实现的,因此在创建父类的时候,是无法向父类传递参数的,因而在实例化父类的时候也无法对父类构造函数内的属性进行初始化。
二、构造函数继承
1 // 构造函数继承
2 // 声明父类
3 function SuperClass(id) {
4 // 引用类型共有属性
5 this.books = ['JavaScript', 'html', 'css'];
6 // 值类型共有属性
7 this.id = id;
8 }
9
10 // 父类声明原型方法
11 SuperClass.propotype.showBooks = function() {
12 console.log(this.books);
13 }
14
15 // 声明子类
16 function SubClass(id) {
17 // 继承父类
18 SuperClass.call(this, id);
19 }
20
21 // 创建第一个子类的实例
22 var instance1 = new SubClass(10);
23 // 创建第二个子类的实例
24 var instance2 = new SubClass(11);
25
26 instance1.books.push('设计模式');
27 console.log(instance1.books); // ['JavaScript', 'html', 'css', '设计模式']
28 console.log(instance1.id); // 10
29 console.log(instance2.books); // ['JavaScript', 'html', 'css']
30 console.log(instance2.id); // 11
31
32 instance1.showBooks(); // TypeError
解释:
(1)SuperClass.call(this,id)是构造函数式继承的精华,由于call可以更改函数的作用环境,在子类中对SuperClass调用这个方法就是将子类中的变量在父类中执行一遍。
(2)因为父类中是给this绑定属性的,所以子类自然也就继承了父类的公有属性。
(3)由于这种类型的继承没有涉及原型prototype,所以父类的原型方法自然不会被子类继承,如果想被子类继承就必须放在构造函数中,这样创造出来的每个实例都会单独拥有一份而不能共用,这样就违背了代码复用的原则。
三、组合继承
之前两种模式的特点:
(1)类式继承是通过子类的原型prototype对父类实例化来实现的
(2)构造函数式继承是通过在子类的构造函数作用环境中执行一次父类的构造函数来实现的。
1 // 组合继承
2 // 声明父类
3 function SuperClass(name) {
4 // 值类型共有属性
5 this.name = name;
6 this.books = ['html', 'css', 'JavaScript'];
7 }
8 // 父类原型共有方法
9 SuperClass.prototype.getName = function() {
10 console.log(this.name);
11 }
12 // 声明子类
13 function SubClass(name, time) {
14 // 构造函数式继承父类name属性
15 SuperClass.call(this, name);
16 // 子类中新增共有属性
17 this.time = time;
18 }
19 // 类式继承 子类原型继承父类
20 SubClass.prototype = new SuperClass();
21 // 子类原型方法
22 SubClass.prototype.getTime = function() {
23 console.log(this.time);
24 }
25
26 var instance1 = new SubClass('js book', 2015);
27 instance1.books.push('设计模式')
28 console.log(instance1.books); // ['html', 'css', 'JavaScript', '设计模式']
29 instance1.getName(); // js book
30 instance1.getTime(); // 2015
31
32 var instance2 = new SubClass('css book', 2016);
33 console.log(instance2.books); // ['html', 'css', 'JavaScript']
34 instance2.getName(); // css book
35 instance2.getTime(); // 2016
这种继承方式的缺点:父类构造函数调用了两次,一次是在使用造函数继承时执行了一遍父类的构造函数,而在实现子类原型的类式继承时又调用了一遍父类构造函数。
四、原型式继承
借助原型prototype可以根据已有的对象创建新的对象,同时不必创建新的自定义对象类型。
1 // 原型式继承
2 function inheritObject(o) {
3 // 声明一个过渡函数对象
4 function F() {}
5 // 过渡对象的原型继承父对象
6 F.prototype = o;
7 // 返回过渡对象的一个实例,该实例的原型继承了父对象
8 return new F();
9 }
10
11 var book = {
12 name: 'js book',
13 alikeBook: ["css book", "html book"];
14 };
15 var newBook = inheritObject(book);
16 newBook.name = 'ajax book';
17 newBook.alikeBook.push("xml book");
18
19 var otherBook = inheritObject(book);
20 otherBook.name = "flash book";
21 otherBook.alikeBook.push("as book");
22
23 console.log(newBook.name); // ajax book
24 console.log(newBook.alikeBook); // ["css book", "html book", "xml book", "as book"]
25 console.log(otherBook.name); // flash book
26 console.log(otherBook.alikeBook); // ["css book", "html book", "xml book", "as book"]
27 console.log(book.name); // js book
28 console.log(book.alikeBook); // ["css book", "html book", "xml book", "as book"]
解释:
(1)其实这是对类式继承的一个封装,其中的过渡对象就相当于类式继承中的子类,只不过在原型式中作为一个过渡对象出现,目的是为了创建要返回的新的实例化对象。
(2)类式继承出现的问题,原型式继承也会出现,引用类型会被共用。
(3)F过渡类的构造函数中无内容,所以开销比较小。
五、寄生式继承
寄生式继承就是对原型继承的第二次封装,并且在这第二次封装过程中对继承的对象进行了拓展,这样新创建的对象不仅仅有父类中的属性和方法,而且还添加新的属性和方法。
1 // 寄生式继承
2 // 声明基对象
3 var book = {
4 name: 'js book',
5 alikeBook: ["css book", "html book"]
6 };
7 function createBook(obj) {
8 // 通过原型继承方式创建新对象
9 var o = new inheritObject(obj);
10 // 拓展新对象
11 o.getName = function() {
12 console.log(this.name);
13 };
14 // 返回拓展后的新对象
15 return o;
16 }
17 var newBook = createBook(book);
18
19 console.log(newBook.name); // js book
20 newBook.getName(); // js book
六、寄生组合式继承
1 /**
2 * 寄生式继承 继承原型
3 * 参数 subClass 子类
4 * 参数 superClass 父类
5 */
6 function inheritPrototype(subClass, superClass) {
7 // 复制一份父类的原型副本保存在变量中
8 var p = inheritObject(superClass.prototype);
9 // 修正因为重写子类原型导致子类的constructor属性被修改
10 p.constructor = subClass;
11 // 设置子类原型
12 subClass.prototype = p;
13 }
14 // 定义父类
15 function SuperClass(name) {
16 this.name = name;
17 this.colors = ["red", "blue", "green"];
18 }
19 // 定义父类原型方法
20 SuperClass.prototype.getName = function() {
21 console.log(this.name);
22 };
23 // 定义子类
24 function SubClass(name , time) {
25 // 构造函数式继承
26 SuperClass.call(this, name);
27 this.time = time;
28 }
29 // 寄生式继承父类原型
30 inheritPrototype(SubClass, SuperClass);
31 // 子类新增原型方法
32 SubClass.prototype.getTime = function() {
33 console.log(this.time);
34 }
35 // 创建两个测试方法
36 var instance1 = new SubClass('js book', 2014);
37 var instance2 = new SubClass('css book', 2013);
38 instance1.colors.push('black');
39 console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
40 console.log(instance2.colors); // ['red', 'blue', 'green']
41 instance2.getName(); // css book
42 instance2.getTime(); // 2013
解释:
(1)在构造函数继承中我们已经调用了父类的构造函数,因此我们需要的就是父类的原型对象的一个副本。而这个副本我们通过原型继承便可得到,但是这么直接赋值给子类会有问题的,因为对父类原型对象得到的复制对象p中constructor属性不是subClass子类对象,因此在寄生式继承中要对复制的对象p做一次增强,修复其constructor属性指向不正确的问题,最后将得到的复制对象p赋值给子类的原型,这样子类的原型就继承了父类的原型并且没有执行父类的构造函数。
参考资料:《JavaScript设计模式》