js原型以及原型链

news2024/9/26 1:26:19

目录

  • 原型
  • 隐式原型
  • 显式原型
  • constructor
    • new操作符
  • 重写原型对象
  • 原型链
  • 继承
    • 原型链继承
    • 借用构造函数继承
      • 组合构造继承
    • 原型继承
    • 寄生继承
      • 组合寄生继承
  • 原型继承关系

原型

JavaScript中,每个对象都有一个内置属性[[prototype]],这个属性指向一个另一个对象
当我们访问对象中的属性时,会触发[[GET]]操作
这个操作会现在自己对象内部寻找对应的值,如果找不到就会在[[prototype]]中所指向的对象中寻找
可以通过__proto__Object.getPrototypeOf两个属性来访问这个对象
可以通过__proto__Object.setPrototypeOf两个属性来设置这个对象
注意,__proto__是早期浏览器自行添加的属性,而Object.getPrototypeOfObject.setPrototypeOf标准添加
如下代码所示

        var obj = {

        }
        console.log(obj.__proto__)
        console.log(Object.getPrototypeOf(obj))
        console.log(obj.__proto__ === Object.getPrototypeOf(obj))
        var obj2 = {

        }
        var obj3 = {
            a: 1
        }
        obj2.__proto__ = obj3
        console.log(obj2.__proto__)
        console.log(obj2.a)
        Object.setPrototypeOf(obj2, obj)
        console.log(obj2.__proto__)

控制台结果如下
结果

隐式原型

每个对象都会有一个__proto__属性,这个属性不建议直接访问或修改,是只在JavaScript内部使用的属性,因此被称之为隐式原型

显式原型

函数也是一个特殊的对象,是对象也就意味着也拥有隐式原型
但与普通对象不同的是,函数同时也拥有显式原型
隐式原型不同的是,显式原型可以直接访问,并且经常使用
显示原型的作用就是用来构造对象
函数的显式原型可以通过prototype属性来访问
如下代码

        function foo() {

        }
        var obj = {

        }
        console.log(foo.prototype)
        console.log(obj.prototype)

控制台结果如下
结果

constructor

在说明显式原型的用处之前需要先知道一个函数constructor
constructor在函数的显式原型
constructor也被称之为构造函数
这个constructor指向函数本身

        function foo() {

        }
        console.log(foo.prototype.constructor)
        console.log(foo === foo.prototype.constructor)

控制台结果
结果

new操作符

在之前的this绑定规则一文中new关键字做了以下操作

  1. 创建一个空对象
  2. 空对象this绑定到这个空对象
  3. 执行函数体里的代码

其实还有第四步
即将函数的显式原型赋值到空对象中的隐式原型
这意味着如果我们通过某一个函数来构建一个对象,这个对象的隐式原型指向的是函数的显式原型

        function Person() {

        }
        var obj = new Person()
        console.log(obj.__proto__)
        console.log(obj.__proto__ === Person.prototype)

控制台结果如下
结果
我们说new关键字会执行函数体里的代码,这句话不能说错
但更精确的说法是new关键字会执行显式原型中constructor函数里的代码

重写原型对象

如果我们需要在显式原型上添加许多属性,通常我们会重写整个显式原型

        function Person() {

        }
        Person.prototype = {
            a: 1,
            b: 2,
            foo: function () {
                console.log(this.a)
            }
        }
        var obj = new Person()
        console.log(obj.b)
        obj.foo()
        console.log(Person.prototype.constructor)

控制台结果如下
结果
可以看到,如果我们重写显式原型的话constructor会指向Object

        Person.prototype.constructor = Person

我们可以通过这种方式来修改Personconstructor,但这样修改得到的constructor[[Enumerable]]被设置成了true
默认情况下的constructor[[Enumerable]]false
如果想要解决这个问题,可以通过Object.defineProperty函数

        Object.defineProperty(Person.prototype, "constructor", {
            enumerable: false,
            value: Person
        })

这样得到的constructor就是不可枚举的了
关于对象的属性描述符可以看我这篇文章
(未动笔,未来可寄)

原型链

