JavaScript高级 |彻底搞懂原型对象

news2024/11/25 14:08:22
本文已收录于专栏
⭐️ 《JavaScript》⭐️

学习指南:

  • 对象的原型
  • 函数的原型
    • new操作符
    • 将方法放原型里
    • constructor
  • 总结梳理
    • 原型对象
    • 内存表现
  • 完结散花
  • 参考文献

对象的原型

JavaScript 当中每个对象都有一个特殊的内置属性[[prototype ]] ,这个特殊的对象可以指向另外一个对象。
那这个对象有什么用呢?

  • 当我们通过引用对象的属性Key 来获取一个value时,它会触发[[Get]]的操作;
  • 这个操作会首先检查该对象是否有对应的属性,如果有点话就使用它。
  • 如果对象中没有该属性,那么会访问对象的内置属性[[prototype]],在此属性指向的对象中查找是否有该属性。
  • 只要是对象都会都有这样的一个内置属性。

属性 [[Prototype]] 是内部的⽽且是隐藏的,所有我们可获取的方式有两种:

  • 方式一:通过对象的_proto_属性可以获取到(早期路由器自己添加的,存在一定的兼容性问题)
  • 方式二:通过Object.getPrototypeOf 方法可以获取到。

函数的原型

  1. 将函数看成是一个普通函数的对象时,它是具备[[Prototype]]或者说是_proto_(隐式原型)。
var obj = {}
function fun(){}

console.log(obj._proto_)
console.log(fun._proto_)
  1. 将函数看成是一个普通函数时,它是具备prototype属性的。(显式原型)
var obj = {}
function fun(){}

console.log(fun.prototype)
// console.log(obj.prototype) 对象是没有prototype属性的。

所以 只有函数才具有prototype属性。
注意这里的prototype和[[prototype]]是完全不同的两个概念。

new操作符

new Person(); 

new完 之后会发生什么?

var obj = {};
  • 创建空对象。
this = obj;
  • 将这个空对象赋值给this
obj._proto_ = Person.prototype
  • 将函数的显示原型赋值给创建的这个空对象的_proto_属性作为它的隐式原型。
  • 执行函数体中的代码。
  • 将这个对象默认返回。

再举一个例子来理解一下。

function foo(){

}
console.log(foo.prototype);
	// 先打印一下 foo 属性 
var fun = new foo();
  // 创建空的对象
  // 将foo的prototype原型(显示原型)赋值给空对象的_proto_(隐式原型)
console.log(fun._proto_);

console.log(foo.prototype === fun._proto_)// true

将方法放原型里

function Student(name,age,sno){
	this.name = name;
  this.age = age;
  this.sno = sno;

  this.running = function(){
    console.log(this.name +" running");
  }
  this.eating = function(){
    console.log(this.name +" eating");
  }
  this.studying = function(){
    console.log(this.name +" studying");
  }
}

var stu1 = new Student("why",18,111);
var stu2 = new Student("kobe",16,112);
var stu3 = new Student("jame",15,113);

之前我们编写多个函数方法的时候,会直接在对象中创建。
但我们发现在每调用一次函数时,都会同时创建多个相同的函数对象。
比如上面的例子中三个函数对象runningeatingstudying都分别创建了三次。

那有没有一种方法让我们每种对象只需要创建一次,然后共享这些属性呢?
答案就将函数方法放到显示原型里面!

function Student(name,age,sno){
	this.name = name;
  this.age = age;
  this.sno = sno;
}
Student.prototype.running = function(){
  console.log(this.name + "running")
	}
Student.prototype.eating = function(){
    console.log(this.name +" eating");
  }
Student.prototype.studying = function(){
    console.log(this.name +" studying");
  }
var stu1 = new Student("why",18,111);
var stu2 = new Student("kobe",16,112);
var stu3 = new Student("jame",15,113);
Student.prototype.running = function()
  • 由构造函数创建出来的所有对象,都会共享这些属性,而且每种只创建一次。

查找原理

  • 先在对象内部进行查找。
  • 如果没有找到,就去原型里查找。

那可不可以将其他属性也放到原型里面?
答案是否定的。
因为每个对象的属性值都是不一样的,而原型只有一个。
image.png

constructor

事实上原型对象上面是有一个属性的:constructor

  • 默认情况下原型上都会添加一个属性叫做 constructor,这个constructor指向当前的函数对象。
  • Person === Person.prototype.constructor
function Person{
  
}
console.log(Person)//[Function:Person]
console.log(Person.prototype.constructor); //[Function:Person]
console.log(p1.__proto__.constructor);//[Function:Person]
console.log(p1.__proto__.constructor);// Person

