资讯专栏INFORMATION COLUMN

JS学习笔记(第6章)(面向对象之继承——JS继承的六大方式)

lscho / 2020人阅读

摘要:除此之外,在超类型的原型中定义的方法,对子类型而言也是不可兼得,结果所有类型都只能用构造函数模式。创建对象增强对象指定对象继承属性这个例子的高效率体现在它只调用了一次构造函数。

1、原型链

原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
构造函数、原型和实例的关系:每个构造函数都有一个原型对象;原型对象都包含着一个指向构造函数的指针;实例都包含一个指向原型对象的内部指针。如果我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地另一个原型中也包含着指向另一个构造函数的指针……层层递进,就构成了实例与原型的链条,这就是所谓原型链的基本概念。

实现原型链有一种基本模式,其代码大致如下:

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

function SubType() {
    this.subproperty = false;
}

//继承了SuperType
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
    return this.subproperty;
};

var instance = new SubType();
alert(instance.getSuperValue());   //true
(1)不要忘记默认的原型

(2)确定原型和实例的关系

可以通过两种方式来确定原型和实例之间的关系。
1)使用instanceof操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true。

alert(instance instanceof Object);  //true
alert(instance instanceof SuperType);  //true
alert(instance instanceof SubType);  //true

2)使用isPrototypeOf()方法。只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此isPrototypeOf()方法也会返回true。

alert(Object.prototype.isPrototypeOf(instance));  //true
alert(SuperType.prototype.isPrototypeOf(instance));  //true
alert(SubType.prototype.isPrototypeOf(instance));  //true
(3)谨慎地定义方法

子类型有时候需要覆盖超类型中的某个方法或者需要添加超类型中不存在的某个方法。但不管这样,给原型添加方法的代码一定要放在替换原型的语句之后

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

function SubType() {
    this.subproperty = false;
}

//继承了SuperType
SubType.prototype = new SuperType();

//添加新方法
SubType.prototype.getSubValue = function() {
    return this.subproperty;
};
//重写超类型中的方法
SubType.prototype.getSuperValue = function() {
    return false;
};
var instance = new SubType();
alert(instance.getSuperValue());  //false

在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链。会导致实例与原型链之间的联系被切断。

function SubType() {
    this.subproperty = false;
}

//继承了SuperType
SubType.prototype = new SuperType();

//使用字面量添加新方法,会导致上一行代码无效
SubType.prototype = {
    getSubValue : function() {
        return this.subproperty;
    },
    sonOtherMethod : function() {
        return false;
    }
};
var instance = new SubType();
alert(instance.getSuperValue());  //error!
(4)原型链的问题

1)最主要的问题来自包含引用类型值的原型,包含引用类型值的原型属性会被所有实例共享;
2)第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数;

function SuperType() {
    this.colors = ["red", "blue", "green"];
}

function SubType(){

}
//继承了SuperType
SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);   //"red,blue,green,black"

var instance2 = new SuperType();
alert(instance2.colors);   //"red,blue,green,black"

如上所示,我们对instance1.colors的修改能够通过instance2.colors反映出来。

2、借用构造函数

思想:在子类型构造函数的内部调用超类型构造函数。
函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法也可以在新创建的对象上执行构造函数。

function SuperType() {
    this.colors = ["red", "blue", "green"];
}

function SubType(){
    //继承了SuperType,
    SuperType.call(this);//在子类型构造函数的内部调用超类型构造函数
}

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);   //"red,blue,green,black"

var instance2 = new SuperType();
alert(instance2.colors);   //"red,blue,green"

通过使用call()方法或apply()方法,我们实际上是在新创建的SubType实例的环境下调用了SuperType构造函数。这样一来,就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果SubType的每个实例就会具有自己的colors属性的副本了。

(1)传递参数

相对于原型链而言,借用构造函数的一个很大优势在于:可以在子类型构造函数中向超类型构造函数传递参数。

function SuperType(name) {
    this.name = name;
}

