JavaScript原型继承与面向对象编程思想

原型继承与面向对象编程思想

        在JavaScript中,原型(prototype)、构造函数(constructor)和实例对象(instance)是面向对象编程中的重要概念,并且它们之间存在着紧密的关系。

  • 原型(prototype):原型是JavaScript中对象之间关联的一种机制。每个JavaScript对象(除了null和undefined)都有一个原型对象,它包含了对象的属性和方法。当访问一个对象的属性或方法时,如果对象本身没有定义该属性或方法,JavaScript引擎会通过原型链向上查找,直到找到对应的属性或方法为止。同理,原型链是由对象的原型对象构成的链式结构,通过这种机制,对象可以继承原型对象的属性和方法。
  • 构造函数(constructor):构造函数是用于创建对象的函数。JavaScript中,可以通过定义一个函数并使用new关键字来创建对象的实例。构造函数定义了对象的初始状态和行为,并且可以在创建实例时对其进行初始化。构造函数可以包含属性和方法,并且可以使用this关键字引用要创建的实例对象。
  • 实例对象(instance):实例对象是通过构造函数创建的对象,它具有构造函数定义的属性和方法。每个实例对象都是独立的,它们可以根据需要修改自己的属性值,并且可以调用构造函数中定义的方法。实例对象通过原型链与构造函数的原型对象关联在一起,从而实现属性和方法的继承。

JavaScript 原型与原型链

prototype: 每一个 函数都有一个特殊的属性叫做原型,指向 该函数的原型对象,原型对象上定义的属性和方法,可以被该函数的实例所共享。
constructor: 相比于普通对象的属性,原型对象prototype属性本身会有一个指向构造函数的指针,即constructor属性, 指向创建该原型对象的构造函数。prototype包含constructor。
__proto__: 每一个对象都有一个__proto__属性, 指向它的构造函数的prototype属性所指向的对象,也就是该对象的原型。
function Car(make, model, year) {//Car是一个构造函数  this.make = make;  this.model = model;  this.year = year;  
}  
var myCar = new Car('Toyota', 'Corolla', 1995);//myCar是Car的一个实例  
// 原型链结构如下:  
// myCar -> Car.prototype -> Object.prototype  
// 查看各个属性的指向  
console.log(myCar.__proto__ === Car.prototype); //true 因为myCar是由Car构造函数创建的
console.log(Car.prototype.constructor === Car); //true 因为Car.prototype是Car构造函数的原型对象 
console.log(myCar.constructor === Car); //true 因为myCar继承自Car的原型  //**指向Car构造函数对象本身的原型,Car是一个函数,它的原型是Function.prototype(Function.prototype是所有函数对象的默认原型)
console.log(Object.getPrototypeOf(Car) === Function.prototype); //true 
console.log(Car.__proto__===Function.prototype) //true 
//**指向通过Car构造函数创建的实例的原型,是一个包含共享属性和方法的对象(是一个独立的对象)
console.log(Object.getPrototypeOf(Car.prototype) === Object.prototype); 
console.log(Car.prototype.__proto__===Object.prototype) //true // 沿着原型链查找属性  
console.log(myCar.toString); //函数,来自Object.prototype
Car.prototype.say=function(){console.log("hi")};//给Car的原型对象添加say方法
myCar.say()//Hi

原型

        在JavaScript中,每个函数都有一个prototype属性,这个属性指向函数的原型对象,这个原型对象包含了可以从该对象实例上访问的属性和方法。此外,每个实例对象都有一个__proto__属性它指向这个对象的原型对象(构造该实例的函数的原型对象)

        当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

        当给一个对象添加一个属性或方法时,它会先查找这个对象本身是否有这个属性或方法。如果有,它就会直接覆盖;如果没有,它会把这个属性或方法添加到这个对象本身;这个过程叫做属性或方法的赋值;赋值后,这个对象就拥有了这个属性或方法。

在JavaScript中,每个对象都有一个关联的原型(prototype),它是一个对象或 null。原型对象包含共享的属性和方法,可以被其他对象继承,而对象则可以访问这些属性和方法。

原型链

原型链的工作原理可以概括为

当访问一个对象的某个属性时,会先在这个对象本身属性上查找,

如果没有找到,则会到自身的__proto__上查找,而实例的__proto__指向其所属类的prototype,即会在类的prototype上进行查找,

如果还没有找到,继续到类的prototype的__proto__中查找,即Object.prototype,

如果在Object.prototype中依旧没有找到,那么返回null。

        原型链允许对象继承其他对象的属性和方法,而不需要在每个对象中重复定义这些属性和方法,从而提高了代码的复用性和效率。

  • 一切对象都是继承自Object对象,Object对象直接继承根源对象null
  • 一切的函数对象(包括Object对象),都是继承自Function对象
  • Function对象的__proto__会指向自己的原型对象,最终还是继承自Object对象