function Person(name,age){
	this.name = name;
  this.age = age;
}

对应内存图如下:

  • 创建出来的对象中的prototype属性指向了显示原型对象的constructor

image.png

function Person(name,age){
	this.name = name;
  this.age = age;
}
var p1 = new Person("why",18);
var p2 = new Person("kobe",30);

console.log(p1.name);
console.log(p2.name);
  • new出来的两个新的空对象的prototype属性指向了显示原型对象的constructor

image.png

function Person(name,age){
	this.name = name;
  this.age = age;
}
//新增方法
Person.prototype.running = function(){
	console.log("running");
}
var p1 = new Person("why",18);
var p2 = new Person("kobe",30);

p1.running();
p2.running();
  • 在Person的原型上添加了running函数,于是新开辟的running这块内存也指向了Person显式原型对象。+

image.png

function Person(name,age){
	this.name = name;
  this.age = age;
}
Person.prototype.running = function(){
	console.log("running");
}
var p1 = new Person("why",18);
var p2 = new Person("kobe",30);

console.log(p1.name);
console.log(p2.name);

p1.running();
p2.running();

//新增属性
Person.protptype.address = "中国"
p1.__proto__.info = "中国很美丽!"

p1.height = 1.88

p2.isAdmin = true;
  • 在原型上添加新的属性并赋值,Person显式原型对象的内存中同样也声明了新增的这些属性。
  • p1对象中新增了height属性,在p2对象中新增了isAdmin属性。

image.png

function Person(name,age){
	this.name = name;
  this.age = age;
}
Person.prototype.running = function(){
	console.log("running");
}
var p1 = new Person("why",18);
var p2 = new Person("kobe",30);

console.log(p1.name);
console.log(p2.name);

p1.running();
p2.running();

//新增属性
Person.protptype.address = "中国"
p1.__proto__.info = "中国很美丽!"

p1.height = 1.88

p2.isAdmin = true;

// 修改address
p1.address = "河北省"
console。log(p2.address)
  • Person显式原型对象中的 address属性并未被覆盖,而是被加到了p1对象里面。
  • p2.address打印的仍然是 Person显式原型对象中的 中国

image.png


	Person.prototype.message = "Hello Person";
  Person.prototype.info = {name:"沈七",age:30};

  Person.prototype.running = function(){};
  Person.prototype.eating = function(){};
      

我们也可以通过直接赋值一个新的原型对象来简写上面代码。

Person.prototype = {
  message:"Hello Person",
  info:{name:"沈七",age:30},
	running:function(){}
	eating:function(){}
	constructor:Person
}

总结梳理

隐式原型

  • JavaScript每一个对象都有[[prototype]]属性,它的属性值是对某个特定对象的引用。
  • 我们把这里的“某个特定对象”称为该实例对象的原型,也称之为隐式原型。
  • [[prototype]]属性是内部且是隐藏的,所以我们需要__proto__属性来操作[[prototype]]属性,因为__proto__属性存在于每一个对象当中且允许被访问。

显式原型

  • 对于每一个函数对象(非箭头函数)其都有prototype属性,被叫做显式原型。
  • prototype也会指向一个对象,这个对象的所有属性和方法都会被构造函数的实例所继承,这对象被成为原型对象。

两者之间的关系
每一个实例对象都通过__proto_指向它的构造函数的原型对象。

这是因为new操作符的底层实现决定的。

  • 当一个对象通过构造函数 new出实例后
  • 该对象prototype显式原型会通过__proto__赋值给[[prototype]]的隐式原型。
obj._proto_ = Person.prototype

注意[[prototype]]prototype是完全不同的两个属性.

原型对象

原型对象里面都有两个属性:__proto__constructor

  • constructor:用来记录实例对象是由哪个构造函数创建的,所以它默认指向创建它的构造函数。
  • __proto__:原型对象也是对象,所以它也有__proto__属性。

内存表现

function Person(){
  
}
var p1 = new Person();

image.png

  • Personprototype属性会指向它的显式原型,即Person函数的原型对象
  • Person函数的原型对象constructor属性会指向创建它的构造函数,即Person
  • new出来的实例对象p1的隐式原型__proto__会自动指向创建它的构造函数的显式原型,即Person函数的原型对象
console.log(p1._proto == Person.prototype)//true
  • 因为Person函数的原型对象本身也是一个对象,所以是由Object``new出来的。
  • 所以Person函数的原型对象的隐式原型__proto__属性指向Obejct的原型对象的显式原型。
console.log(Person.prototype.__proto__ == Obejct.prototype)//true
  • Object作为顶级父类,它的原型对象的隐式原型__proto__属性指向null
