JS原型链和继承

article/2025/9/13 0:35:14

JS原型链和继承

认识对象的原型

[[Get]]:JS的存取描述符——get方法,在获取对象属性时会自动调用

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

  • [[prototype]]指向的对象:

    • 当我们通过引用对象的属性名来获取属性值的时候,会触发[[Get]]
    • [[Get]]会首先检查该对象是否有对应的属性,如果有就直接使用
    • 如果对象中没有该属性,那么会访问内置属性[[prototype]]所指向的对象,在其中再次检查是否有对应的属性,有就直接用,没有则返回undefined
    // JavaScript
    var obj = {name: "coder",age: 18
    }obj.__proto__ = {address: "广州市"
    }console.log(obj) // {name: "coder",age: 18}
    console.log(obj.address) // 广州市
    
  • 如果通过字面量直接创建一个对象(如:var a={}),这个对象也会有这样的属性吗?如果有,应该如何获取这个属性呢?

    • 有。只要是对象都会有这样的一个内置属性
    • 获取方式有两种:
      1. 通过对象的 __proto__ 属性可以获取到(但是这个属性是早期浏览器自己添加的,开发中尽量避免使用,存在一定的兼容性问题)
      2. 通过 Object.getPrototypeOf 方法可以获取到
    // JavaScript
    // 承接上文代码
    console.log(obj.__proto__) // {address: '广州市'}
    console.log(Object.getPrototypeOf(obj)) // {address: '广州市'}
    

什么是原型链?

  • [[Get]]首先会在当前对象中查找属性
  • 如果没有找到,就会去原型链(__proto__)对象上查找
  • 在找到顶层原型时,便不会再向继续查找了
// JavaScript
// 承接上文代码
obj.__proto__ = {}
obj.__proto__.__proto__ = {}
obj.__proto__.__proto__.__proto__ = {target: "excellent"
}
console.log(obj.target) // excellent
console.log(obj.__proto__.__proto__.__proto__.__proto__ === Object.prototype) // true(顶层原型,后面说)

顶层原型是什么?

到底是找到哪一层对象之后停止继续查找了呢?—顶层

// JavaScript
var obj1 = {name: "zzwi"
}
console.log(obj1.__proto__) // { ... }
console.log(obj1.__proto__.__proto__) // null

obj1.__proto__便是顶层原型对象

顶层原型来自哪里?

// JavaScript
var obj2 = {name: "bill",age: 21
}
console.log(Object) // ƒ Object() { [native code] }
console.log(obj2.__proto__) // { ... }
console.log(Object.prototype) // { ... }
// 新创建的obj2和原生Object函数 的原型属性指向共同的原型对象
console.log(obj2.__proto__ === Object.prototype) // true// Object函数的原型对象的constructor指向Object函数
console.log(Object.prototype.constructor) // ƒ Object() { [native code] }
// Object函数的原型对象已是顶层
console.log(Object.prototype.__proto__) // null// Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。
console.log(Object.getOwnPropertyDescriptors(Object.prototype)) // { ... }

从上面的Object原型我们可以得出一个结论:

原型链最顶层的原型对象就是Object的原型对象

顶层原型Object的特性:

  1. 该对象有原型属性,但是它的原型属性指向的是null
  2. 该对象上有很多默认的属性和方法
// JavaScript
var p1 = {name: "zzwi",age: 21
}
var p2 = {address: "广州市"
}
p1.__proto__ = p2
console.log(p1.address) // 广州市

代码逻辑如图所示
顶层原型Object

构造函数原型

构造函数也可称为类

代码:

// JavaScript
function Person(){}
var p = new Person()
console.log(Person.prototype.__proto__ === Object.prototype) // true

​ Person构造函数的prototype指向的原型对象拥有它自己的__proto__并指向了顶层原型

new一个对象p的过程

  1. 在内存中创建一个对象 var moni = {}

  2. this的赋值 this = moni

  3. 将Person函数的显式原型prototype赋值给创建出来的对象的隐式原型

    moni.__proto__=Person.prototype

面向对象三大特性:

  • 封装
  • 继承
  • 多态

这里核心讲继承

继承可以帮助我们将重复的代码和逻辑抽取到父类中,子类只需要直接继承过来使用即可。

