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://www.mzph.cn/news/699322.shtml

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

相关文章

js使用import到本js文件中的函数时报错 Error [ERR_MODULE_NOT_FOUND]: Cannot find module

node:internal/process/esm_loader:97internalBinding(errors).triggerUncaughtException(^Error [ERR_MODULE_NOT_FOUND]: Cannot find module D:\桌面\Pagesizedetection\lib\screensize imported from D:\桌面\Pagesizedetection\index.js Did you mean to import ../lib/sc…

网页数据的存储--存储为文本文件(TXT、JSON、CSV)

用解析器解析出数据后,接下来就是存储数据了。数据的存储有多种多样,其中最简单的一种是将数据直接保存为文本文件,如TXT、JSON、CSV等。这里就介绍将数据直接保存为文本文件。 目录 一、Python存储数据的方法 1、 文件读取 2、 文件写入…

【Logback】Logback 日志框架的架构

目录 1、Logger(记录器) (1)有效级别和级别继承 (2)日志打印和日志筛选 (3)记录器命名 2、Appenders(追加器) 3、Layouts(布局)…

npm install 失败,需要node 切换到 对应版本号

npm install 失败 原本node 的版本号是16.9,就会报以上错误 node版本问题了,我切到这个版本,报同样的错。降一下node(14.18)版本就好了 具体的方法:(需要在项目根目录下切换) 1. …

泰山派学习笔记(二)一步一步编译SDK文件

上一节,我们安装了基于虚拟机的ubuntu系统,并且建立了samba服务打通了win10和ubuntu系统中的文件传输。本节课我们继续对立创官方提供的SDK文件进行编译,学习编译的方法。引用官方的话:如果只想下载别人编译好的固件并且做一些应用…

Python实战:xlsx文件的读写

Python实战:xlsx文件的读写 🌈 个人主页:高斯小哥 🔥 高质量专栏:Matplotlib之旅:零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 👈 希望得到您的订阅和支持~ &#…

图像压缩感知的MATLAB实现(OMP)

前面实现了 压缩感知的图像仿真(MATLAB源代码) 效果还不错,缺点是速度慢如牛。 下面我们采用OMP对其进行优化,提升速度。具体代码如下: 仿真 构建了一个MATLAB文件,所有代码都在一个源文件里面&#xf…

Unet 高阶分割网络实战、多类别分割、迁移学习(deeplab、resnet101等等)

1、前言 Unet 图像分割之前介绍了不少,具体可以参考 图像分割专栏 为了实现多类别的自适应分割,前段时间利用numpy的unique函数实现了一个项目。通过numpy函数将mask的灰度值提取出来,保存在txt文本里,这样txt里面就会有类似0 1…

力扣精选100道——外观数列(模拟专题)

外观数列算法题链接 🚩了解题意 该题的下面充分的给你说明了这个题目的意思。 3 3 2 2 2 5 1 我们根据我们正常读的顺序读 俩个3 三个2 一个5 一个1 连起来就是 2 3 3 2 1 5 1 这就是最终输出的字符串。 题目开头说了,我们最初是 1开始读…

板块一 Servlet编程:第五节 Cookie对象全解 来自【汤米尼克的JAVAEE全套教程专栏】

板块一 Servlet编程:第五节 Cookie对象全解 一、什么是CookieCookie的源码 二、Cookie的具体操作(1)创建Cookie(2)获取Cookie(3)设置Cookie的到期时间(4)设置Cookie的路径…

【ArcGIS】利用高程进行坡度分析:区域面/河道坡度

在ArcGIS中利用高程进行坡度分析 坡度ArcGIS实操案例1:流域面上坡度计算案例2:河道坡度计算2.1 案例数据2.2 操作步骤 参考 坡度 坡度是地表单元陡缓的程度,通常把坡面的垂直高度和水平距离的比值称为坡度。 坡度的表示方法有百分比法、度数…

计算机网络面经-TCP三次握手一文说清

目录 说一下TCP的三次握手? 为什么要三次握手?两次行不行?四次呢? 为什么建立连接是三次握手,关闭连接确是四次挥手呢? TCP四次挥手的过程? 如果已经建立了连接,但是客户端突然出…

TSL四次握手

HTTPS 常用的密钥交换算法有两种,分别是 RSA 和 ECDHE 算法。 其中,RSA 是比较传统的密钥交换算法,它不具备前向安全的性质,因此现在很少服务器使用的。而 ECDHE 算法具有前向安全,所以被广泛使用。 1. ECDHE算法 1.…

PostgreSQL如何使用UUID

离线安装时,一般有四个包,都安装的话,只需要开启uuid的使用即可,如果工具包(即 postgresql11-contrib)没有安装的话,需要单独安装一次,再进行开启。 开启UUID方法 下面介绍一下如何开启&#…

ELK介绍以及搭建

基础环境 hostnamectl set-hostname els01 hostnamectl set-hostname els02 hostnamectl set-hostname els03 hostnamectl set-hostname kbased -i s/SELINUXenforcing/SELINUXdisabled/ /etc/selinux/config systemctl stop firewalld & systemctl disable firewalld# 安…

互联设备-中继器-路由器等

网卡的主要作用 1 在发送方 把从计算机系统要发送的数据转换成能在网线上传输的bit 流 。 2 在接收方 把从网线上接收来的 bit 流重组成计算机系统可以 处理的数据 。 3 判断数据是否是发给自己的 4 发送和控制计算机系统和网线数据流 计算机的分类 1、台式机 2、小型机和服…

亿道丨三防平板丨加固平板丨为零售业提供四大优势

随着全球经济的快速发展,作为传统行业的零售业也迎来了绝佳的发展机遇,在互联网智能化的大环境下,越来越多的零售企业选择三防平板电脑作为工作中的电子设备。作为一种耐用的移动选项,三防平板带来的不仅仅是坚固的外壳。坚固耐用…

计算机网络面经-从浏览器地址栏输入 url 到显示主页的过程?

大概的过程比较简单,但是有很多点可以细挖:DNS解析、TCP三次握手、HTTP报文格式、TCP四次挥手等等。 DNS 解析:将域名解析成对应的 IP 地址。TCP连接:与服务器通过三次握手,建立 TCP 连接向服务器发送 HTTP 请求服务器…

模型 KISS复盘法

系列文章 分享 模型,了解更多👉 模型_总纲目录。重在提升认知。反思过去,不断进步。 1 KISS复盘法的应用 1.1 团队项目复盘 在一个团队项目结束后,团队成员可以使用KISS模型进行复盘,以总结经验教训并改进未来的工作…

Web3之光:揭秘数字创新的未来

随着数字化时代的深入发展,Web3正以其独特的技术和理念,为我们打开数字创新的崭新视角。作为数字化时代的新兴力量,Web3将深刻影响着我们的生活、工作和社会。本文将揭秘Web3的奥秘,探讨其在数字创新领域的前景和潜力。 1. 重新定…