当前位置: 首页 > news >正文

js原型链以及实现继承的手段

 1.原型链

其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么层层递进,就构成了实例与原型的链条。这就是原型链的基本概念。

function Human(){this.type = 'mammal'
}
Human.prototype.getType = function(){return this.type;
};function Person(){this.alive = true;this.foods = 'vegetable'
};// 继承了Human
Person.prototype = new Human()
// 给原型添加方法的代码放在替换原型的语句之后
Person.prototype.getAlive = function(){return this.alive;
}
var instance = new Person();instance.getType(); // 'mammal'
console.log(instance.type); // 'mammal'
instance.getAlive(); // true
console.log(instance.foods); // vegetable

 上述代码实现的本质是用一个新类型的实例重写原型对象。

原来存在于Human实例中的所有属性和方法,现在也存在于Person.prototype中。我们没有使用Person默认提供的原型,而是给它换了一个新原型;这个新原型就是Human的实例。

最终结果就是instance指向Person的原型,Person的原型又指向Human的原型。getType()方法仍然还在Human.prototype中,但type则位于Person.prototype中。这是因为type是一个实例属性,而getType()则是一个原型方法。既然Person.prototype现在是Human的实例,那么type当然就位于该实例中了。也就是说alive、foods存在于instance实例中,type存在于Person.prototype实例中。因为彼此都是互相的实例属性。

现在instance.constructor现在指向的是Human,这是因为原来Person.prototype被重写了的缘故。(实际上不是Person原型的constructor属性被重写了,而是Person的原型指向了另一个对象Human的原型,而这个原型对象的constructor属性指向的是Human)

通过实现原型链,本质上扩展了原型搜索机制。即如果搜索一个实例属性时,先搜索实例,继续搜索实例的原型,在通过原型链实现继承的情况下,搜索过程就可以沿着原型链继续向上。调用instance.getType()会经历三个搜索步骤:(1)搜索instance实例;(2)搜索Person.prototype;(3)搜索Human.prototype 搜索过程总是要一环一环地前行到原型链末端才会停下来。

如果上述代码,Person原型上若重新定义getType()方法。那么调用instance.getType()则会读取Person原型上的这个方法,屏蔽掉Human原型上的这个方法。但是Human的其他实例调用getType()方法则不受影响,继续调用的是Human原型上的该方法。

function Human(){this.type = 'mammal'
}
Human.prototype.getType = function(){return this.type;
};function Person(){this.alive = true;this.foods = 'vegetable'
};// 继承了Human
Person.prototype = new Human()
// 给原型添加方法的代码放在替换原型的语句之后
Person.prototype.getAlive = function(){return this.alive;
};
Person.prototype.getType = function(){return false;
};
const instance = new Person();
instance.getType(); // false
const people = new Human();
people.getType(); // 'mammal'

通过原型链实现继承的时候,不能使用对象字面量创建原型方法。因为这样会重写原型链。

 

function Human(){this.type = 'mammal'
}
Human.prototype.getType = function(){return this.type;
};function Person(){this.alive = true;this.foods = 'vegetable'
};// 继承了Human
Person.prototype = new Human()
// 给原型添加方法的代码放在替换原型的语句之后
Person.prototype = {getAlive: function(){return this.alive;},getAnother: function(){return false;}
}
const instance = new Person();
instance.getType(); // error

以上代码,刚刚把Human的实例赋给Person原型,紧接着又将原型替换成一个对象字面量。所以现在Person的原型包含的是一个Object的实例,而不是Human的实例。原型链已经被切断。Person与Human之间已经没有关系了。 

所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。也就是说Person继承了Human,而Human继承了Object。当调用instance.toString()时,实际上调用的是保存在Object.prototype中的那个方法。

由于原型链的关系,instance是Object、Person、Human中任何一个类型的实例。

console.log(instance instanceof Object); // true
console.log(instance instanceof Person); // true
console.log(instance instanceof Human); // true
console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(Person.prototype.isPrototypeOf(instance)); // true
console.log(Human.prototype.isPrototypeOf(instance)); // true