function exampleFunction() {} // 一个函数对象
// exampleFunction是一个函数对象,它的__proto__属性指向Function.prototype
console.log(exampleFunction.__proto__ === Function.prototype); // true  
// Function.prototype本身也是一个对象,它的__proto__属性指向Object.prototype 
console.log(Function.prototype.__proto__ === Object.prototype); // true  
// 原型链:exampleFunction -> Function.prototype -> Object.prototype  

        在JavaScript中,每个对象都有一个指向它的构造函数的指针,而每个构造函数都有一个指向它的原型对象的指针。当创建一个新的对象实例时,这个实例的__proto__指针会指向构造函数的原型对象,同时,构造函数的prototype属性也会指向这个原型对象。这样,就形成了一个构造函数、原型和实例之间的三角关系,这种三角关系的工作方式如下:

  • 当创建一个新实例时,它的__proto__指针被设置为构造函数的prototype对象(即原型对象)
  • 构造函数的prototype属性指向原型对象,这个原型对象包含了所有实例共享的属性和方法。
  • 原型对象的constructor属性指向构造函数本身,形成了一个闭环。

原型链是由对象的原型构成的链状结构。当试图访问对象的属性或方法时,如果对象本身没有定义,JavaScript引擎就会沿着原型链向上查找,直到找到相应的属性或方法,或者链结束(即原型为null)。

JavaScript 原型实现继承(原型继承、构造函数继承、组合继承)

        JavaScript的原型继承与其他一些面向对象语言的类继承有所不同。在JavaScript中,没有显式的类声明和继承关键字,而是通过原型链和构造函数来实现继承。

下面通过一个父类Animal来举例子:

function Animal(name) {this.name = name || 'Animal';
}
Animal.prototype.sayHello = function() {console.log( "'Hello, I'm'" + this.name);
};

原型继承

        原型链继承通过将父类的实例作为子类的原型,从而实现继承。(通过将一个构造函数的实例赋值给另一个构造函数的原型来实现继承关系)

//通过这种方式,Cat 继承了 Animal 的属性和方法。
function Cat(color) {this.color = color
}
// 将Animal的实例赋值给Cat的原型
Cat.prototype = new Animal();
Cat.prototype.name = 'Whiskers';
let myCat = new Cat();
myCat.sayHello();  // 输出:Hello, I'm Whiskers
优点:
①实例既是子类的实例,也是父类的实例,继承关系非常纯粹。
②子类可以访问父类新增的原型方法和属性。
③实现简单,易于理解和实现。
缺点:
①创建子类实例的时候,不能传参数。无法向超类传递参数。
②子类无法在构造器中新增属性和方法,只能在实例化后添加。
③无法实现多继承。
④所有实例共享来自原型对象的属性,包括引用属性。
原型链继承适用于简单的继承关系和单一继承需求的场景。

构造函数继承

        因为JS中没有类这个概念,所以JS的设计者使用了构造函数来实现继承机制。JS通过构造函数来生成实例。但在构造函数中通过this赋值的属性或者方法,是每个实例的实例属性以及实例方法,无法共享公共属性。所以又设计出了一个原型对象,用来存储构造函数的公共属性以及方法

        构造函数继承通过在子类构造函数中调用父类构造函数,复制父类的实例属性给子类,实现对父类属性的继承。

function Dog(name, color) {//使用apply()或call()方法以新创建的对象(即new操作符调用子构造函数创建的那个对象)为上下文执行父类构造函数(以普通函数的形式)Animal.call(this, name);this.color = color;
}
let myDog = new Dog("Buddy", "brown");
myDog.sayHello();  // 输出:Uncaught TypeError: myDog.sayHello is not a function
优点:
①解决了原型链继承中子类实例共享父类引用属性的问题。
②可以在创建子类实例时向父类传递参数。
③支持多继承,可以调用多个父类构造函数。
缺点:
①没有把子类和父类的原型关联起来,子类实例并不是父类的实例,只是子类的实例。
无法继承父类的原型属性和方法,子类没法使用父类的原型方法。
③无法实现函数复用,每个子类都有父类实例函数的副本,影响性能。
构造继承适用于需要继承实例属性、避免引用属性共享以及多继承的场景。

组合继承(原型继承+构造继承)

        组合继承结合了原型继承和构造继承的优点,通过调用父类构造函数来继承父类的属性,并将父类实例作为子类原型,实现函数复用。

