JavaScript高级 ES5 面向对象原型继承

news2024/11/20 8:39:57

原型以及ES5中实现继承

    • 1. 对象和函数的原型
      • 1. 普通对象的原型 [[prototype]]
      • 2. 函数的原型 prototype
    • 2. new、constructor
      • 1. new 操作符
      • 2. constructor属性
      • 3. 将方法放到原型上
      • 4. 创建对象的内存表现
      • 5. 重写原型对象
    • 3. 原型链的查找顺序
    • 4. 原型链实现的继承
    • 5. 借用构造函数继承
    • 6. 寄生组合实现继承

1. 对象和函数的原型

1. 普通对象的原型 [[prototype]]

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

在这里插入图片描述

当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作,这个操作会首先检查该对象是否有对应的属性,如果有的话就使用它,如果对象中没有该属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性

获取原型的方式

通过对象的 __proto__ 属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题)

通过 Object.getPrototypeOf 方法可以获取到

    var obj = {
      name: "why",
      age: 18
    }
    console.log(obj)

    var info = {}

    // 获取对象的原型
    console.log(obj.name, obj.age) // 在这里我们可以直接获取对象自身的属性
    console.log(obj.__proto__) // 不推荐使用
    console.log(Object.getPrototypeOf(obj)) // 标准使用
    console.log(obj.__proto__ === Object.getPrototypeOf(obj)) // true 因为他们指向的是同一个对象

    // 疑问: 这个对象的原型有什么用呢?
    // 当我们通过[[get]]方式获取一个属性对应的value时
    // 1> 它会优先在自己的对象中查找, 如果找到直接返回
    // 2> 如果没有找到, 那么会在原型对象中查找
    obj.__proto__.message = "Hello World"
    console.log(obj.message) // Hello World

在这里插入图片描述

但是不管是通过字面量直接创建一个对象还是使用构造函数,只要是对象都会有这样的一个内置属性

2. 函数的原型 prototype

所有的函数都有一个prototype的属性(注意:不是 __proto__ ),但是对象是没有prototype属性的

因为它是一个函数,才有了这个特殊的属性,而不是它是一个对象,所以有这个特殊的属性

    var obj = {}
    function foo() {}

    // 1.将函数看成是一个普通的对象时, 它是具备__proto__(隐式原型)
    console.log(obj.__proto__) // 获取的是对象的原型
    console.log(foo.__proto__) // ƒ () { [native code] }
    // foo这个函数本身是Function的一个实例对象
    console.log(foo.__proto__ === Function.prototype); //true
    //  Function是由其本身自己构造出来的。所有函数都是大写Function的实例
    console.log(Function.__proto__ === Function.prototype); //true


    // 2.将函数看成是一个函数时, 它是具备prototype(显式原型)
    // 作用: 用来构建对象时, 给对象设置隐式原型的
    console.log(foo.prototype)
    // console.log(obj.prototype) 需要注意的是 对象是没有prototype 的!!!

2. new、constructor

1. new 操作符

new 关键字的步骤

  1. 在内存中创建一个新的对象(空对象)
  2. 将空对象赋值给 this
  3. 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
  4. 执行函数体的代码
  5. 将这个对象默认返回
    function Foo() {
      // 1.创建空的对象
      // 2.将Foo的prototype原型(显式隐式)赋值给空的对象的__proto__(隐式原型)
    }

    console.log(Foo.prototype)

    var f1 = new Foo()
    var f2 = new Foo()
    var f3 = new Foo()
    var f4 = new Foo()
    var f5 = new Foo()
    console.log(f1.__proto__)
    console.log(f1.__proto__ === Foo.prototype) // true
    console.log(f3.__proto__ === f5.__proto__) // true

在这里插入图片描述

那么也就意味着我们通过构造函数创建出来的所有对象的[[prototype]]属性都指向Foo.prototype

2. constructor属性