原型链的问题

(1)尽管原型链很强大,可以用它实现继承,但它也存在一些问题。最主要的问题是包含引用类型值的原型。在通过原型实现继承时,原型会变成另一个类型的实例。于是,原先实例属性也就成为现在原型属性。如果原先实例属性是一个引用类型,那么原型的其他实例都会共享这个属性。

function Human(){this.colors = ['white','black']
};
function Person(){
};
// Person继承了Human
Person.prototype = new Human();const instance1 = new Person();
instance1.colors.push('yellow');
console.log(instance1.colors); // ['white','black', 'yellow']const instance2 = new Person();
console.log(instance2.colors); // ['white','black', 'yellow']

如上代码,所有Person实例,都共享了colors的属性。

 (2)创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上应该说没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

2.借用构造函数

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

function Human(name){this.name = name;
};
Human.prototype.sayName = function(){alert(this.name);
}
function Person(){// 继承了Human,同时还传递了参数Human.call(this, 'lee');// 实例属性this.age = 29;
};const instance = new Person();
console.log(instance.name); // 'lee'
console.log(instance.age); // 29
instance.sayName(); // error

为了确保Human里的属性不会重写Person中的实例属性,所以在调用超类构造函数后,再添加应该在子类中定义的属性。

借用构造函数的问题

(1)方法都在构造函数中定义,函数复用无从谈起。

(2)在超类型的原型中定义的方法,对子类型而言也时不可见的,结果所有类型都只能使用构造函数模式。

考虑到这些问题,借用构造函数的技术也时很少单独使用的。

3.组合继承

有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

function Human(name){this.name = name;this.colors = ['black','white'];
};
Human.prototype.sayName = function(){alert(this.name);
}
function Person(name, age){// 继承了Human,同时还传递了参数Human.call(this, name); // 第二次调用超类型Human()// 实例属性this.age = age;
};
Person.prototype = new Human(); // 第一次调用超类型Human()
Person.prototype.constructor = Person;
Person.prototype.sayAge = function(){alert(this.age)
}
const instance = new Person('lee', 29);
instance.colors.push('yellow');
console.log(instance.colors); // ['black','white','yellow']
instance.sayName(); // 'lee'
instance.sayAge(); // 29const instance1 = new Person('Alice', 24);
console.log(instance1.colors); // ['black','white']
instance1.sayName(); // 'Alice'
instance1.sayAge(); // 24

组合继承的问题

无论什么情况下,都会调用2次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。 如上面例子,在第一次调用Human构造函数时,Person.prototype会得到2个属性,name和colors;它们都是Human的实例属性,只不过现在位于Person.prototype的原型中。当调用Person构造函数时,又会调用一次Human构造函数,这一次又在新对象上创建了实例属性name和colors。于是,这两个属性就屏蔽了原型中的两个同名属性。也就是说有2组name和colors属性。一组在实例上,一组在原型中。

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为javascript中最常用的继承模式。而且instanceof和isPrototypeOf也能够用于识别基于组合继承创建的对象。

4.原型式继承

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

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

从本质上讲,object()对传入其中的对象执行了一次浅复制。

const person = {name: 'lee',friends: ['Alice', 'Bob', 'Jack']
}
// 这里的object是上面创建的那个object的函数
const anotherPerson = object(person);
anotherPerson.name = 'Gred';
anotherPerson.friends.push('Linda');const yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Coco';
yetAnotherPerson.friends.push('lucy');console.log(person.friends); //  ['Alice', 'Bob', 'Jack', 'Linda', 'lucy']
console.log(anotherPerson .friends); //  ['Alice', 'Bob', 'Jack', 'Linda', 'lucy']
console.log(yetAnotherPerson.friends); //  ['Alice', 'Bob', 'Jack', 'Linda', 'lucy']

上面例子,person中存在一个基本类型的属性和一个引用类型的属性。这意味着person.friends不仅属于person所有,而且也会被anotherPerson以及yetAnotherPerson共享。实际上,这就相当于又创建了person对象的两个副本。

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