function Bird(name, wingspan) {Animal.call(this, name);this.wingspan = wingspan;
}
// 使用Object.create创建新对象,避免引用类型属性共享
Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.constructor = Bird;  // 修复构造函数指向
let myBird = new Bird("Feathers", 50);
myBird.sayHello();  // 输出:Hello, I'm Feathers
特点:
①继承父类实例属性和方法。
②继承父类原型属性和方法。
③既是父类的实例,也是子类的实例。
缺点:
①调用了两次父类构造函数,影响性能。
组合继承适用于大多数场景,是一种常用的继承方式。

ES6中的类和继承

        ES6引入了class 关键字,使得面向对象编程更加直观。但本质上仍然使用原型链实现继承。

class Fish extends Animal {constructor(name, type) {super(name);this.type = type;}swim() {console.log(this.name + " is swimming.");}
}
let myFish = new Fish("Goldie", "Goldfish");
myFish.sayHello();  // 输出:Hello, I'm Goldie
myFish.swim();  // 输出:Goldie is swimming.

JavaScript new创建对象原理详解

        任何函数只要是使用new操作符调用的就是构造函数,而不使用new操作符调用的函数就是普通函数。构造函数是用于创建对象的函数,通过构造函数可以定义对象的属性和方法,原型是一个对象,构造函数通过prototype属性与原型关联。

构造函数的两种属性类型:

  • 实例属性在函数内部通过this设置的都是实例属性,每个实例对象都有自己的一份实例数据,不会相互影响
  • 原型属性在函数外部通过.prototype设置的都是原型属性,是所有实例对象共享的,如果是引用值,那么一个实例修改会导致所有实例都受到影响

        使用new执行函数的时候,new会帮我们在函数内部加工this,最终将this作为实例返回给我们,可以方便我们调用其中的属性和方法。

  • ①在内存中创建一个新对象
  • ②将该新对象内部的[[Prototype]]特性__proto__连接到(赋值为)该构造函数的prototype属性(将构造函数的原型对象赋值给新对象的原型对象,对象与构造函数之间并没有直接的关联)
  • ③将函数内部的this指向这个新创建的对象(将构造函数的作用域赋值给新对象)
  • 执行构造函数内部的代码(为新对象添加实例属性和实例方法)
  • ⑤如果构造函数返回一个非原始类型(即对象或函数)的值,则返回该对象;否则,将this作为返回值,返回刚创建的新对象
const plainObject = {};//1.创建空的简单js对象
plainObject.__proto__ = function.prototype;//2.将空对象的__proto__连接到该函数的prototype
this = plainObject;//3.将函数的this指向新创建的对象
return this//4.返回this

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

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

相关文章

1.0 vue环境安装

1、安装node.js 1.1 下载最新版本Node.js (nodejs.org)Node.js 1.2 开始安装 普通的安装过程,也记录下吧 安装完成! 1.3 检查nodejs是否安装成功 代开cmd命令窗口输入 node -v,如果看到了刚才下载的版本号,则表示已经安装成功…

Go 中如何高效遍历目录?探索几种方法

嗨,大家好!我是波罗学。本文是系列文章 Go 技巧第十八篇,系列文章查看:Go 语言技巧。 目录遍历是一个很常见的操作,它的使用场景有如文件目录查看(最典型的应用如 ls 命令)、文件系统清理、日志…

基于yolov5的苹果检测(pytorch框架)【python源码+UI界面+功能源码详解】

功能演示: 基于yolov5的苹果检测系统,系统既能够实现图像检测,也可以进行视屏和摄像实时检测_哔哩哔哩_bilibili (一)简介 基于yolov5的苹果检测系统是在pytorch框架下实现的,这是一个完整的项目&#xf…

axure9.0 工具使用思考

原型设计软件【AxureRP】快速原型设计工具原型设计软件【AxureRP】快速原型设计工具原型设计软件【AxureRP】快速原型设计工具原型设计软件【AxureRP】快速原型设计工具原型设计软件【AxureRP】快速原型设计工具原型设计软件【AxureRP】快速原型设计工具原型设计软件【AxureRP】…

Vue学习之计算属性