原型对象上面是有一个constructor属性的, 默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象

    // 非常重要的属性: constructor, 指向Person函数对象
    function Person() {

    }

    // 1.对constructor在prototype上的验证
    var PersonPrototype = Person.prototype
    console.log(PersonPrototype) 
    console.log(PersonPrototype.constructor) // ƒ Person() {}
    console.log(PersonPrototype.constructor === Person)

    console.log(Person.name) // Person
    console.log(PersonPrototype.constructor.name) // Person

    // 2.实例对象p
    var p = new Person()
    console.log(p.__proto__.constructor) // ƒ Person() {}
    console.log(p.__proto__.constructor.name) // Person

3. 将方法放到原型上

当我们多个对象拥有共同的值时, 我们可以将它放到构造函数对象的显式原型,由构造函数创建出来的所有对象, 都会共享这些属性

 /*
    1.什么是函数的显式原型
      * 区分和对象原型区别
    2.函数的原型的作用
      * 在通过new操作创建对象时, 将这个显式原型赋值给创建出来对象的隐式原型
    3.案例Person, 将所有的函数定义放到了显式原型上
    */

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

      // 1.方式一: 编写函数, 会创建很多个函数对象
      // 将方法放到原型上,可以避免内存的浪费
      // this.running = function() {
      //   console.log(this.name + " running")
      // }
      // this.eating = function() {
      //   console.log(this.name + " eating")
      // }
      // this.studying = function() {
      //   console.log(this.name + " studying")
      // }
    }

    // 当我们多个对象拥有共同的值时, 我们可以将它放到构造函数对象的显式原型
    // 由构造函数创建出来的所有对象, 都会共享这些属性
    Student.prototype.running = function() {
      console.log(this.name + " running")
    }
    Student.prototype.eating = function() {
      console.log(this.name + " eating")
    }

    // 1.创建三个学生
    var stu1 = new Student("ximingx", 18, 111)
    var stu2 = new Student("kobe", 30, 112)
    var stu3 = new Student("james", 18, 111)

    // 隐式原型的作用
    // 1> stu1的隐式原型是谁? Student.prototype对象
    // 2> stu1.running查找:
    //  * 先在自己身上查找, 没有找到
    //  * 去原型去查找
    stu1.running()
    stu2.eating()

4. 创建对象的内存表现

    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()

在这里插入图片描述

5. 重写原型对象

如果我们需要在原型上添加过多的属性,通常我们会重写整个原型对象

每创建一个函数, 就会同时创建它的prototype对象, 这个对象也会自动获取constructor属性

重写整个原型对象相当于给prototype重新赋值了一个对象, 那么这个新对象的constructor属性, 会指向Object构造函数, 而不是Person构造函数了

    function Person() {

    }

    console.log(Person.prototype)
    
    // 在原有的原型对象上添加新的属性
    // Person.prototype.message = "Hello Person"
    // Person.prototype.info = { name: "哈哈哈", age: 30 }
    // Person.prototype.running = function() {}
    // Person.prototype.eating = function() {}

    // console.log(Person.prototype)
    // console.log(Object.keys(Person.prototype))

    // 直接赋值一个新的原型对象
    Person.prototype = {
      message: "Hello Person",
      info: { name: "哈哈哈", age: 30 },
      running: function() {},
      eating: function() {},
      // constructor: Person
    }
    
    Object.defineProperty(Person.prototype, "constructor", {
      enumerable: false,
      configurable: true,
      writable: true,
      value: Person
    })

    console.log(Object.keys(Person.prototype))

在这里插入图片描述

如果希望constructor指向Person,那么可以手动添加, 上面的方式虽然可以, 但是也会造成constructor的[[Enumerable]]特性被设置了true,默认情况下, 原生的constructor属性是不可枚举的,如果希望解决这个问题, 就可以使用我们前面介绍的Object.defineProperty()函数

3. 原型链的查找顺序

我们知道,从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取