在JavaScript中需要利用原型链的机制实现一下继承

原型链的继承

父类与子类

父类放置公共属性和方法,减少重复代码、实现代码复用

子类放置特有属性和方法,使代码更为灵活

// JavaScript
// 父类:公共属性和方法
function Person() {this.name = "coder"
}
Person.prototype.running = function() {console.log(this.name + " running~")
}
// 子类:特有属性和方法
function Student() {this.sno = 111
}var p = new Person()
Student.prototype = p
Student.prototype.studying = function() {console.log(this.name + " studying~")
}var stu = new Student()
console.log(stu.name) // coder
stu.running() // coder running
stu.studying() // coder studying

p是父类创建的对象,原型属性__proto__由父类Person的prototype赋值,因此也指向Person的原型对象

Student的prototype赋值为p对象,Student.prototype.__proto__ === Person.prototype就此和Person建立原型链关系,成为Person的子类

所以,由Student创建的对象stu,其中stu.__proto__ = Student.prototype,查找name属性和running方法时,能够沿着原型链找到父类Person内的name属性和原型对象的running方法
原型链_父类子类

Object是所有类的父类

如下代码所示

// JavaScript
function Person(name, age) {this.name = namethis.age = age
}
Person.prototype.running = function() {console.log(this.name + "running~")
}
var p1 = new Person("kaze", 18)console.log(p1) // Person {name: 'kaze', age: 18}
console.log(p1.valueOf()) // Person {name: 'kaze', age: 18}
console.log(p1.toString()) // [object Object]

Object父类

原型链继承的弊端

承接上文 父类与子类 的代码

// JavaScript
// 1. 第一个弊端:打印stu对象,继承的属性是看不到的
console.log(stu) // Student {sno: 111}// 2. 第二个弊端:创建出来的两个stu对象
var stu1 = new Student()
var stu2 = new Student()
// 直接修改对象上的属性,是给本对象添加了一个新属性
stu1.name = "kaze"
// stu1.friends = []
console.log(stu1.name) // kaze
console.log(stu2.name) // coder
// 获取引用,修改引用的属性值,会相互影响
stu1.friends.push("kaze")
console.log(stu2.friends) // ['kaze']// 3. 第三个弊端:在前面实现类的过程中都没有传递参数
var stu3 = new Student("bill", 21)

借用构造函数继承

代码如下:

// JavaScript
// 父类:公共属性和方法
function Person(name,age,friends) {this.name = namethis.age = agethis.friends = friends
}
Person.prototype.running = function() {console.log(this.name + " running~")
}
// 子类:特有属性和方法
function Student(name, age, friends, sno) {// 原型链继承弊端的解决方法Person.call(this, name, age, friends)this.sno = sno
}var p = new Person()
Student.prototype = p
Student.prototype.studying = function() {console.log(this.name + " studying~")
}var stu = new Student("coder",18,["trump"],1)console.log(stu) // Student {name: 'coder',age: 18,friends: ['trump'],sno: 1}var stu1 = new Student("zzwi",20,["kaze"],111)
var stu2 = new Student("bill",19,["kobe"],222)stu1.friends.push("black")
console.log(stu1.friends) // ['kaze','black']
console.log(stu2.friends) // ['kobe']

使用call方法, Person.call(this, name, age, friends) ,这就是原型链继承弊端的解决方法

如图

借用构造函数继承

借用构造函数继承的弊端

强调: 借用构造函数也是有弊端:
1.第一个弊端: Person函数至少被调用了两次
2.第二个弊端: stu的原型对象即p对象上会多出一些属性, 但是这些属性没有存在的必要

原型式继承函数

代码如下

// JavaScript
var obj = {name: 'zzwi',age: 18
}// 原型式继承函数
// 方法1
function createObject1(o) {var newObj = {}Object.setPrototypeOf(newObj,o)return newObj
}
var info1 = createObject1(obj)
console.log(info1.__proto__) // {name:'zzwi',age:18}// 方法2
function createObject2(o) {function Fn() {}Fn.prototype = ovar newObj = new Fn()return newObj
}
var info2 = createObject1(obj)
console.log(info2.__proto__) // {name:'zzwi',age:18}// 方法3
var info = Object.create(obj)
console.log(info.__proto__) // {name:'zzwi',age:18}

