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

news2024/12/25 12:38:36

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

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

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/490893.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java校招面试】基础知识(六)——计算机网络

目录 前言一、TCP协议 / UDP协议二、HTTP协议后记 前言 本篇主要介绍计算机网络的相关内容。 “基础知识”是本专栏的第一个部分,本篇博文是第六篇博文,如有需要,可: 点击这里,返回本专栏的索引文章点击这里&#xf…

Leetcode303. 区域和检索 - 数组不可变

Every day a leetcode 题目来源&#xff1a;303. 区域和检索 - 数组不可变 解法1&#xff1a;暴力 代码&#xff1a; class NumArray { public:vector<int> v;NumArray(vector<int> &nums){v nums;}int sumRange(int left, int right){int sum 0;for (in…

ChatGPT镜像网站【免费白嫖】

文章目录 前言说明网站 前言 ChatGPT是一种基于OpenAI GPT&#xff08;Generative Pretrained Transformer&#xff09;模型的聊天机器人&#xff0c;它可以对用户提出的问题做出回答&#xff0c;同时还能够进行精准的语言理解和回复&#xff0c;较好地满足了人们与机器人之间…

避免使用第三方工具完成电脑环境检测

0. 简介 在之前配置各种深度学习环境的时候经常需要先检测一下电脑的软硬件环境&#xff0c;其实整个过程比较重复和固定&#xff0c;所以我们是否有可能一键检测Python版本、PIP版本、Conda版本、CUDA版本、电脑系统、CPU核数、CPU频率、内存、硬盘等内容这是很多Deepper苦恼…

[架构之路-183]-《软考-系统分析师》-13-系统设计 - 高内聚低耦合详解、图解以及技术手段

目录 第1章 什么是高内聚低耦合 1.1 概念 1.2 目的 1.3 什么时候需要进行高内聚低耦合 1.4 什么系统需要关注高内聚、低耦合 第2章 分类 2.1 内聚的分类 2.2 耦合的分类 第3章 增加高内聚降低耦合度的方法 3.1 增加高内聚 3.2 降低耦合度 第1章 什么是高内聚低耦…

SpringCloud_Gateway服务网关

文章目录 一、SpringCloudGateway服务网关概论1、SpringCloudGateway服务网关概论2、SpringCloudGateway的三大核心概念 二、SpringCloudGateway的路由及断言1、子模块项目SpringCloudGateway的搭建2、SpringCloudGateway_Java API构建路由3、SpringCloudGateway的动态路由功能…

cmd 批量ping命令

cmd 批量ping命令 1、批量ping 1个网段2、批量ping多个网段 1、批量ping 1个网段 ping 1个网段 for /l %X in (2,1,254) do (ping -n 2 10.1.2.%X && echo 10.1.2.%X >> ok.txt || echo 10.1.2.%X >> no.txt)命令说明&#xff1a; %l 表示在(2,1,254) 按…

免费矢量图标网站都有哪些,推荐这10个

矢量图标是我们日常设计应用程序和网页过程中不可缺少的元素之一。通过小矢量图标&#xff0c;我们可以快速方便地实现视觉指导和功能划分。 但在创作中&#xff0c;设计师往往需要花费大量的时间和精力来寻找不同网站的矢量图标&#xff0c;以满足他们的设计需求&#xff0c;…

电子价签能给生鲜零售带来什么?

生鲜零售 变价难 超市中的水果、蔬菜、鱼肉海鲜等商品&#xff0c;往往会受季节变化、运输和储存成本、自然环境引起的生产成本、供需关系等因素影响&#xff0c;其商品价格变动比较频繁。如不能及时更新价格&#xff0c;容易影响商品的销售&#xff0c;进而影响超市的盈利能…

asp.net基于web的大学生交友网站shfw9294程序

为社会的和谐做出了贡献。 通过网络&#xff0c;不同地区的人员可跨越时间、地点进行交流。建设一个安全、可靠、开放、纯净的大学生交友网站系统&#xff0c;为中山市大学生提供一个使用方便的、 界面美观的交友园地是本系统的主要目的。 系统使用Visual studio.net2010作为系…