[Object: null prototype] {} 原型属性已经指向的是null,也就是已经是顶层原型

    // 1.{}的本质
    // var info = {}
    // 相当于
    // var info = new Object()
    // console.log(info.__proto__ === Object.prototype)

    // 2.原型链
    var obj = {
      name: "why",
      age: 18
    }

    // 查找顺序
    // 1.obj上面查找
    // 2.obj.__proto__上面查找
    // 3.obj.__proto__.__proto__ -> null 上面查找(undefined)
    // console.log(obj.message)


    // 3.对现有代码进行改造
    obj.__proto__ = {
      // message: "Hello aaa"
    }

    obj.__proto__.__proto__ = {
      message: "Hello bbbb"
    }

    obj.__proto__.__proto__.__proto__ = {
      message: "Hello ccc"
    }

    console.log(obj.message)

我们可以得出一个结论:原型链最顶层的原型对象就是Object的原型对象

4. 原型链实现的继承

    // 定义Person构造函数(类)
    function Person(name, age, height, address) {
      this.name = name
      this.age = age
      this.height = height
      this.address = address
    }
    Person.prototype.running = function() {
      console.log("running~")
    }
    Person.prototype.eating = function() {
      console.log("eating~")
    }
    // 定义学生类
    function Student(name, age, height, address, sno, score) {
      this.name = name
      this.age = age
      this.height = height
      this.address = address

      this.sno = sno
      this.score = score
    }
    // 创建一个父类的实例对象(new Person()), 用这个实例对象来作为子类的原型对象
    var p = new Person("why", 18)
    Student.prototype = p

    Student.prototype.studying = function() {
      console.log("studying~")
    }

在这里插入图片描述

原型链继承的弊端

第一,我们通过直接打印对象是看不到这个属性的

第二,这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题

第三,不能给Person传递参数(让每个stu有自己的属性),因为这个对象是一次性创建的(没办法定制化)

5. 借用构造函数继承

借用继承的做法非常简单:在子类型构造函数的内部调用父类型构造函数

因为函数可以在任意的时刻被调用,因此通过apply()和call()方法也可以在新创建的对象上执行构造函数

    // 定义Person构造函数(类)
    function Person(name, age, height, address) {
      this.name = name
      this.age = age
      this.height = height
      this.address = address
    }

    Person.prototype.running = function() {
      console.log("running~")
    }
    Person.prototype.eating = function() {
      console.log("eating~")
    }

    // 定义学生类
    function Student(name, age, height, address, sno, score) {
      // 重点: 借用构造函数
      Person.call(this, name, age, height, address)
      // this.name = name
      // this.age = age
      // this.height = height
      // this.address = address

      this.sno = sno
      this.score = score
    }
    
    var p = new Person("why", 18)
    Student.prototype = p

组合继承最大的问题就是无论在什么情况下,都会调用两次父类构造函数

除此之外所有的子类实例事实上会拥有两份父类的属性, 一份在当前的实例自己里面(也就是person本身的),另一份在子类对应的原型对象中(也就是person.__proto__里面)

	function Person(name, age, height) {}
    function Student() {}

    inherit(Student, Person)
    
    // 1.之前的做法: 但是不想要这种做法
    // var p = new Person()
    // Student.prototype = p

    // 2.方案一:
    var obj = {}
    // obj.__proto__ = Person.prototype
    Object.setPrototypeOf(obj, Person.prototype)
    Student.prototype = Person.prototype

    // 3.方案二:
    function F() {}
    F.prototype = Person.prototype
    Student.prototype = new F()

    // 4.方案三:
    var obj = Object.create(Person.prototype)
    console.log(obj.__proto__ === Person.prototype)
    Student.prototype = obj

6. 寄生组合实现继承

创建一个封装继承过程的函数, 该函数在内部以某种方式来增强对象,最后再将这个对象返回

    // 创建对象的过程
    function createObject(o) {
      function F() {}
      F.prototype = o
      return new F()
    }
    // 寄生式函数
    function inherit(Subtype, Supertype) {
      Subtype.prototype = createObject(Supertype.prototype)
      Object.defineProperty(Subtype.prototype, "constructor", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: Subtype
      })
    }
  
    // 寄生组合式继承
    // 原型链/借用/原型式(对象之间)/寄生式函数
    function Person(name, age, height) {
      this.name = name
      this.age = age
      this.height = height
    }

    Person.prototype.running = function() {
      console.log("running~")
    }
    Person.prototype.eating = function() {
      console.log("eating~")
    }

    function Student(name, age, height, sno, score) {
      Person.call(this, name, age, height)
      this.sno = sno
      this.score = score
    }
    inherit(Student, Person)
    
    Student.prototype.studying = function() {
      console.log("studying")
    }

    // 创建实例对象
    var stu1 = new Student("why", 18, 1.88, 111, 100)

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

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