const person = {name: 'lee',friends: ['Alice', 'Bob', 'Jack']
}
// 这里的object是上面创建的那个object的函数
const anotherPerson = object.create(person, {name: 'Gred'
});
console.log(anotherPerson.name); // 'Gred'

原型式继承的问题

与使用原型链实现继承有相似的问题。也就是包含引用类型的属性,会共享相应的值。但是如果只想让一个对象与另一个对象保持类似的情况,没必要创建构造函数。原型式继承式完全可以胜任。

5.寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象。

function createAnother(origin){const clone = object(origin); // 通过调用函数创建一个新对象clone.sayHi = function(){ // 以某种方式增强对象alert('Hi');};return clone; // 返回这个对象
}const person = {name: 'lee',friends: ['Alice', 'Bob', 'Jack']
}const anotherPerson = createAnother(person);
anotherPerson.sayHi(); // 'Hi'
anotherPerson.friends.push('Lee');
console.log(anotherPerson.friends); // ['Alice', 'Bob', 'Jack', 'Lee']
console.log(person.friends); // ['Alice', 'Bob', 'Jack', 'Lee']

寄生式继承的问题

与构造函数模式类似,会因为不能做到函数复用,而降低效率。上面例子object()函数不是必须的,任何能够返回新对象的函数都适用于此模式。在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也式一种有用的模式。

6.寄生组合式继承

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

function inheritPrototype(person, human){const prototype = object(human.prototype); // 创建对象副本prototype.constructor = person; // 增强对象person.prototype = prototype; // 指定对象
}

在函数内部,第一步式创建超类型原型的一个副本。第二步式为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认constructor属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。

function Human(name){this.name = name;this.colors = ['black','white'];
};
Human.prototype.sayName = function(){alert(this.name);
}
function Person(name, age){// 继承了Human,同时还传递了参数Human.call(this, name);// 实例属性this.age = age;
};
inheritPrototype(Person, Human);
Person.prototype.sayAge = function(){alert(this.age)
}

这个例子的高效体现在它只调用了一次Human构造函数,并且因此避免了在Person.prototype上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceof和isPrototypeOf()。开发人员普遍认为寄生组合式继承式引用类型最理想的继承范式。 

相关文章:

js原型链以及实现继承的手段

1.原型链 其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。 简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。…...

jdk8u201版本cpu.load过高问题的排查和解决

文章目录 1、背景2、现象3、排查定位4、原因总结5、解决 1、背景 jdk8u45版本存在安全漏洞,性能问题。需要升级到8u201 2、现象 升级到201版本后,出现cpu.load过高 3、排查定位 使用压测工具压测时,cpu.load过高问题必现,确认…...

【计算机网络笔记】数据交换之报文交换和分组交换

系列文章目录报文交换分组交换存储-转发报文交换 vs 分组交换总结 系列文章目录 什么是计算机网络? 什么是网络协议? 计算机网络的结构 数据交换之电路交换 报文交换 报文:源(应用)发送的信息整体。比如一个文件、一…...

【广州华锐互动】利用VR开展细胞基础实验教学有什么好处?

在科技发展的驱动下,虚拟现实(VR)技术已被广泛应用于各个领域,包括教育和医学。尤其是在医学教育中,VR技术已成为一种革新传统教学模式的有效工具。本文将探讨使用VR进行细胞基础实验教学的优势。 首先,VR技…...

基于SSM+Vue的咖啡销售系统

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…...

L2-026 小字辈

本题给定一个庞大家族的家谱,要请你给出最小一辈的名单。 输入格式: 输入在第一行给出家族人口总数 N(不超过 100 000 的正整数) —— 简单起见,我们把家族成员从 1 到 N 编号。随后第二行给出 N 个编号,…...

linux 查看系统版本

命令:lsb_release -a 可能遇到的问题: 问题1: 报错:command not found: lsb_release原因:系统没有安装 lsb_release解决方案:sudo apt-get install lsb-release 问题2: 报错: Tra…...

Python实现PDF转换文件格式

