首页 > 其他分享 >记录-对象有哪些继承方式

记录-对象有哪些继承方式

时间:2023-05-04 17:22:04浏览次数:41  
标签:function 记录 继承 哪些 SubType 原型 SuperType prototype 构造函数

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

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;
}

let instance = new SubType()
console.log(instance.getSuperValue()); //true

 以上的代码定义了两个类型:SuperType和SubType。这两个类型分别定义了一个属性和方法。两个类型的主要区别是:SubType通过创建SuperType的实例并将其赋值给自己的原型SubType.prototype实现了对SuperType的继承。这个赋值重写了SubType最初的原型,将其替换成SuperType的实例。这意味着SuperType实例可以访问的所有属性和方法也会存在于SubType.prototype。这样实现继承后,紧接着又给SuperType的实例添加了一个新的方法。最后又创建了SubType的实例并调用了它继承的getSuperValue方法。

默认原型

  默认情况下,所有引用类型都继承自Object,这也是通过原型链实现的。任何函数默认的原型都是一个Object的实例,这意味着这个实例有一个内部指针指向Object.prototype。这也是为什么自定义类型能够继承包括toString()、valueOf在内部的所有默认方法的原因。
  SubType继承SuperType,而SuperType继承Object。在调用instance.toString时,实际上调用的是保存在Object.prototype上的方法。

原型与继承的关系

  原型与继承的关系可以通过两种方式来确定:

使用instanceof操作符:

如果一个实例的原型中出现过相应的构造函数,则instanceof返回true

    console.log(instance instanceof  Object); //true
    console.log(instance instanceof  SuperType); //true
    console.log(instance instanceof  SubType); //true

从技术上讲,instance是Object、SuperType、SubType的实例,因为instance的原型链中包含这些构造函数的原型。结果就是instanceof对所有这些构造函数都返回true。

使用isPrototypeOf()方法

原型链中的每个原型都可以调用这个方法,这要原型链中包含这个原型,这个方法就会返回true

    console.log(Object.prototype.isPrototypeOf(instance)); //true
    console.log(SuperType.prototype.isPrototypeOf(instance)); //true
    console.log(SubType.prototype.isPrototypeOf(instance)); //true

关于方法

子类有时需要覆盖父类的方法,或者增加父类没有的方法。为此,这些方法必须在原型赋值之后再添加到原型上。来看下面的例子:

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;
}

let instance = new SubType()
console.log(instance.getSuperValue()); //false

以上代码新增两个方法。第一个方法getSubValue()是SubType的新方法。而第二个方法getSuperValue()是原型链上已经存在但在这里被遮蔽的方法。;后面在SubType实例上调用getSuperValue()时调用的是这个方法。而SuperValue的实例仍然会调用最初的方法。重点在于上述两个方法都是在把原型赋值为SuperType的实例之后定义的。
  另一个要理解的重点,以对象字面量方式创建原型方法,会破环之前的原型链。因为这样相当于重写原型链。

   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(){
            return this.subproperty;
        },
        someOtherMethod(){
            return false;
        }
    }

    let instance = new SubType()
    console.log(instance.getSuperValue()); //instance.getSuperValue is not a function

子类的原型在被赋值为SuperType的实例后,又被一个对象字面量覆盖了。覆盖后的原型是一个Object的实例,而不再是SuperType的实例。因此,之前的原型链就断了。SubType和SuperType之间也没有关系了。

原型链的额问题

原先的实例属性变成了原型属性
  原型包含的引用值会在所有实例间共享,在使用原型实现继承时,原型实际上变成了另一个类型的实例。

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

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

    let instance1  = new SubType()
    instance1.colors.push("black");
    console.log(instance1.colors); //["red", "blue", "green", "black"]

    let instance2 = new SubType()
    console.log(instance2.colors); //["red", "blue", "green", "black"]

SuperType构造函数定义了一个colors属性,其中包含一个数组(引用值)。每个SuperType的实例都会有自己的colors属性,包含自己的数组。但是当SubType通过原型继承SuperType后,SubType.prototype变成了SuperType的一个实例,因而也获得了自己的colors属性。这类似于创建了SubType.prototype.colors属性。最终结果是,SubType的所有实例都会共享这个colors属性。这一点通过instance1.colors上的属性也能反应到instance2.colors上就可以看出。