console.log(Object.prototype.__proto == null)// true

Person对象是由谁创建出来的呢?,它的隐式原型又指向谁?

我们来打印一下试试。

function Person(){

}
console.log(Person.__proto__)

image.png
我们发现Person.__proto__实际上是指向Function.prototety的。
也就是说:

  • Function是所有直接声明的函数的构造函数。
  • 所有直接声明的函数都是Function的一个实例对象。
  • 所有直接声明的函数的__proto__都指向同一个地方那就是Function.prototety.

我们可以进一步验证:

function Person(){

}
function foo(){

}
console.log(Person.__proto__==Function.prototype);// true
console.log(foo.__proto__==Function.prototype);   // true
console.log(foo.__proto__==Person.prototype);     // true

因为Function函数的原型对象本身也是一个对象,所以Function函数的原型对象的隐式原型__proto__也指向Obejct的显式原型.

console.log(Function.prototype.__proto__  == Object.prototype);// true

Obejct本身也是一个函数,所以它的隐式原型__proto__指向Function函数的原型对象的显式原型。

console.log(Object.__proto__ == Function.prototype);// true

Function的本身也是一个函数,所以它的隐式原型__proto__指向它自己的Function函数的原型对象的显式原型。

console.log(Function.__proto__  == Function.prototype);// true

于是就有了下面这张图。
image.png
小结:

  • p1Person的实例对象。
  • objObject的实例对象。
  • Function/Object/FOO都是Function的实例对象。
  • 原型对象默认创建时,隐式原型都是指向Object的显式原型的。(Object指向null)

又因为:
当A类的原型对象的隐式原型__proto__指向B类的显式对象时,我们称之为A类是继承于B类的。

if(A.prototype.__proto__ == B.prototype){
  A 继承于 B
}

推到出的结论:

  • ObjectPerson/Function的父类

FunctionObject的关系:

  • ObjectFunction 的父类。
  • FunctionObject的构造函数。

完结散花

ok以上就是对 JavaScript高级 |彻底搞懂原型对象 的全部讲解啦,很感谢你能看到这儿。如果有遗漏、错误或者有更加通俗易懂的讲解,欢迎小伙伴私信我,我后期再补充完善。

参考文献

coderwhy老师JS高级视频教程

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

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

相关文章

科技云报道:畅想无人化运维的AIOps,还有多远的路要走?

科技云报道原创。 在IT行业,运维人常常自我调侃“赚着5k的月薪,操着5千万的心,名下挂着5亿的资产”。 机房的暖通、网络、综合布线,系统的监控告警、故障响应等一大堆繁杂琐碎的工作,充斥着运维人的日常。 与开发和产…

自定义Feign的配置

SpringBoot虽然帮我们实现了自动装配,但是也是支持自定义配置的。 Feign运行自定义配置来覆盖默认配置,可以修改的默认配置如下: 配置Feign日志有两种方式 方式一:配置文件方式 1)全局生效 feign:client:config:defa…

【愚公系列】2022年12月 Elasticsearch数据库-ELK添加SQL插件和浏览器插件(二)

文章目录前言一、ELK添加SQL插件和浏览器插件1.配置插件2.浏览器插件3.Elasticsearch术语介绍4.测试SQL插件和浏览器插件前言 下载SQL插件地址:https://github.com/NLPchina/elasticsearch-sql 我们选择7.15.2版本,ES页选择7.15.2版本把最后面的下载链…

车间调度|基于遗传算法的柔性车间调度(Matlab代码实现)

目录 1 概述 2 遗传优化算法 3 车间调度 4 运行结果 5 参考文献 6 Matlab代码实现 1 概述 调度通过合理安排生产资源,以缩短生产时间和提高资源利用率为目的,在生产系统中扮演着重要的角色。作业车间调度问题(Job-shop Schedu-ling Problem,JSP)是一类经典…

1996-2020年全国31省农村电力和农田水利建设相关数据

1996-2020年全国31省农村电力和农田水利建设相关数据 1、1996-2020年 2、范围:31省 3、指标包括: 乡村办水电站、装机容量、发电量、农村用电量、有效灌溉面积、旱涝保收面积、机电排灌面积、实际耕地灌溉面积、新增耕地灌溉面积、节水灌溉面积、新增…

2023年第六届先进控制,自动化与机器人国际会议(ICACAR 2023)

2023年第五届先进控制,自动化与机器人国际会议(ICACAR 2023) 重要信息 会议网址:www.icacar.org 会议时间:2023年4月14-16日 召开地点:中国北京 截稿时间:2023年2月28日 录用通知&#xf…