最近工作中经常遇到收到其他人提供的pdf文档,想要编辑修改下或者复制部分内容比较困难,想通过现有的pdf工具软件转换文档格式,基本都要充钱,为了免费实现pdf转换工具,网上查了下相关技术方案,整理了下代码&…...

【Ceph Cluster】完全删除Ceph集群

注意:在执行这些步骤之前,请确保你已经备份了所有重要的数据,并且你明白这些步骤将永久删除 Ceph 集群。 停止 Ceph 服务: systemctl stop ceph.target卸载 Ceph 包:卸载 Ceph 相关的软件包,使用你的 Linux…...

4.Vue-Vue调用第三方接口

题记 用vue调用第三方接口,以下是全部代码和操作流程。 寻找第三方接口网站 推荐:免费API - 提供免费接口调用平台 (aa1.cn) 下面的代码以下图中的接口为例 安装axios模块 在终端输入以下命令: npm install axios 调用第三方接口代码 调…...

大语言模型在推荐系统的实践应用

本文从应用视角出发,尝试把大语言模型中的一些长处放在推荐系统中。 01 背景和问题 传统的推荐模型网络参数效果较小(不包括embedding参数),训练和推理的时间、空间开销较小,也能充分利用用户-物品的协同信号。但是它的缺陷是只能利用数据…...

第三章 交换技术及应用

目录 3.1 port-vlan技术 3.1.1 VLAN概述 3.1.2 VLAN划分方法——Port-VLAN 3.1.3 Port-VLAN工作原理 3.1.3 Port-VLAN配置 3.2 port-vlan仿真演示 3.2.1 实验背景 3.2.2 实验目的 3.2.3 实验设备 3.2.4 实验步骤思维导图 3.3 tag-vlan技术 3.3.1 问题分析 3.3.2 T…...

地震勘探原理部分问题解答

1、二维/三维(陆地/海洋)地震勘探,炮点(激发点)和检波点(接收点)的排布位置如何?画图作答? (1)陆地地震勘探 二维陆地地震野外采集:震…...

两个步骤轻松搞定批量合并视频

你是否曾经有过批量合并视频的需求,但是却苦于不知道如何下手?今天,我将为你介绍一个简单易行的方法,只需两个步骤,让你轻松实现批量合并视频。 第一步:下载并打开固乔智剪软件 首先,你需要下载…...

VR虚拟现实在室内设计仿真教学中的应用演示

1. 虚拟实景漫游:利用VR技术,学生可以通过戴上VR头盔来进入一个虚拟的室内环境中,感受真实的空间氛围。他们可以自由移动和观察,感受室内设计的效果。这样的体验可以增强学生的想象力和空间感知能力,提高他们的设计水平…...

Python操作串口通信

Python操作串口通信 注意Linux下先要修改串口的权限: sudo chmod 777 /dev/ttyUSB0 以下是python代码: # codingutf-8 # 包:pyserial,pymysql # 权限:sudo chmod 777 /dev/ttyUSB0 import serial # import pymysql …...

图详解第四篇:单源最短路径--Dijkstra算法

文章目录 1. 最短路径问题2. 单源最短路径--Dijkstra算法算法思想图解如何存储路径及其权值代码实现调式观察打印最短路径Dijkstra算法的缺陷 3. 源码 1. 最短路径问题 最短路径问题: 从带权有向图(求最短路径通常是有向图)G中的某一顶点出发…...

CRMEB多商户商城系统阿里云集群部署教程

注意: 1.所有服务创建时地域一定要选择一致,这里我用的是杭州K区 2.文件/图片上传一定要用类似oss的云文件服务, 本文不做演示 一、 创建容器镜像服务,容器镜像服务(aliyun.com) ,个人版本就可以 先创建一个命名空间 然后创建一个镜像仓库 查看并记录镜像公网地址…...

Java第三方登录封装工具类

Java中可以使用第三方登录来简化用户登录流程,常见的第三方登录如QQ、微信、微博等。下面是一个Java封装第三方登录的工具类: import java.io.IOException; import java.util.HashMap; import java.util.Map;import org.apache.http.client.ClientProto…...