模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。比如说,我们有这样一个包含嵌套数组的对象: const author reactive({name: John Doe,books: [Vue 2 - Advan…

深入了解负载均衡器

每个负载均衡器都是反向代理,但并非每个反向代理都必须是负载均衡器。 0*GFvXmdPz97bwF8vU.jpeg 问题: OSI模型是什么样的? 1*JzMQUxqHiATuQlIgyIv-xQ.png 问题: 负载均衡器的需求是什么? 答案 → 为了创建一个容错系统…

SpringCloud(16)之SpringCloud OpenFeign和Ribbon

一、Spring Cloud OpenFeign介绍 Feign [feɪn] 译文 伪装。Feign是一个轻量级的Http封装工具对象,大大简化了Http请求,它的使用方法 是定义一个接口,然后在上面添加注解。不需要拼接URL、参数等操作。项目主页:GitHub - OpenFeign/feign: Feign makes w…

Promethues的Agent 模式代理转发的实施教程

目录 一、为什么需要代理转发? 二、Prometheus Agent模式的实施步骤 1、升级Prometheus的版本 2、配置B服务器的配置文件 3、启动代理点B服务器的Prometheus 4、接收端C服务器的Prometheus的安装同步骤1 5、启动接收端C服务器的Prometheus 6、验证是否能够正…

CVE-2023-44313 Apache ServiceComb Service-Center SSRF 漏洞研究

本次项目基于go语言(本人不精通),虽不是java web框架了 ,但搭建web服务的框架一些思想理念却是通用的,我们由此可以得到一些蛛丝马迹....... 目录 漏洞简介 漏洞分析 漏洞复现 漏洞简介 Apache ServiceComb Servi…

【MySQL系列 04】深入浅出索引

一、索引介绍 提到数据库索引,相信大家都不陌生,在日常工作中会经常接触到。比如某一个 SQL 查询比较慢,分析完原因之后,你可能就会说“给某个字段加个索引吧”之类的解决方案。 但到底什么是索引,索引又是如何工作的…

Stable Diffusion 模型分享:A-Zovya RPG Artist Tools(RPG 大师工具箱)

本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 A-Zovya RPG Artist Tools 模型是一个针对 RPG 训练的一个模型,可以生成一些 R…

sql注入 [极客大挑战 2019]FinalSQL1

打开题目 点击1到5号的结果 1号 2号 3号 4号 5号 这里直接令传入的id6 传入id1^1^1 逻辑符号|会被检测到,而&感觉成了注释符,&之后的内容都被替换掉了。 传入id1|1 直接盲注比较慢,还需要利用二分法来编写脚本 这里利用到大佬的脚…

如何使用ChatGPT创建一份优质简历

目录 第一步:明确目标和重点 第二步:与ChatGPT建立对话 第三步:整理生成的内容 第四步:注重行文风格 第五步:强调成就和量化结果 第六步:个性化和定制 第七步:反复修改和完善 总结 在现…

国家建筑装配式内装产业基地在沪成立,副主任单位优积科技协同助推绿色低碳循环发展

上海市室内装饰行业协会装配式内装产业专业委员会成立大会暨“国家建筑装配式内装产业基地”项目启动会于3月21日下午1点在上海光大酒店隆重举行。出席此次活动的包括市装协会长徐国俭,市装协党支部书记兼秘书长丛国梁,市装协装配式内装委主任顾泰昌&…

【人脸朝向识别与分类预测】基于LVQ神经网络

课题名称:基于LVQ神经网络的人脸朝向识别分类 版本日期:2024-02-20 运行方式:直接运行GRNN0503.m文件 代码获取方式:私信博主或 企鹅号:491052175 模型描述: 采集到一组人脸朝向不同角度时的图像,图像…

python 基础知识点(蓝桥杯python科目个人复习计划49)

今日复习内容:做复习题 例题1:希尔排序 题目描述: 希尔排序是直接插入排序算法的一种更高效的改进版本,但它是非稳定排序算法。希尔排序是基于插入排序的以下两点性质而提出的改进方法之一: 1.插入排序在对几乎已经…

代码随想录算法训练营第四十天|343. 整数拆分 96.不同的二叉搜索树

343. 整数拆分 链接:. - 力扣(LeetCode) 思路: 动态规划的题目虽然说是要先确定dp数组的含义,再确定递归公式,但是总感觉这两者是相辅相成的,是一起出来的,但是到此,dp数组…

kubernetes负载均衡部署

目录 1.新master节点的搭建 对master02进行初始化配置(192.168.88.31) 将master01的配置移植到master02 修改master02配置文件 2.负载均衡的部署 两台负载均衡器配置nginx 部署keepalived服务 所有node节点操作 总结 实验准备: k8s…

开源大语言模型作为 LangChain 智能体

概要 开源大型语言模型 (LLMs) 现已达到一种性能水平,使它们适合作为推动智能体工作流的推理引擎: Mixtral 甚至在我们的基准测试中 超过了 GPT-3.5,并且通过微调,其性能可以轻易的得到进一步增强。 引言 针对 因果语言建模 训练的大型语言模…

QEMU之CPU虚拟化

概述 KVM是由以色列初创公司Qumranet在CPU推出硬件虚拟化之后开发的一个基于内核的虚拟机监控器。 KVM是一个虚拟化的统称方案,除了x86外,ARM等其他架构也有自己的方案,所以KVM的主体代码位于内核树virt/kvm目录下面,表示所有CP…