子类型在实例化时不能给夫类型的构造函数传参
  我们无法在不影响所有对象实例的情况下把参数传进父类的构造函数。再加上原型中包含引用值的问题,就导致原型链基本不会被单独使用。

2. 盗用构造函数

  盗用构造函数是用来解决原型包含引用值导致的继承问题。基本思路:在子类构造函数中调用父类构造函数。因为函数是在特定上下文中执行代码的简单对象,所以可以使用apply()和call()方法以新创建的对象上下文执行构造函数。

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

function SubType(){
    // 继承SuperType
    /* *********** */
    SuperType.call(this);
    /* *********** */
}

let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // ["red", "blue", "green", "black"]

let instance2 = new SubType()
console.log(instance2.colors); //["red", "blue", "green"]

以上代码展示了盗用构造函数的调用。通过使用call()或者apply()方法,SuperType构造函数在为SubType的实例创建的新对象的上下文中执行了。这相当于新的SubType对象上运行了SuperType函数中的所有初始化代码。结果就是每个实例都会有自己的colors属性。

1. 优点

  可以在子类构造函数中向父类构造函数传参

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

function SubType(){
    // 继承SuperType并传参
    SuperType.call(this,"Nicholas");
    // 实例属性
    this.age = 29
}

let instance = new SubType()
console.log(instance.name); //Nicholas
console.log(instance.age); //29

 SuperType构造函数接受一个参数name,然后将它赋值给一个属性。在SubType构造函数中调用SuperType构造函数时传入这个参数,实际上会在SubType的实例上定义name属性。为确保SuperType构造函数不会覆盖SubType定义的属性,可以在调用父类构造函数之后再给子类实例添加额外的属性。

2. 缺点

必须在构造函数中定义方法,因此函数不能重用。
子类不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式。
由于存在以上问题,所以盗用构造函数基本上也不能单独使用。

3. 组合继承  

组合继承也叫伪经典继承,综合了原型链和盗用构造函数,将两者的优点集中起来。基本思路是:使用原型链继承原型上的属性和方法,通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

/* 组合继承 */

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"]
}
SuperType.prototype.sayName = function(){
    console.log(this.name);
}
function SubType(name,age){
    // 继承属性
    SuperType.call(this,name)
    
    this.age = age
}

// 继承方法  
SubType.prototype = new SuperType()
SubType.prototype.sayAge = function(){
    console.log(this.age);
}

let instance1 = new SubType("Nicholas",29)
instance1.colors.push("blcak");
console.log(instance1.colors); //["red", "blue", "green", "blcak"]
instance1.sayName() //Nicholas
instance1.sayAge(); //29

let instance2 = new SubType("Greg",27)
console.log(instance2.colors); //["red", "blue", "green"]
instance2.sayName(); //Greg
instance2.sayAge(); //27

SuperType构造函数定义了两个属性name和colors,而它的原型上也定义了一个方法叫sayName()。SubType构造函数调用了SuperType构造函数,传入name参数,然后又定义了自己的属性age。此外,SubType.prototype也被赋值为SuperType的实例。原型赋值之后,又在这个原型上添加了新方法sayAge()。这样就可以创建两个SubType实例,让这两个实例都有自己的属性,包括colors,同时还共享相同的方法。
  组合继承弥补了原型链和盗用构造函数的不足,是JavaScript中使用最多的继承模式。而且组合继承也保留了instanceof操作符和isPrototypeOf()方法识别合成对象的能力。
缺点:
  父类构造函数会被调用两次,一次是在创建子类原型时调用,一次是在子类构造函数中调用。
  本质上,子类原型最终是要包含超类对象的所有实例属性,子类构造函数只要在执行时重写自己的原型就行了。

4. 原型式继承

  2006年Douglas Crockford的文章:《JavaScript中的原型式继承》中介绍了一种不涉及严格意义上构造函数的继承方法。作者的出发点是即使不自定义类型也可以通过原型实现对象之间的信息共享。文章给出了一个函数:

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

  这个object方法会创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。本质上,object()是对传入对象执行了一次浅复制。

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

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

let yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push("Barbie")

console.log(person.friends); //["Shelby", "Court", "Van", "Rob", "Barbie"]

ECMAScript通过增加Object.create()方法将原型式继承的概念规范化。这个方法接受两个参数:作为新对象原型的对象,给新对象定义额外属性的对象(可选)。在只有一个参数时,Object.create()与这里的object()方法效果类似。

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

let yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda"
yetAnotherPerson.friends.push("Barbie")

console.log(person.friends); //["Shelby", "Court", "Van", "Rob", "Barbie"]

Object.create()的第二个参数与Object.defineProperties()的第二个参数一样:每个新增参数属性都通过各自的描述符来描述。以这种方式添加的属性会遮蔽原型对象上的同名属性。

let person = {
    name:"Nicholas",
    friends:["Shelby",'Court','Van']
}
let anotherPerson = Object.create(person,{
    name:{
        value:'Greg'
    }
});
console.log(anotherPerson.name); //Greg

原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。但要记住,属性中包含的引用值始终会在相关对象间共享,跟使用原型模式时一样的。

5. 寄生式继承

  寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。基本的寄生继承模式如下:

function createAnother(original){
    let clone = object(original);   //通过调用函数创建一个新对象
    clone.sayHi = function(){ //以某种方式增强这个对象
        console.log("HI");
    };
    return clone;  //返沪这个对象
}

createAnother()函数接受一个参数,就是新对象的基准对象。这个对象original会被传给object()函数,然后将返回的新对象赋值给clone对象添加一个新方法sayHi()。最后返回这个对象。

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

function createAnother(original){
    let clone = object(original);   //通过调用函数创建一个新对象
    clone.sayHi = function(){ //以某种方式增强这个对象
        console.log("Hi");
    };
    return clone;  //返沪这个对象
}

let person = {
    name:"Nicholas",
    friends:["Shelby",'Court','Van']
}

let anotherPerson = createAnother(person);
anotherPerson.sayHi(); //Hi
 

 这个例子基于person对象返回了一个新对象。新返回的anotherPerson对象具有person的所有属性和方法,还有一个新方法叫sayHi()
  寄生式继承同样适合主要关注对象,而不在乎类型和构造函数的场景。object()函数不是寄生式继承所必需的,任何返回新对象的函数都可以在这里使用。
==注意:==通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似。

6. 寄生式组合继承

  通过盗用构造函数继承属性,但使用混合式原型链继承方法。基本思路是不通过调用父类构造函数给子类原型赋值,而实取得父类原型的一个副本。说到底就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。

function inheritPrototype(subType,superType){
    let prototype = object(superType.prototype); //创建对象
    prototype.constructor = subType; //增强对象
    subType.prototype = prototype; //赋值对象
}

这个inheritPrototype()函数实现了寄生式组合继承的核心逻辑。这个函数接受到两个参数:子类构造函数和父类构造函数。这个函数内部,第一步是创建一个父类原型的一个副本。然后,给返回的prototype对象设置constructor属性,解决由于重写原型导致默认constructor丢失的问题。最后将新创建的对象赋值给子类型的原型。调用inheritPrototype()就可以实现前面例子中的子类型原型赋值:

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

function inheritPrototype(subType,superType){
    let prototype = object(superType.prototype); //创建对象
    prototype.constructor = subType; //增强对象
    subType.prototype = prototype; //赋值对象
}

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

SuperType.prototype.sayName = function(){
    console.log(this.name);
}

function SubType(name,age){
    SuperType.call(this,name);
    this.age = age;
}

inheritPrototype(SubType,SuperType);

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

这里只调用了一次SuperType构造函数,避免了SubType.prototype上不必要也用不到的属性,因此可以说这个例子效率更高。而且,原型链仍然保持不变,因此,instanceof操作符和isPrototypeOf()方法正常有效。寄生式组合继承可以说是引用类型继承的最佳方式。

本文转载于:

https://blog.csdn.net/qq_39200185/article/details/115134869

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

