JavaScript设计模式(3)-设计模式的基础-面向对象JS-继承

一,前言

上一篇介绍面向对象JS的实现原理以及封装特性的实现

JavaScript设计模式(2)-设计模式的基础-面向对象JS-封装

这一篇主要介绍JS模拟面向对象继承特性的实现
JS并没有继承机制,因此也少了很多限制,使JS具有了一定的灵活性
对于继承的方式也有多种实现方法

二,类式继承

最常见的继承方式就是类式继承

// 声明父类
function SuperClass(){
    this.superValue = true;
}
// 为父类原型添加公有方法,可供所有实例调用
SuperClass.prototype.getSuperValue = function(){
    return this.superValue;
}

// 声明子类
function SubClass(){
    this.subValue = false;
}
// 子类继承父类,子类prototype原型为父类实例
SubClass.prototype = new SuperClass();
// 为子类原型添加公有方法,可供所有实例调用
SubClass.prototype.getSubValue = function(){
    return this.subValue;
}

类式继承:

类式继承:将父类实例赋值给子类原型

类式继承的原理:

通过类的原型对象可以为类添加公有方法

创建一个父类实例,会复制一套父类构造函数中的属性与方法,并将实例的__proto__指向父类原型对象
因此新创建的父类实例拥有了父类原型对象上的属性和方法,并且能访问到父类原型上的属性和方法

将这个父类实例赋值给子类原型,
子类原型就能访问到父类原型上的属性,方法
以及从父类的构造函数中复制的属性和方法

这就是类式继承的原理
var instance = new SubClass();
instance.getSuperValue();   // true
instance.getSubValue();     // false

instanceof作用

检测对象是否为某个类的实例(是否继承了某个类)

instanceof原理:

通过判断对象的prototype链来确认对象是否为某个类的实例
console.log(instance instanceof SuperClass);  // true prototype链存在SuperClass
console.log(instance instanceof SubClass);    // true prototype链存在SubClass

console.log(SubClass instanceof SuperClass);  // false 
console.log(SubClass.prototype instanceof SuperClass);  // true

console.log(instance.prototype instanceof Object);//true 所有对象都是Object的实例

类式继承的缺点

1,共享引用
子类通过prototype原型对父类实例化,实现子类继承父类
所有子类实例的prototype原型都指向同一个父类对象,
此时,如果父类中的公有属性为引用类型,就会被所有子类实例所共用
所以,任何一个子类实例修改从父类构造函数中继承来的公有属性就会影响到其他子类实例
// 创建父类
function SuperClass(){
    // 父类公有属性-数组为引用类型
    this.books = ['js', 'html', 'css']
}

// 创建子类
function SubClass(){}
// 子类继承父类
SubClass.prototype = new SuperClass();

//创建2个子类实例
var instance1 = new SubClass();
var instance2 = new SubClass();
console.log(instance1.books);//  ['js', 'html', 'css']
instance2.books.push('js设计模式');
console.log(instance1.books);//  ['js', 'html', 'css', 'js设计模式']
由于2个子类原型指向同一个父类实例,共享了数组引用类型的属性,导致相互影响
2,无法初始化属性
子类依靠prototype原型对父类实例化实现继承
因此创建父类时无法向父类传递参数,也无法对构造函数中的属性进行初始化

三,构造函数继承

针对于类式继承的缺点,灵活的js还有其他的继承方法,如构造函数继承
// 声明父类
function SuperClass(id){
    // 公有属性-数组为引用类型
    this.books = ['js', 'html', 'css'];
    // 公有属性-值类型
    this.id = id;
}
// 父类原型方法
SuperClass.prototype.showBooks = function(){
    console.log(this.books);
}

// 声明子类
function SubClass(id){
    SuperClass.call(this, id);
}

// 创建两个子类实例
var instance1 = new SubClass(1);
var instance2 = new SubClass(2);

// 修改instance1的books数组
instance1.books.push("js设计模式");

// 打印修改后的结果
console.log(instance1.id);      // 1
console.log(instance1.books);   // ['js', 'html', 'css', 'js设计模式']
console.log(instance2.id);      // 2
console.log(instance2.books);   // ['js', 'html', 'css']

instance1.showBooks();          // TypeError 父类原型方法未被子类继承
从运行结果可以看出,对实例1的引用类型属性修改并没有对实例2的属性造成影响
每个实例都拥有属于自己的一份属性,而非公用同一引用

对子类实例调用父类原型方法报错,子类未继承父类原型方法

SuperClass.call(this, id);

这句代码是关键,call方法可以更改函数的作用环境,this指代当前环境
在子类(SubClass)中对父类(SuperClass)调用call方法,让父类在子类环境中执行
父类执行时会给this绑定属性,而this是子类环境,所以子类就继承了父类的公有属性

也正是因此,这种继承方式并没有涉及到prototype原型,
父类的原型方法不会被子类继承,想要被子类继承就必须要放到构造函数中才可以,
但这样创建创建出来的实例都会单独拥有一份属性而不能共用,违背了代码复用原则

构造函数继承的优缺点

优点:
相比于类式继承,构造函数继承克服了实例共享引用类型属性和不能初始化属性的缺点
缺点:
但由于没有使用prototype原型,导致子类不能继承父类原型

四,组合继承

类式继承:通过子类原型对父类实例化(共享引用,无法实例化)
构造函数继承:在子类作用环境中执行父类构造函数(解决了共享引用,无法实例化,但不能继承父类原型)

所以,只要子类能够继承父类原型就没有问题了
// 声明父类
function SuperClass(name){
    // 公有属性-数组为引用类型
    this.books = ['js', 'html', 'css'];
    // 公有属性-值类型
    this.name = name;
}
// 父类原型公有方法
SuperClass.prototype.getName = function(){
    console.log(this.name);
}

// 声明子类
function SubClass(name, createTime){
    // 构造函数式继承
    SuperClass.call(this, name);
    // 子类新增公有属性
    this.createTime = createTime;
}
//类式继承-子类原型继承父类实例
SubClass.prototype = new SuperClass();
// 子类原型新增公有方法
SubClass.prototype.getCreateTime(){
    console.log(this.time);
}

// 实例化两个子类对象并对其进行操作
var instance1 = new SubClass("js设计模式", 2018);
instance1.books.push("js设计模式");
console.log(this.books);    // ['js', 'html', 'css', 'js设计模式']
instance1.getName();        // js设计模式
instance1.getCreateTime();  // 2018

var instance2 = new SubClass("javascrpit", 2017);
console.log(instance2.books);   // ['js', 'html', 'css']
instance2.getName();            // javascrpit
instance2.getCreateTime();      // 2017
可以看到,对子类实例1的数组(引用类型属性)进行了修改,并没有影响子类实例2
子类实例1和实例2均可以调用子类原型方法getCreateTime和父类原型方法getName获得自身实例的值
所以这种组合方式,既克服了公有属性指向同一引用的问题,也解决了不能初始化属性的问题
同时子类还可以继承父类原型方法

在子类构造函数中执行父类构造函数
在子类原型上实例化父类,
这就是组合继承,
包含了类式继承和构造函数继承的优点,克服了他们的不足

组合继承的缺点

尽管组合继承的方式客服了之前类式继承和构造函数继承的不足,但仍不完美

因为在使用构造函数继承是执行了一遍父类构造函数:
SuperClass.call(this, name);

而为了实现子类继承父类原型所使用的类式继承中,又调用了一遍父类的构造函数:
SubClass.prototype = new SuperClass();

因此父类的构造函数被调用了两次,造成了资源的浪费

猜你喜欢

转载自blog.csdn.net/abap_brave/article/details/81118682