最终目的

info、info1、info2对象的原型都指向了obj对象

寄生式继承

寄生式继承是结合原型式继承和工厂模式的一种方式

代码如下:

// JavaScript
var personObj = {running: function() {console.log("running~");}
}// 工厂模式
function createStudent(name) {var stu = Object.create(personObj) // 原型式继承stu.name = namestu.studying = function() {console.log("studying~");}return stu
}var stuObj1 = createStudent("coder")
console.log(stuObj1.__proto__) // {running: ƒ}

接下来便是最终方案:

寄生组合式继承

现在我们来回顾一下之前提出的比较理想的组合继承

  • 组合继承是比较理想的继承方式, 但是存在两个问题:

  • 问题一: 构造函数会被调用两次: 一次在创建子类型原型对象的时候, 一次在创建子类型实例的时候.

  • 问题二: 父类型中的属性会有两份: 一份在原型对象中, 一份在子类型实例中.

事实上, 我们现在可以利用寄生式继承将这两个问题给解决掉.

  • 你需要先明确一点: 当我们在子类型的构造函数中调用父类型.call(this, 参数)这个函数的时候, 就会将父类型中 的属性和方法复制一份到了子类型中. 所以父类型本身里面的内容, 我们不再需要.

  • 这个时候, 我们还需要获取到一份父类型的原型对象中的属性和方法.

能不能直接让子类型的原型对象 = 父类型的原型对象呢?

  • 不要这么做, 因为这么做意味着以后修改了子类型原型对象的某个引用类型的时候, 父类型原生对象的引用类型 也会被修改.

  • 我们使用前面的寄生式思想就可以了

代码如下:

// JavaScript
// 原型式继承
function createObject(o) {function Fn() {}Fn.prototype = oreturn new Fn()
}
// 继承函数封装_工厂模式
function inheritPrototype(SubType,SuperType) {SubType.prototype = createObject(SuperType.prototype)// 将stu对象的类型值从Person修改为StudentObject.defineProperty(SubType.prototype,"constructor",{enumerable: false,configurable: true,writable: true,value: SubType})
}function Person(name,age,friends) {this.name = namethis.age = agethis.friends = friends
}Person.prototype.running = function() {console.log("running~")
}function Student(name,age,friends,sno,score) {Person.call(this,name,age,friends,sno,score)this.sno = snothis.score = score
}inheritPrototype(Student,Person)Student.prototype.studying = function() {console.log("studying~")
}var stu = new Student("zzwi",18,["bill"],111,100)console.log(stu) // Student { ... }
stu.running() // running~
console.log(Person.prototype.studying) // undefined

对象的方法补充

hasOwnProperty

  • 对象是否有某一个属于自己的属性(不是在原型上的属性)

in/for in 操作符

  • 判断某个属性是否在某个对象或者对象的原型上

instanceof

  • 用于检测构造函数的pototype,是否出现在某个实例对象的原型链上

isPrototypeOf

  • 用于检测某个对象,是否出现在某个实例对象的原型链上

代码如下

// JavaScript
var obj = {name: "bill",age: 18
}var info = Object.create(obj,{address: {enumerable: true,value: "广州市"}
})// hasOwnProperty方法判断
// 属性或方法在当前对象中则返回true,在原型中便返回false
console.log(info.hasOwnProperty("address")) // true
console.log(info.hasOwnProperty("name")) // false// ================================================// in 操作符:不管在当前对象还是原型中返回的都是true
console.log("address" in info) // true
console.log("name" in info) // true
for(var key in info) {console.log(key) // address name age
}// ================================================// instanceof
// 用于检测构造函数的prototype,是否出现在某个实例对象的原型链上
function Person() {}
function Student() {}
Student.prototype = new Person()
Student.prototype.studying = function() {console.log("studying~");
}
var stu = new Student()
console.log(stu instanceof Student) // true
console.log(stu instanceof Person) // true
console.log(stu instanceof Object) // true// 用于检测某个对象,是否出现在某个实例对象的原型链上
console.log(Person.prototype.isPrototypeOf(stu)) // true
console.log(Student.isPrototypeOf(stu)) // false