相关文章

深入URP之Shader篇10: 深度值专题(1)

之前研究Unlit shader的时候就遇到一些Z值相关的问题,一笔带过了,比如ComputeFogFactor中的UNITY_Z_0_FAR_FROM_CLIPSPACE。今天就把URP Shader中出现的Z相关的问题做一个专题一起研究下。 深度缓冲的方向和UNITY_REVERSED_Z 先说这个关于z的宏&#x…

nacos:服务注册与发现

导入SpringCloudAlibaba相关的依赖&#xff0c;并在父工程将依赖进行管理 <dependencyManagement> <dependencies> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-s…

Java EE|多线程代码实例之定时器与线程池

文章目录&#x1f534;定时器什么是定时器以及开发中的作用标准库中的定时器定时器的实现&#x1f534;线程池什么是线程池标准库中的线程池创建一个线程池ThreadPoolExecutor构造方法解析线程池的实现&#x1f534;定时器 什么是定时器以及开发中的作用 程序中的定时器功能与…

【互联网大厂机试真题 - 华为】九宫格

题目描述 九宫格是一款广为流传的游戏,起源于河图洛书。游戏规则是:1到9九个数字放在3x3的格子中,要求每行、每列以及两个对角线上的三数之和都等于15. 在金麻名著《射雕英雄传》中黃蓉曾给九宫格的一种解法,口诀:戴九恩一,左三右七,二四有肩,八六为足,五居中央。解法…

【云原生进阶之容器】第四章Operator原理4.3节--Operator模式

1 Operator概述 1.1 诞生背景 Kubernetes实际是期望状态管理器。先在Kubernetes中指定应用程序期望状态(实例数,磁盘空间,镜像等),然后它会尝试把应用维持在这种状态。Kubernetes的控制平面运行在Master节点上,它包含数个controller以调和应用达到期望状态: 检查当前的…

【阶段三】Python机器学习30篇:机器学习项目实战:智能推荐系统的基本原理与计算相似度的常用方法

本篇的思维导图: 智能推荐系统模型 智能推荐系统属于非监督式学习,是机器学习一个非常重要的应用领域,它能带来的经济价值往往是直接且非常可观的。 智能推荐系统的基本原理 智能推荐系统的应用场景 互联网每天都在产生海量信息,用户行为数据呈现爆发式增长…

PyTorch - 常见神经网络

文章目录LeNetAlexNetDropoutAlexNet 网络结构torchvision中的AlexNet的实现ZFNetVGG-NetsVGG 各网络VGG-16 网络结构GoogLeNet代码实现ResNetDenseNetRNNLSTMGRULeNet 1998年&#xff0c;由 LeCun 提出用于手写数字识别任务只有5层结构&#xff1b;目前看来不输入深度学习网络…

智能售货机系统帝可得

智能售货机 概述项目使用springcloudalibaba中提供的短信服务图形验证码生成多端登录/网关统一鉴权对象存储服务代码的自动填充微服务集成emq&#xff0c;发送emq工单业务流 接收工单 拒绝工单 运维工单补货工单使用xxl-job进行任务调度lkd集成xxl-job自动创建维修工单自动…

设计模式_结构型模式 -《代理模式》

设计模式_结构型模式 -《代理模式》 笔记整理自 黑马程序员Java设计模式详解&#xff0c; 23种Java设计模式&#xff08;图解框架源码分析实战&#xff09; 结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式&#xff0c;前者采用继承…

Docker 架构和安装

