Javascript高级编程学习笔记(22)—— 对象继承

继承是所有面向对象的语言最让人津津乐道的概念

许多面向对象的语言都支持两种实现继承的方式:

1、接口继承

2、实现继承

由于ECMAScript中没有函数签名,所以自然也是不支持接口继承

所以JS中能实现的也只能是实现继承,而实现继承主要是依靠原型链

至于原型链的构成在昨天的文章中也大概讲了一下

无非就是每个对象的实例都有一个[[Prototype]]的属性(在游览器中以__proto__来显式地支持此属性)指向了其构造函数的原型对象

每个对象都有一个这样的引用,就构成了原型链

原型链的顶端是Object.prototype

而实现继承很大程度上都需要依靠,原型对象,除了少数情况之外

原型链的基本实现

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
};

// 创建一个新的构造函数
function SubType(){
    this.subprototype = false;
}

// 重写这个构造函数的原型对象让其指向SuperType的实例
SubType.prototype = new SuperType();

// 添加这个新构造函数的自身的方法
SubtType.prototype.getSubValue = funtion(){
    return this.subproperty;
}

var instance = new SubType();
alert(instance.getSuperValue()); // true 可以访问到原型对象上的方法和属性

关于上述原型链的实现其实并不完整

因为昨天我们已经说过,重写原型对象的方法会存在一些问题

上方代码中 SubType.prototype.constructor 并不会正确地指向 SubType 这一构造函数

而是会指向 SuperType, 因为 SuperType 的实例并没有 constructor 属性,所以JS会访问SuperType实例所指的原型对象的 constructor 属性也就是 SuperType这一构造函数

PS. constructor属性会在构造函数创建原型对象时,让这个对象指向构造函数,但当我们重写构造函数的prototype属性时(也就是让prototype指向另一个对象),JS并不会自动帮我们完成这一过程

确定原型和实例的关系

一般来说有两种方法

1、instanceof

2、isPrototypeOf()

两种方式从原理上来说并没有什么太大的区别

因为只要是在实例的原型链上出现过的原型对象都会被判为 true

所以当我们需要具体的判断某个实例是否是由某个特定的构造函数构造的时候,就不能使用上述的方法

但我们可以借助原型链的特点来判断

实例对象.__proto__.constructor === 要判断的构造函数

原型链继承

在这么长的铺垫下终于进入主题了

第一种实现继承的方式,在刚在的展示中基本上已经展示了

还是在给个完整的原型链继承的代码吧

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
};

// 创建一个新的构造函数
function SubType(){
    this.subprototype = false;
}

// 重写这个构造函数的原型对象让其指向SuperType的实例
SubType.prototype = new SuperType();
// 修改 constructor 让其指向正确地构造函数
SubType.prototype.constructor = SubType;

// 添加这个新构造函数的自身的方法
SubtType.prototype.getSubValue = funtion(){
    return this.subproperty;
}

var instance = new SubType();
alert(instance.getSuperValue()); // true 可以访问到原型对象上的方法和属性

PS. 在实现继承的过程中有几个地方需要注意

1. 给原型对象添加方法的语句一定要在替换原型之后

2.一旦替换了原型就要小心不要在定义原型上的新方法时错误地替换了原型

这种继承方式存在以下问题

1、对于引用类型的值会被所有实例所共享

2、没有办法在不影响所有对象实例的情况下,向父类的构造函数传递参数

所以在实践当中我们很少对单独使用这种方式来实现继承

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

这种继承方式的核心思想就是在子类的构造函数中调用父类的构造函数

这样就可以获得父类的所有属性

function SuperType(){
    this.color = 'color';
}

function SubType (){
    SuperType.applay(this,arguments);
    this.name = 'lhy';
}

通过改变this指向来让父类构造函数中创建的属性创建到子类构建的对象上

和构造函数模式样

这样的方法没法解决函数复用的问题,也没法继承到父类的原型上的属性

但是这种方法可以在创建子类时给父类的构造函数传递参数并且不会影响其实例对象

虽然这种方法有独特的优势,但是它的问题也不少

所以我们在实践中也很少单独使用这种继承方式

组合继承

有小伙伴可能发现之前的两种方式在某种程度上是互补的

所以第三种继承的实现方式当然就是由前面两种方式组合而来的组合继承

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
};

// 创建一个新的构造函数
function SubType(){
    // 调用父类的构造函数
    SuperType.applay(this,arguments);
    this.subprototype = false;
}

// 重写这个构造函数的原型对象让其指向SuperType的实例
SubType.prototype = new SuperType();
// 修改 constructor 让其指向正确地构造函数
SubType.prototype.constructor = SubType;

// 添加这个新构造函数的自身的方法
SubtType.prototype.getSubValue = funtion(){
    return this.subproperty;
}

var instance = new SubType();
alert(instance.getSuperValue()); // true 可以访问到原型对象上的方法和属性

这种组合方式也是在实践中我们最常用的方法(ES5)

融合了前两种方式优点,除此而外 instanceof 和 isPrototypeOf() 也能正确识别

原型式继承

有些时候我们要实现继承,很可能只是希望新的这个类型在已有类型的基础上多出一些属性

没有必要大费周章地创建新的原型函数,那么原型式继承给了我们新的选择

function  object(o){// o是参照的对象
    function F(){};
    F.prototype = o;
    return new F();
}

这个简短的函数就是原型式继承的核心思想

虽然都是将新对象以旧对象为原型创建,但是和原型链继承的方式的区别也很显著

那就是这种继承方式不需要构造函数,这也是它存在的意义

为了规范这种继承,ES5规范了Object.create() 方法

传入两个参数,1、原型对象 2、为新对象定义额外属性的对象(可选)

所以我们可以直接使用create 方法

但是这种方式存在的问题也很明显,那就是引用类型的值在实例中会共享(在以同一个对象为参照对象的情况下)

寄生式继承

这种继承方式是原型式继承的进阶版

其思路与寄生构造函数类似

function  object(o){// o是参照的对象
    function F(){};
    F.prototype = o;
    return new F();
}

function createAnother(o){
    var clone = object(o);
    clone.say = function(){
        alert('lhy');
    }
    return clone;
}

这种方法相当于,在原型式继承的基础上把给新对象添加新属性的工作,以工厂模式的思路实现了

当然,这种方式的缺点就是,函数没法复用(构造函数模式的通病)

寄生组合继承

看到这里,大家可能以为组合式继承,就是最好的选择

其实细心的小伙伴可能已经发现了,在我们创建子类实例的时候父类的构造函数调用了两次

我们调用两次的原因无非就是需要一个父类的原型副本而已

所以寄生组合继承的思路就是通过原形式继承来获取父类原型的副本,从而避免调用两次父类的构造函数

function  object(o){// o是参照的对象
    function F(){};
    F.prototype = o;
    return new F();
}

// 定义继承的方法
function inheritPrototype(SubType,SuperType){
    var prototype = object(SuperType);
    prototype.constructor = SubType;
    SubType.prototype = prototype;
}

function SuperType(name){
    this.name = name;
    this.color = ['blue','red','green'];
}

function SubType(name,age){
    SuperType.call(this,name);// 调用父类的构造函数
    this.age = age;
}

inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
    alert(this.age);
}

这里为了大家更好理解,所以没有用 Object.create()

在实践中大家最好用 Object.create() 代替上面我定义的 object 函数

以上就是JS对象继承的主要内容了

原文地址:https://www.cnblogs.com/lhyxq/p/10172561.html