【计算机网络】总结复习(1)

本文主要记录在看小林coding 时的一些体会&#xff0c;会记录一些我认为重要的知识点以及感受 网络基础篇 osi 七层协议 tcp/ip 四层协议 应用层 传输层 网络层 网络接口层 实际场景&#xff1a; 输出网址 到 网页显示 过程url 解析&#xff08;协议web 服务器 数据源路径…

智慧城市规划数字化管理:数字孪生技术的创新应用

随着智能城市的不断发展&#xff0c;数字孪生技术也开始在智慧城市的建设中得到了广泛应用。数字孪生作为一种数字化的复制技术&#xff0c;它可以模拟真实世界中的实体和过程。 在城市规划方面&#xff0c;数字孪生可以帮助城市规划师更加直观地了解城市的整体规划和发展趋势&…

菜鸡shader2:L2基于BlinnPhong假环境反射,生锈材质

目录 假环境反射材质&#xff08;不锈钢材质&#xff09;生锈材质 假环境反射材质&#xff08;不锈钢材质&#xff09; 先放上最终图&#xff1a; 处理高光部分&#xff1a; 这里是phong模型不是blinnphong。应用观察方向的反射方向和光方向点乘算出高光&#xff0c;然后重…

PHP+vue大学生心理健康评价和分析系统8w3ff

本整个大学生心理健康管理系统是按照整体需求来实现各个功能的&#xff0c;它可以通过心理健康测评来检测大学生的心理健康&#xff0c;并且给予预警&#xff0c;还可以预约医生来解决问题。并且&#xff0c;管理员可以查看用户信息&#xff0c;发布一些关于心理健康的文章。该…

CVE-2023-29489 cPanel XSS漏洞分析研究

前言 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 如果文章中的漏洞出现敏感内容产生了部分影响&#xff0c;请及时联系作者&#xff0c;望谅解。 一、漏洞原理 漏洞简述 cPa…

MySQL之Log Buffer详解

前言 本文已收录在MySQL性能优化原理实战专栏&#xff0c;点击此处浏览更多优质内容。 上一篇文章MySQL之Doublewrite Buffer详解首次提到Redo Log的概念&#xff0c;Redo Log是数据库体系架构中非常重要的一个模块&#xff0c;它能保证数据库的Crash-safe&#xff08;崩溃恢复…

Linux Docker 搭建WordPress个人博客(避坑篇)

本文主要参考文章&#xff1a;Docker实战&#xff1a;Docker安装WordPress&#xff0c;快速搭建自己的博客 但我在参考过程中踩坑较多&#xff0c;特此记录下 1、What is WordPress 官网&#xff1a;https://wordpress.com/zh-cn/ wordpress是世界上应用最广泛的开源CMS程序…

Kotlin中的密封类和密封接口

密封类和密封接口是 Kotlin 中允许创建受限类层次结构的两个特性。这两个构造用于定义一组有限的可能子类型&#xff0c;并防止在声明的层次结构之外定义其他子类型。 密封类 密封类是一个可以被子类化的类&#xff0c;但只能在声明它的同一个文件中进行子类化。这意味着密封…

内网穿透(ngrock)

什么是内网穿透&#xff1f; 内网穿透&#xff0c;即NAT穿透&#xff0c;网络连接时术语&#xff0c;计算机是局域网内时&#xff0c;外网与内网的计算机节点需要连接通信&#xff0c;有时就会出现不支持内网穿透。简单来说&#xff0c;就是让身处局域网的电脑&#xff0c;被大…

《基于深度学习模型的非接触式面部视频记录技术用于心房颤动的检测》阅读笔记

目录 一、论文摘要 二、论文十问 Q1: 论文试图解决什么问题&#xff1f; Q2: 这是否是一个新的问题&#xff1f; Q3: 这篇文章要验证一个什么科学假设&#xff1f; Q4: 有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课题在领域内值得关注的研究员&#xff1f; …