BUUCTF学习(四): 文件包含tips

1、介绍 2、hackBar插件安装 教程: Hackbar插件安装-CSDN博客 3、解题 php://filter/readconvert.base64-encode/resource要读取的文件 ?filephp://filter/readconvert.base64-encode/resourceflag.php Base64 在线编码解码 | Base64 加密解密 - Base64.us 结束...

德国人工智能公司【Kodex AI】完成160万欧元融资

来源:猛兽财经 作者:猛兽财经 猛兽财经获悉,总部位于德国柏林的人工智能公司【Kodex AI】今日宣布已完成160万欧元融资。 本轮融资由Signals VC领投,Techstars、德意志银行等天使投资者参与,其中包括:most AI首席执行官…...

LeetCode 2 两数相加

题目描述 链接:https://leetcode.cn/problems/add-two-numbers/?envTypefeatured-list&envId2ckc81c?envTypefeatured-list&envId2ckc81c 难度:中等 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式…...

springboot项目启动失败,不打印报错详细信息(启动打印日记问题)

1&#xff1a;出现这种我问题一般都是日记的问题&#xff0c;查看控制台启动打印的第一句&#xff0c;为什么启动失败&#xff0c;需要用那个日记 2&#xff1a;如果使用的是log4j或者logback与slf4j都是默认在依赖web自带的如下 <dependency><groupId>org.springf…...

MyBatis (where、set、foreach)标签

where标签 在上一节SQL 语句中加入了一个条件“11”&#xff0c;如果没有加入这个条件&#xff0c;那么可能就会变成下面这样一条错误的语句。 SELECT id,name,url,age,country FROM website AND name LIKE CONCAT(%,#{name},%)显然以上语句会出现 SQL 语法异常&#xff0c;但…...

flutter开发之安装dart

1、在MacOS系统中打开终端&#xff0c;进入到官网Get the Dart SDK | Dart brew tap dart-lang/dartbrew install dart 注意&#xff1a;若显示没有brew&#xff0c;请先执行第二步骤&#xff0c;如下&#xff1a; 2、打开homebrew的官网Homebrew — The Missing Package Man…...

向量召回:深入评估离线体系,探索优质召回方法

向量召回:深入评估离线体系,探索优质召回方法 1.简介 近年来,基于向量进行召回的做法在搜索和推荐领域都得到了比较广泛的应用,并且在学术界发表的论文中,基于向量的 dense retrieve 的方法也在不少数据集上都战胜了 sparse retrieve,吸引了越来越多的关注。在内网的不…...

播放器缓存队列bug解决方案

背景 我在开发一个播放器的缓存队列时&#xff0c;遇到一个bug,导致包和帧无法被下一个模块读取 找了半天&#xff0c;原来是队列中的包和帧数据要进行内容的刷新暂存 包数据和帧数据不能直接放入队列 //入队&#xff0c;包进队列 int AVPacketQueue::Push(AVPacket *val,i…...

React拖拽实践

当涉及到前端开发中的用户体验时&#xff0c;拖拽功能是一个常见而重要的需求。在React中&#xff0c;实现拖拽功能可以通过多种方式完成&#xff0c;但通常需要深刻理解React的状态管理、事件处理和DOM操作。本文将探讨React中拖拽的实践&#xff0c;包括基本原理、拖拽库的使…...

Stable Diffusion绘图,lora选择

best quality, ultra high res, (photorealistic:1.4), 1girl, off-shoulder white shirt, black tight skirt, black choker, (faded ash gray hair:1), looking at viewer, closeup <lora:koreandolllikeness_v20:0.66> 最佳品质&#xff0c;超高分辨率&#xff0c;&am…...

kube-controller-manager和kube-scheduler不能正常启动

kube-controller-manager-k8s-worker01和kube-scheduler-k8s-worker01没有启动起来 原因&#xff1a; 解决&#xff1a;进入/etc/kubernetes/manifests 编辑 将镜像地址修改为 然后重启kubelet&#xff1a;systemctl restart kubelet.service...