对象-函数-原型之间的关系

代码如下:

// JavaScript
var obj = {name: "why"
}console.log(obj.__proto__) // { ... }// 对象里面是有一个__proto__对象: 隐式原型对象// Foo是一个函数, 那么它会有一个显式原型对象: Foo.prototype
// Foo.prototype来自哪里?
// 答案: 创建了一个函数, Foo.prototype = { constructor: Foo }// Foo是一个对象, 那么它会有一个隐式原型对象: Foo.__proto__
// Foo.__proto__来自哪里?
// 答案: new Function()  Foo.__proto__ = Function.prototype
// Function.prototype = { constructor: Function }
// Function.prototype === Function.__proto__// var Foo = new Function()
function Foo() {}console.log(Foo.prototype === Foo.__proto__) // false
console.log(Foo.prototype.constructor) // Foo(){}
console.log(Foo.__proto__.constructor) // Function(){...}
console.log(Function.prototype === Function.__proto__) // truevar foo1 = new Foo()
var obj1 = new Object()console.log(Object.getOwnPropertyDescriptors(Function.__proto__)) // {...}

对象-函数-原型之间的关系

JavaScript_Object_Layout

参考

深入JavaScript高级语法-coderwhy


http://chatgpt.dhexx.cn/article/LC1mddOr.shtml

相关文章

JS原型链继承

再讲述JS原型链继承之前,我希望大家能够先理解 《函数,函数原型和函数实例之间的关系》,这样有助于大家理解JS原型链继承的原理,下面先看一张图吧,咱们看图说话: 如果大家看完了 《函数,函数原型和函数实…

原型链和继承的六种实现方式

一省:HTML 12. img标签的alt和title有什么不同? alt: 当图片加载不出来的时候,就会在图片未显示的地方出现一段 alt 设置的属性内容。浏览器的搜索引擎可以通过 alt 属性的文字描述来获取图片。 title: title是鼠…

原型,原型链,原型的继承

原型的作用? 1.节省内存空间 2.实现数据共享(继承) 什么是原型? 任何一个函数都有propotype属性,它本身是一个对象,我们称之为原型 构造函数,实例化对象与原型之间的关系? 1.任何一个函数都有prototype属性,它本身是一个对象,我们称之为原型 2.构造函数也是函数,也都…

JS原型、原型链和7种继承方法【白话文讲解】

前言 在学习JS原型、原型链和继承之前,我们必须先弄懂三个W,也就是我们常说的“学习三问” 学习三问: 1.它是什么?(What) 2. 为什么用它?(Why) 3. 什么时候用它&#xff…

原型链与常用继承方法

原型链:当访问一个对象的属性时,如果该对象内部不存在这个属性,就会去该对象的__proto__ 上(也就是它构造函数的prototype)查找 。该构造函数的prototype上也有一个自己的__proto__ 属性,然后继续向上查找,…

如何用原型链的方式实现一个 JS 继承?

大家好,我是前端西瓜哥。今天讲一道经典的原型链面试题。 原型链是什么? JavaScript 中,每当创建一个对象,都会给这个对象提供一个内置对象 [[Prototype]] 。这个对象就是原型对象,[[Prototype]] 的层层嵌套就形成了…

JS学习笔记 原型链和利用原型实现继承

原型链 原型链是一种关系,实例对象和原型对象之间的关系,关系是通过原型(__proto__)来联系的 实例对象中有__proto__,是对象,叫原型,不是标准的属性,浏览器使用,并且有的游览器不支持构造函数中有prototype属性,也是对象,叫原型 注意 原型中的方法是可…

Arduino基本知识(marlin固件配置)

初识arduino,根据mega2560(某宝可以买到)官网的100个管脚具体控制一句传输进行操作。 https://www.arduino.cc/en/Hacking/PinMapping2560 其管脚图如上所示。 首先在官网下载arduino的配套软件 https://www.arduino.cc/ 对于编程&#xf…

3D打印机硬件驱动-马林固件最新版本2.0.X中文注释(3)marlin 2.0.9.2 截至发稿时间2021年12月16日