JavaScript中,如果要实现继承,就必须要理解一个重要概念,即原型链
当我们从一个对象获取一个属性时,如果在当前对象中没有获取到对应的值时就会通过对象的隐式原型来寻找
如果也没有找到的话就会一直向上寻找
所有对象的顶层原型为 [Object: null prototype] {}
所有通过Object创建出来的对象其隐式原型都指向这个
这个原型其实也有对应的隐式原型,但指向的是null
综上所述,在JavaScript中所有类的父类是Object
原型链的顶层对象就是Object隐式原型
在理解了原型链之后我们就能实现继承

继承

以下是几种继承的实现方式

原型链继承

原型链继承是通过JavaScript对象属性查找规则实现的一种继承方式

        function Person() {
            this.age = 18
        }
        var p = new Person()
        function Student() {
            this.id = "101"
        }
        Student.prototype = p
        var stu = new Student()
        console.log(stu.age)
        console.log(stu.id)

控制台结果如下
结果
这个方法需要构造一个父类的实例对象,再将子类显式原型指向父类构造的实例对象子类构造实例对象时生成的对象其隐式原型就指向了父类构造的实例对象
这个方法也有自己的缺点

  1. 某些属性其实是存储在父类的实例对象上的,直接打印子类的实例对象是看不到这些属性的
  2. 这个属性会被多个对象共享
  3. 这个属性的值是唯一的

借用构造函数继承

借用构造函数继承的关键就在于子类中直接调用父类的构造函数

        function Person() {
            this.age = 18
        }
        function Student() {
            Person.call(this)
            this.id = "101"
        }
        var stu = new Student()
        console.log(stu)

控制台结果如下

结果
可以看到此时父类的属性也已经继承过来了
但这只是属性的继承,如果想要调用父类的方法的话还需要和原型链继承一起使用

组合构造继承

        function Person() {
            this.age = 18
        }
        Person.prototype.foo = function () {
            console.log(this.age)
        }
        var p = new Person()
        function Student() {
            Person.call(this)
            this.id = "101"
        }
        Student.prototype = p
        var stu = new Student()
        console.log(stu.age)
        console.log(stu.id)
        stu.foo()

控制台结果如下

结果

这样我们就实现了属性和方法的一起继承
这种方法其实也有一些问题

  1. 这个方法会调用两次构造函数
    1. 一次在生成子类实例对象时调用了父类的构造函数
    2. 一次在创建子类原型的时候
  2. 所有的子类实例对象会拥有两份父类属性
    一份在自己这里,一份在自己的隐式原型中
    默认访问时优先访问自己本身有的属性

原型继承

在2006年时道格拉斯·克罗克福德提出了一种新的继承方式
这种方法并不依靠constructor来实现

        function Person() {
            this.age = 18
        }
        Person.prototype.foo = function () {
            console.log("this function")
        }
        function Student() {
            this.id = "101"
        }
        var obj = {}
        Object.setPrototypeOf(obj, Person.prototype)
        Student.prototype = obj
        var newObj = new Student()

我们使用借用构造函数继承的目的就是要一个新对象新对象隐式原型指向父类的显式原型,最后子类显式原型再指向这个新对象
通过Object.setprototypeOf方法来设置新的obj对象的隐式原型指向父类显式原型子类显式原型指向obj
这样就绕过了constructor
还有其他几种实现方法

        function Person() {
            this.age = 18
        }
        Person.prototype.foo = function () {
            console.log("this function")
        }
        function Student() {
            this.id = "101"
        }
        var obj = {}
        function F() { }
        F.prototype = Person.prototype
        Student.prototype = new F()
        var newObj = new Student()

定义一个新函数,使新函数的显式原型直接指向父类的显式原型
在构造这个新函数的对象时实际上是构造了一个指向父类的空的新对象
再将子类的显式原型指向这个新对象
这也是道格拉斯·克罗克福德提出来的方法

        function Person() {
            this.age = 18
        }
        Person.prototype.foo = function () {
            console.log("this function")
        }
        function Student() {
            this.id = "101"
        }
        var obj = Object.create(Person.prototype)
        Student.prototype = obj
        var newObj = new Student()

这里使用了Object.create方法,这个方法会创建一个空对象并将这个空对象的隐式原型指向你传入的对象
可能存在一些兼容性问题

寄生继承

最后我们将原型继承封装成一个函数

        function inherit(Subtype, Supertype) {
            function F() { }
            F.prototype = Supertype.prototype
            var obj = new F()
            Subtype.prototype = obj
            Object.defineProperty(Subtype.prototype, "constructor", {
                enumerable: false,
                value: Subtype
            })
        }