Docker 包括三个基本概念: 镜像&#xff08;Image&#xff09;&#xff1a;Docker 镜像&#xff08;Image&#xff09;&#xff0c;就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。 容器&#xff08;Con…

<Linux> Linux项目自动化构建工具—makemakefile的使用

< Linux> Linux 项目自动化构建工具—make/makefile的使用 文章目录< Linux> Linux 项目自动化构建工具—make/makefile的使用一、make/makefile的背景二、如何编写 makefile1.依赖关系和依赖方法2.makefile的使用3.clean的使用4.多文件编译5.伪目标 .PHONY6.三个时…

无约束优化——线性搜索法

无约束优化——线性搜索法&#xff08;line search&#xff09;前言概述构建关于步长的函数Wolfe条件Strong Wolfe条件Zoutendijk条件后记前言 该系列为学习笔记系列&#xff0c;所有内容可以在 Numerical Optimization (2nd Edition) 中找到&#xff0c;该书十分有用经典建议…

组件封装 - Tabs组件

首先我们先来看一下 Element UI 里面的 Tabs 是怎么样的 <template><el-tabs v-model"activeName" class"demo-tabs" tab-click"handleClick"><el-tab-pane label"User" name"first">User</el-tab-pa…

基于matlab的汽车牌照识别程序详细教程

设计一个基于matlab的汽车牌照识别程序&#xff0c;能够实现车牌图像预处理&#xff0c;车牌定位&#xff0c;字符分割&#xff0c;然后通过神经网络对车牌进行字符识别&#xff0c;最终从一幅图像中提取车牌中的字母和数字&#xff0c;给出文本形式的车牌号码。关键词&#xf…

C语言 一个特殊的数组【柔性数组】

文章目录前言柔性数组的特点柔性数组的使用柔性数组的优势写在最后前言 也许你从来就没有听过柔性数组&#xff08;flexible array&#xff09;这个概念&#xff0c;但他是真的存在&#xff1b;柔性数组的概念存在于C99标准当中&#xff0c;C99标准表示&#xff1a;结构体的最后…

Linux-进程概念

目录 进程状态&#xff1a; 操作系统简图 调用进程bin.exe的详细过程 cpu运行队列的结构 R进程和阻塞进程 进程状态&#xff1a;挂起&#xff1a; Linux操作&#xff1a; ​编辑 R运行状态 S休眠状态 T暂停状态&#xff1a; kill kill -18 表示继续 kill -9 杀死进程…

肿瘤内微生物群在癌症转移中的新作用

谷禾健康 癌症是一种复杂的疾病&#xff0c;归因于多因素变化&#xff0c;导致治疗策略困难。 90%的癌症患者死于复发或转移。癌症转移是恶性肿瘤进展的关键步骤&#xff0c;由癌细胞内在特性和外在环境因素决定。 一些微生物组通过诱导癌性上皮细胞和慢性炎症促进癌发生、癌症…

光耦合器:类型及其应用

光耦合器&#xff1a;类型及其应用 介绍 光耦合器&#xff08;也称为光隔离器&#xff09;是一种在两个隔离电路之间传输电信号的半导体器件。光耦合器由两部分组成&#xff1a;发射红外光的LED和检测LED光的光敏器件。这两个部件都装在一个带有连接销的黑匣子中。输入电路接收…

数据可视化笔记记录(二)pandas

笔记内容是根据B站上面的一位老师的视频而记录了&#xff0c;因为我也还在学习&#xff0c;所以个人理解可能会出现错误&#xff0c;若有错误请指出。另外这篇文章会很长 B站视频连接、 numpy,matplotlib的学习记录 pandas 学习记录 SerIes结构&#xff0c;一图胜千言 Seri…

实用工具:FastDoc 文档提取工具

实用工具&#xff1a;FastDoc 文档提取工具 简单、实用的 HTTP API 文档生成工具&#xff0c;支持 Spring MVC/Spring Boot 下的控制器信息提取&#xff0c;类似 Swagger 但稍有不同的机制。 在线演示地址在 https://framework.ajaxjs.com/demo/fast-doc/。 关于研发者这工具…