/** * Marlin 3D Printer Firmware 头描述详见其他两个文件头描述 * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm * * This program is…

Marlin固件配置

文档来源自http://www.geek-workshop.com/thread-33314-1-1.html 1、基本配置基本配置是可选的,主要是给你的固件起个名字,如果你的配置很牛,让大家知道你是谁。据说这个在启动的时候会显示在显示屏中,应为我没有显示屏,所以无法验证。这个修改也很简单,通过搜索找到“S…

MKS MONSTER8 V1.0使用说明书(基于Marlin 2.0.X固件配置Voron 2.4)

广州谦辉信息科技有限公司 (基于Marlin 2.0.x 固件配置 Voron 2.4) 创客基地QQ群:489095605 232237692 邮箱:Huangkaidamakerbase.com.cn 主板购买链接:https://item.taobao.com/item.htm?spma1z10.5-c-s.w4002-23356668283.43.7eec55caLT…

ESP32烧录Marlin固件

安装platformIO 这点很简单,保证你拥有一个能够成功连接外网的环境即可。内网可能不太稳定有可能安装失败。 克隆代码 我这里采用的是fyset_e4的代码,这个代码开源在了https://github.com/FYSETC/FYSETC-E4,作者已经针对marlin固件做了一些配置。 更改…

i3型3D打印机制作详解——Marlin固件中文介绍

关注微信公众号:嵌入式基地 后台回复:3d打印机 获取资料 硬件框架搭建介绍 https://blog.csdn.net/qq_39020934/article/details/80380250 Marlin固件中文介绍 https://download.csdn.net/download/qq_39020934/10401251 …

3D打印机Marlin固件双Z轴设置

3D打印机Marlin固件双Z轴设置 在3D打印机Marlin固件的最新版本2.1.1中,设置双Z轴和老版本有一些改动。记录一下如何在最新版本的Marlin固件中设置双Z轴。 以MKS GEN_L V2.1的主板为例,硬件连接还是和原来一样,第二个Z轴的电动机连接到空闲的…

Marlin 固件配置手动退换料

换料的步骤首先把喷头加热,软化喷嘴里残余的线材,然后反转挤出机,把线材抽出来。最后装入新线材,并挤出余留在喷头里的材料。整个过程用一个命令 M600 就能完成。默认情况下 Marlin 固件并没有开启这个功能,但是可以修…

MKS_SGEN_L V1.0 marlin 固件编译

1.下载 Visual Studio Code 打开Visual Studio Code 安装插件 在应用商店搜索下载安装如下图: 安装完这些还是不行的 还要另外安装python 3.8.8 其他版本python 不行右下角会报错,提示安装python 3.8 python 3.8.8 百度网盘下载链接:https://pan.bai…

3D打印机硬件驱动-马林固件最新版本2.0.X中文注释(1)marlin 2.0.9.2 截至发稿时间2021年12月16日

马林固件最新版本翻译注释 /* Marlin Firmware 马林固件 (c) 2011-2020 MarlinFirmware Portions of Marlin are (c) by their respective authors. 马林部分程序来源于世界各地的开发者 All code complies with GPLv2 and/or GPLv3 所有源码依靠GPLv2 和 GPLv3架构编写…

Marlin固件学习总结(一)

接触过3D打印也有一段时间了,一直没有将学到的知识以文本的形式记录下来。现在也没有太多时间继续玩这个了,因此想慢慢把之前所接触到所学到的知识通过文本的形式记录一下,也分享给那些感兴趣的人。 既然是开篇我们先了解一下marlin固件的结构…

Marlin固件之二:源代码详解与移植

由于需要进行固件定制化,Marlin固件太过于强大和紧凑,我对这个固件进行了裁剪,只剩下主枝干,实现功能的定制和裁剪。以下的代码详解是基于我已经移植在stm32上面的一个程序进行的。

Marlin固件之—:基础入门与测试

一、Marlin的简单介绍 Marlin固件是一个3D打印的开源固件,3D打印固件有许多,Marlin最为健全和强大,当然相对也会复杂一些。使用Gcode控制爱,Gcode是数控机床等工控控制使用范围较广的一种指令协议。在这里介绍一些Marlin的入门经…