这个inherit就是寄生继承的实现方法
这种方法同样由道格拉斯·克罗克福德提出

组合寄生继承

此时寄生继承已经能解决原型继承借用构造函数继承中的绝大部分问题,剩下的一个最大的问题就是还需要继承父类中的属性
为了解决这个问题我们需要综合上面所有的方法来得到最终的解决方案

        function inherit(Subtype, Supertype) {
            function F() { }
            F.prototype = Supertype.prototype
            var obj = new F()
            Subtype.prototype = obj
            Object.defineProperty(Subtype.prototype, "constructor", {
                enumerable: false,
                value: Subtype
            })
        }

        function Person() {
            this.age = 18
        }
        Person.prototype.foo = function () {
            console.log("this function")
        }
        function Student() {
            Person.call(this)
            this.id = "101"
        }
        inherit(Student, Person)
        var newObj = new Student()
        console.log(newObj.id)
        console.log(newObj.age)
        newObj.foo()

控制台结果如下
结果
这也是目前在ES6以前使用最多的继承解决方案

原型继承关系

最后我们再来梳理一下在JavaScript中的原型继承关系
Object是所有类的父类
Object的显式原型的隐式原型指向null

1

因为Object也是一个函数,也同样拥有隐式原型,它的隐式原型指向Function的显式原型
Function隐式原型同样指向自己的显式原型
因为Function显式原型是通过new Object创建出来的,所以它的隐式原型指向Object的显式原型

2

我们创建的函数的显式原型指向函数自己的显式原型
我们的函数本质上是通过new Function创建出来的
所以函数的隐式原型则指向Function的显式原型

3

我们通过foo创建出来的对象隐式原型指向foo的显式原型
通过new Object创建出来的对象隐式原型指向Object的显式原型

4

以上就是在JavaScript中的原型继承关系图
最后附带一张更加形象的示例图

最后

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

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

相关文章

Nginx实现反向代理和负载均衡

Nginx安装 本文章主要介绍下,如何使用Nginx来实现反向代理和负载均衡,Nginx安装和基础知识,可参考我的这篇文章 Nginx安装。 Nginx实现反向代理 实现反向代理需要准备两台Nginx服务器。一台Nginx服务器A,ip为 192.168.206.140&…

Linux下查找python路径

本地目前装了几个版本的python,这里记录下查找python路径的方法。 1:whereis命令 whereis python2:which命令 which python与whereis相似,但which会返回第一个找到的执行文件的位置。 3:find命令 find命令可以搜索系…

Power BI-云端报表定时刷新--ODBC、MySQL、Oracle等其他本地数据源的刷新(二)

ODBC数据源 一些小众的数据源无法直接连接,需要通过微软系统自带的应用“ODBC数据源”连接。 1.首次使用应安装对应数据库的ODBC驱动程序,Mysql的ODBC驱动需要手动安装 2.在web服务中进行数据源的配置 Mysql数据源 1.Powerbi与Gateway第一次连SQL…

❤️创意网页:创意视觉效果粒子循环的网页动画

✨博主:命运之光 🌸专栏:Python星辰秘典 🐳专栏:web开发(简单好用又好看) ❤️专栏:Java经典程序设计 ☀️博主的其他文章:点击进入博主的主页 前言:欢迎踏入…

【雕爷学编程】MicroPython动手做(15)——掌控板之AB按键2

知识点:什么是掌控板? 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片,支持WiFi和蓝牙双模通信,可作为物联网节点,实现物联网应用。同时掌控板上集成了OLED…

【嵌入式Linux项目】基于Linux的全志H616开发板智能家居项目(语音控制、人脸识别、安卓APP和PC端QT客户端远程操控)有视频功能展示

目录 一、功能需求 二、开发环境 1、硬件: 2、软件: 3、引脚分配: 三、关键点 1、设计模式之工厂模式 2、wiringPi库下的相关硬件操作函数调用 3、语音模块的串口通信 4、线程 5、摄像头的实时监控和拍照功能 6、人脸识别 四、编…

❤️创意网页:炫酷的网页 - 创造华丽粒子动画

✨博主:命运之光 🌸专栏:Python星辰秘典 🐳专栏:web开发(简单好用又好看) ❤️专栏:Java经典程序设计 ☀️博主的其他文章:点击进入博主的主页 前言:欢迎踏入…