排序子序列

1 题目来源: 牛客网:排序子序列 2 题目描述  牛牛定义排序子序列为一个数组中一段连续的子序列,并且这段子序列是非递增或者非递减排序。牛牛有一个长度为n的整数数组A,他现在有一个任务是把数组A分为若干段排序子序列,牛牛想知道他最少可以把这个数组分…

Ashampoo Burning Studio创建可启动磁盘

Ashampoo Burning Studio创建可启动磁盘 Ashampoo的产品通常适合质量,但在其中,它是世界上最好的软件之一,名为Ashampoo Burning Studio。与著名的Nero程序相比,该软件几乎一无是处,所有用于制作、写入和复制光盘的软件…

Python Tutorial——模块

如果你从Python解释器中退出,并且再次进入,你会发现你以前定义的函数和变量都已经丢失了。所以,如果你想写一个在某种程度上更长的程序,使用一个文本编辑器来准备解释器的输入会使情况有所好转,并且使用文件代替输入来…

最简单的方式实现Zotero文件同步+坚果云在多台电脑设备之间

应用场景: 放假回家,只带了笔记本搞科研的好童靴,发现实验室台式机的zotero中的PDF没办法在笔记本上读取。于是探索了一下午如何不重新在网页上保存下载台式机中的PDF,轻松获取异地的文献。 方式一: 氪金付费zotero…

参数估计与假设检验

推断统计:研究如何利用样本数据来推断总体特征 描述统计:描述一组数据的特征 参数估计:利用样本信息估计总体特征 假设检验:利用样本信息判断对总体的假设是否成立 一.参数估计 就是对于总体指标的估计 估计:根据…

免费l2接口有多少种类型?

免费l2接口是一个预先定义的函数,它的目的是让开发人员和开发人员无需访问源代码,也无需访问源代码,也无需理解其内部工作。免费l2接口有多少种类型? 有四种类型的股票l2接口: RPC:通过处理(或任务)共享的数据缓冲区…

SpringBoot整合RabbitMQ实现死信队列

文章目录概念介绍什么是死信死信队列应用工程搭建环境说明搭建步骤实现死信准备Exchange&Queue监听死信队列方式一——消费者拒绝&否认方式二——超过消息TTL方式三——超过队列长度限制代码仓库前面一文通过 Java整合RabbitMQ实现生产消费(7种通讯方式&…

Spark的运行模式介绍

Spark的运行模式 本地模式(Local) 一般用做测试,测试代码的逻辑是否正确 本地模式,只启动一个Driver进程,没有Executor进程的,所有Task都运行在Driver进程中 集群模式 (Cluster) 一…

医疗挂号网站

开发工具(eclipse/idea/vscode等): 数据库(sqlite/mysql/sqlserver等): 功能模块(请用文字描述,至少200字): 管理员功能: 1、管理挂号须知、帮助信息 2、增删改查资讯类型、健康资讯信息 3、增删改查医生职称信息、医生…

装载问题 ——回溯法(Java)

装载问题 ——回溯法(Java) 文章目录装载问题 ——回溯法(Java)1、 问题描述1.1 装载问题1.2 转换问题2、算法设计2.1 可行性约束函数2.2 上界函数2.3 解空间树2.4 剪枝函数2.5 算法设计3、程序代码4、参考资料1、 问题描述 有一…

Hadoop安装准备

虚拟机的安装 配置了静态IP地址(192.168.1.100) 关闭与禁用了防火墙 安装了vim编辑器 虚拟机克隆 克隆出master虚拟机 以同样的步骤克隆出slave1和slave2 虚拟机配置 配置master虚拟机 启动虚拟机 设置主机名 命令:hostname…

C#语言和面向对象OOP

1、【重点面试题】面向对象的三大特性 封装 :隐藏对象的属性,并实现细节(方法),对外提供接口, public全局,protected子类,internal同集,隐藏private 同类,pub…

英文文章写作|文献管理|​​​​​​​阅读文献|引用文献|国内文章

目录 英文文章写作 1.阅读10篇文献,总结100个常用句型和常用短语 2.找3-5篇技术路线和统计方法与你的课题接近的文章,精读 3.针对论文的每一部分,尤其是某种具体方法、要讨论的某一具体方面,各找5-8 篇文献阅读,充…

使用HTMLTestRunner.py生成测试报告

1、如何收集测试结果? 使用第三方封装好类HTMLTestRunner.py生成HTML测试报告 # encoding:utf-8 import unittest import time from HTMLTestRunner import HTMLTestRunner class MyTestCase(unittest.TestCase): # 每条用例初始化 def setUp(s…