function SubType() {
    //继承了SuperType,同时还传递了参数
    SuperType.call(this, "Nicholas");
    //实例属性
    this.age = 29;
}
var instance = new SubType();
alert(instance.name);  //"Nicholas"
alert(intance.age);  //29

为了确保SuperType构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中定义的属性。

(2)借用构造函数的问题

如果仅仅是借用构造函数,那么也就无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起。除此之外,在超类型的原型中定义的方法,对子类型而言也是不可兼得,结果所有类型都只能用构造函数模式。

3、组合继承

将原型链和借用构造函数的技术组合到一起。
思想:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。

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

SuperType.prototype.sayName = function() {
    alert(this.name);
};
function SubType(name ,age) {
    //继承属性
    SuperType.call(this,name);
    this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;  //指向构造函数
SubType.prototype.sayAge = function() {
    alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);   //"red,blue,green,black"
instance1.sayName();  //"Nicholas"
instance1.sayAge();  //29

var instance2 = new SubType("Greg" ,27);
alert(instance2.colors);  //"red,blue,green"
instance2.sayName();  //"Greg"
instance2.sayAge();  //27

instanceof()和isPrototypeOf()也能够用于识别基于组合继承创建的对象、

4、原型式继承

借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型

function object(o) {
    function F() {}
    F.prototype =o;
    return new F();
}

在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。

var person = {
    name : "Nicholas",
    friends : ["Shelby", "Court", "Van"]
};
var anotherPerson = object(perosn);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAbotherPerson = object(perosn);
yetAbotherPerson.name = "Linda";
yetAbotherPerson.firends.push("Barbie");

alert(person.friends);  //"Shelby, Court, Van, Greg, Linda"

ECMAScript5通过新增Object.creat()方法规范了原型式继承。这个方法接收两个参数:用作新对象原型的对象和为新对象定义额外属性的对象(可选)。在传入一个参数的情况下,Object.create()与object()方法的行为相同。

var person = {
    name : "Nicholas",
    friends : ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(perosn);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAbotherPerson = Object.create(perosn);
yetAbotherPerson.name = "Linda";
yetAbotherPerson.firends.push("Barbie");

alert(person.friends);  //"Shelby, Court, Van, Greg, Linda"


Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都通过自己的描述定义的。用这种方式指定的任何属性都会覆盖原型对象上的同名属性。

var person = {
    name : "Nicholas",
    friends : ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(perosn,{
    name : {
        value : "Grag"
    }
});
 alert(anotherPerson.name);  //"Greg"
5、寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。以下代码示范了寄生式继承模式。

function creatAnother(original) {
     var clone = object(original);   //通过调用函数创建一个新对象
     clone.sayHi = function() {      //以某种方式来增强这个对象
         alert("Hi");
     };
     return clone;  //返回这个对象
 }

基于person返回一个新对象——anotherPerson。新对象不仅具有Person的所有属性和方法,还有自己的sayHi方法。

 function creatAnother(original) {
     var clone = object(original);   //通过调用函数创建一个新对象
     clone.sayHi = function() {      //以某种方式来增强这个对象
         alert("hi");
     };
     return clone;  //返回这个对象
 }
 var person = {
    name : "Nicholas",
    friends : ["Shelby", "Court", "Van"]
};

var anotherPerson = creatAnother(person);
anotherPerson.sayHi();  //"hi"
6、寄生组合式继承

组合继承最大的问题就是:无论在什么情况下,都会调用两次构造函数,一次是在创建子类型原型的时候,一次是在子类型构造函数的内部,导致子类最终会包含超类对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。

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

SuperType.prototype.sayName = function() {
    alert(this.name);
};
function SubType(name ,age) {
    //继承属性
    SuperType.call(this.name);       //第二次调用SuperType()
    this.age = age;
}
//继承方法
SubType.prototype = new SuperType();  //第一次调用SuperType()
SubType.prototype.constructor = SubType;  //指向构造函数
SubType.prototype.sayAge = function() {
    alert(this.age);
};


寄生式组合继承,就是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其思想是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需的无非就是超类型原型的一个副本而已。本质上就是,使用寄生式继承来继承超类型的原型,然再将结果指定给子类型的原型。其基本模式如下:

function inheritPrototype(SubType,superType) {
    var prototype = object(superType.prototype);   //创建对象
    prototype.constructor = subType;               //增强对象
    subType.prototype = prototype;                 //指定对象
}

这个函数接收两个参数:子类型构造函数和超类型构造函数。
第一步是创建超类型原型的一个副本;
第二步是为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性;
第三步将新创建的对象(即副本)赋值给子类型的原型。

function inheritPrototype(SubType,superType) {
    var prototype = object(superType.prototype);   //创建对象
    prototype.constructor = subType;               //增强对象
    subType.prototype = prototype;                 //指定对象
}

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

SuperType.prototype.sayName = function() {
    alert(this.name);
};
function SubType(name ,age) {
    //继承属性
    SuperType.call(this.name);      
    this.age = age;
}

inheritPrototype(SubType, SuperType);

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


这个例子的高效率体现在它只调用了一次SuperType构造函数。

7、总结

JavaScript主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。SubType.prototype=new SuperType();这样,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜多带带使用。解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用超类型构造函数SuperType.call(this,name); 这样就可以做到每个实例都具有自己的属性,同时还能保证只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而借用构造函数继承实例属性。

此外,还存在下列可供选择的继承模式:

原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行给定对象的浅复制。而复制的副本还可以得到进一步的改造。

寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于多次调用超类型函数而导致的低效率问题,可以将这个模式与组合继承一起使用。

寄生组合式继承,集寄生式继承与组合继承的优点于一身,是实现基于类型继承的最有效的方式

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109052.html

相关文章

  • JS学习笔记6)(实现继承几种方式

    摘要:使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而借用构造函数继承实例属性。原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行给定对象的浅复制。 1、原型链实现继承 function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = func...

    hiyayiji 评论0 收藏0
  • 读《javaScript高级程序设计-6继承

    摘要:此时的原型对象包括一个指向另一个原型的指针,相应的,另一个原型中的指向另一个构造函数。这种关系层层递进,就通过一个原型对象链接另一个构造函数的原型对象的方式实现了继承。 读这篇之前,最好是已读过我前面的关于对象的理解和封装类的笔记。第6章我一共写了3篇总结,下面是相关链接:读《javaScript高级程序设计-第6章》之理解对象读《javaScript高级程序设计-第6章》之封装类 一...

    villainhr 评论0 收藏0
  • 红宝书笔记-6-面向对象程序设计

    摘要:构造函数本身也是函数,只不过可以用来创建对象而已。在创建子类型的实例时,没有办法在不影响所有对象实例的情况下,不能向超类型的构造函数中传递参数。借用构造函数又叫伪造对象或经典继承。 本章内容 理解对象属性 理解并创建对象 理解继承 ECMA-262 把对象定义为:无序属性的集合,其属性可以包含基本值、对象或者函数。严格来讲,这就相当于说对象是一组没有特定顺序的值。 每个对象都是基于...

    hizengzeng 评论0 收藏0
  • JS学习笔记6)(面向对象程序设计理解对象

    摘要:其中,描述符对象的属性必须是和。吧设置为,表示不能从对象中删除属性。这个方法接收两个对象参数要添加和修改其属性值的对象,第二个是与第一个对象中要添加和修改的属性值一一对应。 理解对象 1、创建自定义对象的两种方法: (1)创建一个Object实例,然后再为它添加属性和方法。 var person = new Object(); person.name = Nicholas; ...

    FingerLiu 评论0 收藏0
  • 接口

    摘要:前言这一系列的文章将主要基于设计模式这本书的要点还有一些翻阅的博客文章借鉴来源会注明外加自己的一些与直觉不同于其他设计模式类的书设计模式是一本讲述设计模式在动态语言中的实现的书它从设计的角度教人编写代码书中的许多实例代码来自实战项目对面向对 前言 这一系列的文章将主要基于js设计模式这本书的要点还有一些翻阅的博客文章,借鉴来源会注明,外加自己的一些demo与直觉.不同于其他设计模式类的...

    zhjx922 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<