操作系统攻击:早期WindowsMS10-046漏洞

目录 概述 漏洞成因 利用过程 漏洞复现 漏洞修复 概述 本次介绍早期的windows高危漏洞——MS10_046_SHORTCUT_ICON_DLLLOADER , 该漏洞可以通过浏览器跳转网络资源的方式利用,结合xss攻击用户的系统 危险性极高。 漏洞成因 漏洞成因: m…

param.grad、requires_grad、grad_fn、grad/梯度为None?

基本概念 1)is_leaf 叶子节点和非叶子节点的区别:计算图中的节点分为叶子节点和非叶子节点,叶子节点可以理解成没有其他tensor再利用它进行计算(例如b a1,那么b需要a进行计算,那么a就不是叶子结点&…

服务器介绍

本文章转载与b战up主谈三国圈,仅用于学习讨论,如有侵权,请联系博主 机架型服务器 堆出同时服务百万人次机组 刀型服务器 服务器炸了 比如用户访问量暴增 超过机组的峰值处理能力,进而导致卡顿或炸服, 适合企业的塔式…

xilinx FPGA 除法器ip核(divider)的使用(VHDLVivado)

一、创建除法ip核 vivado的除法器ip核有三种类型,跟ISE相比多了一个LuMult类型,总结来说就是 LuMult:使用了DSP切片、块RAM和少量的FPGA逻辑原语(寄存器和lut),所以和Radix2相比占用fpga资源更少&#xff…

CS_SAVEBITS 这个样式有什么作用?

简单来说,如果你在创建窗口的时候在窗口类中指定了 CS_SAVEBITS 标志,则窗口管理器会尝试保存此窗口所遮盖的区域的位图数据。 但是,这里比较关键的问题是:为什么要这样做?只有明白了这其中的原理,你才会在…

2023.07.13力扣6题

931. 下降路径最小和 给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径 的 最小和 。 下降路径可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位…

Python批量将Excel内指定列的数据向上移动一行

本文介绍基于Python语言,针对一个文件夹下大量的Excel表格文件,对其中的每一个文件加以操作——将其中指定的若干列的数据部分都向上移动一行,并将所有操作完毕的Excel表格文件中的数据加以合并,生成一个新的Excel文件的方法。 首…

走进Linux世界【七、Linux网络及快照和克隆】

Linux系统学习 走进Linux世界【一、Linux概述】 走进Linux世界【二、VM与Linux安装】 走进Linux世界【三、Linux文件与路径】 走进Linux世界【四、Linux基本命令一】 走进Linux世界【五、Linux基本命令二】 走进Linux世界【六、Linux编辑器vim】 走进Linux世界【七、Lin…

Nest grpc 实践之调用 python ddddocr 库

我曾经写过一个项目 ddddocr_server,使用 fastapi 提供 http 接口,以此来调用 ddddocr 库。 其他语言想要调用的话,则是通过 http 协议的方式来调用。然而 http 协议的开销不小,而 Websocket 调用又不灵活,此时针对这…

【计网】TCP在可靠传输中都干了啥

文章目录 1、概述2、校验和3、序列号和确认应答机制4、重传机制4.1、介绍4.2、超时重传4.3、快速重传 5、滑动窗口协议5.1、介绍5.2、发送方的滑动窗口5.3、接收方的滑动窗口 6、流量控制7、拥塞控制7.1、介绍7.2、慢开始7.3、拥塞避免7.4、快重传和快恢复 1、概述 TCP 是面向…

Day45 算法记录| 动态规划 12

股票问题 309. 买卖股票的最佳时机含冷冻期714.买卖股票的最佳时机含手续费 309. 买卖股票的最佳时机含冷冻期 这个视频讲解的很好 309.最佳买卖股票时机含冷冻期 class Solution {public int maxProfit(int[] prices) {int day prices.length;int [][] dp new int[day][2…

【雕爷学编程】MicroPython动手做(14)——掌控板之OLED屏幕

知识点:什么是掌控板? 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片,支持WiFi和蓝牙双模通信,可作为物联网节点,实现物联网应用。同时掌控板上集成了OLED…

IOS UICollectionView 设置cell大小不生效问题

代码设置flowLayout.itemSize 单元格并没有改变布局大小, 解决办法如下图:把View flow layout 的estimate size 设置为None,上面设置的itemSize 生效了。