标签:function,记录,继承,哪些,SubType,原型,SuperType,prototype,构造函数
From: https://www.cnblogs.com/smileZAZ/p/17371915.html

相关文章

  • 23 15|模块导读:互联网时代架构师都面临哪些新挑战?
    你好,我是郭东白。上个模块我们讲了架构师的六条生存法则,提到了架构师的重要工作就是组织架构活动和制定架构方案。那么具体来说,架构活动的完整过程是什么呢?架构师一般会面临什么样的挑战呢?又需要着重关注哪些节点呢?在这个模块里,我们就来回答一下这些问题。这节课是整个模块的导......
  • springboot 切面注解方式 记录日志
    1.定义GateOpLogimportjava.lang.annotation.*;/***操作日志记录*@authorcodefulture*/@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceGateOpLog{/***说明*/Strin......
  • Windows中批量修改DNS记录
    最近由于公网映射的IP需要更换,有一批DNS记录需要修改。对于使用Windows管理的DNS记录,可以使用Powershell批量导出记录更新后再批量修改。首先使用Powershell将DNS服务器上test.local这个区域里的所有A记录导出Get-DnsServerResourceRecord-ComputerNameITM-ZoneNametest.loc......
  • Python爬虫需要哪些基础
    Python爬虫是指使用Python语言编写程序,自动化地访问Web页面并抓取其中的信息。以下是Python爬虫的基础知识:爬虫的工作原理:爬虫程序通过网络请求获取Web页面的HTML源码,然后使用正则表达式或解析器提取所需要的信息。常用的爬虫库:Python中常用的爬虫库包括requests、BeautifulSoup......
  • 企业内部通讯系统有哪些?
    在当前的信息化时代,通讯系统已经成为人们日常生活中必不可少的工具之一,不仅可以用于个人聊天,也可以用于企业内部沟通,为企业带来了沟通效率提升,但为了更好的提升沟通协作效率,不少企业开始转向使用企业内部通讯系统。企业内部通讯系统是一种专为企业内部员工沟通而设计的软件,不仅有......
  • RetinaNet复现记录
    复现内容因为RetinaNet论文用的是coco数据集,而本人因为实验资源与有限,就使用PASCALVOC2012数据集作为复现RetinaNet框架图此图从论文中截取 复现细节和结果训练设置的超参数 1totalparamnum15,597,9982backbone:Resnet50+FPN3优化器:optimizer=torch.o......
  • OCCT学习记录
    TopoDS_Face和Geom_Surface您好,这是必应。根据我查到的信息,occt中的TopoDS_Face和Geom_Surface是两种不同的数据结构,它们之间有一定的关系,但也有区别。¹²TopoDS_Face是一个拓扑对象,它表示一个有限的曲面,由一个无限的几何曲面(Geom_Surface)和一个或多个边界线(TopoDS_Wire)组成......
  • 记录一件很神奇的类型转换问题(springboot项目+echarts)
    今天博主在应付学校的实验,想要使用echarts绘制一张很简单的条形图(博主是初学者),如下(时间还未作排序) 对于横轴,我封装了一个dateList,这个datelist是用java,将数据库中date类型的数据,提取其年月拼装而成的,代码如下:Stringdate=String.valueOf(art.getArticleCreateTime().getYea......
  • SQL的组成包括哪些部分?
    (1)数据定义语言DDL 用于定义和管理对象,例如数据库,数据表,视图 create,drop,alert (2)数据操作语言DML 用于操作数据库对象所包含的数据 insert,update,delete (3)数据查询语言DQL 用于查询数据库对象所包含的数据 select (4)数据控制语言DCL 用于控制对数据库对象操作的权限 grant,re......
  • 记录一次C#在arm64的Ubuntu下安装使用selenium的过程
    手头上有一台没用的安卓手机,想着安装Ubuntu当爬虫服务器用,安卓手机root后使用delopy安装启动Ubuntu系统这些前置过程就不谈了重点谈怎么在ssh下安装使用selenium的过程首先我们安装aptitude这个软件包管理库,这个库相对于apt功能,对软件的依赖处理更加优秀,且使用方式和apt一样su......