JS对象-原型、原型链和继承

2023-10-29

一、原型

原型的作用是什么?

在理解原型的作用之前,我们需要先知道一个例子

function Person(name) {
  this.name = name
  this.sayName = function () {
    console.log(this.name)
  }
}

const p1 = new Person('张三')
const p2 = new Person('李四')
console.log(p1.sayName === p2.sayName) //false

这里我们声明一个构造函数并在这个构造函数内部定义了相关的属性和方法,我们可以看到,该构造函数在声明实例的时候,sayName这个方法每个实例都重新声明创建了一遍,但是不同实例sayName的内容是完全一样的,这就导致了不必要的浪费。而原型最重要的作用,就是为了让实例去共享相关的属性和方法。如下所示:

function Person(name){
  this.name = name
}

//将sayName声明在原型对象上
Person.prototype.sayName = function(){
  console.log(this.name)
}

const p1 = new Person('张三')
const p2 = new Person('李四')
console.log(p1.sayName === p2.sayName)  //true

实例、构造函数、原型对象的关系

每个构造函数都会创建一个prototype的属性,这个属性的值是一个对象,包含应该由特定引用类型的实例共享的属性和方法。这个对象就是我们所说的原型对象。默认情况下,所有原型对象自动获得一个名为constructor的属性,指回与之关联的构造函数。而通过构造函数来实例化的对象,被称之为实例,每个实例都有一个__proto__的属性,来指向实例的原型对象。如下图所示:
在这里插入图片描述

function Person(){}

console.log(typeof Person.prototype) //object
console.log(Person.prototype) //{}
console.log(Person.prototype.constructor === Person) //true
console.log(Person.prototype.__proto__ === Object.prototype) //true
console.log(Person.prototype.__proto__.constructor === Object) //true
console.log(Person.prototype.__proto__.__proto__ === null) //true
console.log(Person.prototype.__proto__)

let person1 = new Person()
    person2 = new Person()

// 构造函数和实例以及原型对象是三个完全不同的对象
console.log(person1 !== Person)//true
console.log(person1 !== Person.prototype)//true
console.log(Person.prototype !== Person)//true
// 实例的__proto__指向原型对象
console.log(person1.__proto__ === Person.prototype)//true
console.log(person2.__proto__.constructor === Person)//true
console.log(person1.__proto__ === person2.__proto__) //true

// instanceof检查实例的原型链中是否包含指定构造函数的原型
console.log(person1 instanceof Person) //true
console.log(person2 instanceof Object) //true
console.log(Person.prototype instanceof Object) //true

原型相关的方法(以Person构造函数,实例person1为例)

isPrototypeOf():原型对象的方法,可以用来判断person1是否为Person的实例
Object.getPrototypeOf():返回参数的内部特性[[Prototype]]的值

Person.prototype.isPrototypeOf(person1)  //true
Object.getPrototypeOf(person1) 返回person1的原型对象Person.prototype

原型层级及属性访问

通过对象访问属性时,会按照属性名称开始搜索,优先搜索实例本身,如果实例本身不存在,则去实例的原型对象上找是否存在该属性。如果实例上已经存在了同名属性,然后还想访问原型对象上的同名属性,需要使用delete操作符删除实例上的同名属性(只能删除,设置为null无效)。
hasOwnProperty方法用于确定某个属性是在实例上还是在原型对象上。
in操作符可以用于判断某个属性是否在实例属性上或者原型上。
for-in循环中使用in操作符时,可以通过对象访问且可以被枚举的属性都会返回,包括实例属性和原型属性。

function Person(name) {
  this.name = name
}

function hasPrototypeProperty(obj, property) {
  return !Object.hasOwnProperty(property) && (property in obj)
}
Person.prototype.name = "李四"
Person.prototype.work = "developer"
Person.prototype.language = ["英语"]
let p = new Person("张三")

console.log(p.name)
console.log(p.work)
console.log("还没删除name", "name" in p)
console.log(hasPrototypeProperty(p, "name"))
delete p.name
console.log("删除了name", "name" in p)
console.log(hasPrototypeProperty(p, "name"))
console.log(p.name)
p.language = ["英语", "中文"]
// p.language.push('中文')

let p2 = new Person("王五")
console.log(p.language)
console.log(p2.language)
console.log(Object.hasOwnProperty("name"))

输出结果

张三
developer
还没删除name true
false
删除了name true
false
李四
[ '英语', '中文' ]
[ '英语' ]
true

注意:因为从原型上搜索值的过程是动态的,所以即使实例在修改原型之前已经存在,任何时候对原型对象所做的修改也会在实例上反映出来

原型的问题

1.构造函数的弱化

原型弱化了向构造函数传递初始化参数的能力,会导致所有实例默认都取得相同的属性值

2.数据共享性在引用值属性上的问题

举个例子:当一个原型对象的属性是一个数组时,我们在实例中如果访问这个数组并调用数组的push,那就会导致原型上数组内容也发生改变,如果是就是想要修改原型数组内容还好,如果不是,则可能导致所有引用到这个数组地地方可能带来意想不到的问题。

二、原型链

每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。基本上所有的函数,都包含Object原型。而Object的prototype指向的是null。

function SuperType() {
  this.property = true
}

SuperType.prototype.getSuperValue = function () {
  return this.property
}

function SubType() {
  this.subproperty = false
}

SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function () {
  return this.subproperty
}

let instance = new SubType()

console.log(instance.getSuperValue()) // true

完整的原型链如下:
在这里插入图片描述

原型链实现继承的问题

1.原型中存在引用值是可能导致修改原型引用值的问题
详细描述参考上文原型的问题,第二点
2.子类型在实例化时不能给父类型的构造函数传参

三、其他继承实现

1.盗用构造函数

在子构造函数中通过直接调用父类构造函数并采用call/apply方法将参数传递给父构造函数。
缺点:无法实现公共函数在不同实例的共享问题,以及无法访问父类的原型对象中的方法。
优点:子类创建的每个实例,都各自声明了各自的父类属性,不会出现原型链中的引用值问题。

function SuperType(name){
  this.name = name
}

SuperType.prototype.getName = function(){
  return this.name
}

function SubType(){
  SuperType.call(this,"张三")
  this.age = 29
}

var instance = new SubType()
console.log(instance.name) //张三
console.log(instance.age) //29
console.log(instance.getName()) //报错

2.组合继承(经典继承)

组合继承即同时结合盗用构造函数和继承原型的方式。
优点:采取盗用构造函数继承实例属性,避免了引用值的问题,同时使用原型链继承原型上的属性和方法。
缺点:父类构造函数始终会被调用两次,一次在是创建子类原型时调用,另一次是在子类构造函数中调用。

function SuperType(name) {
  this.name = name
  this.colors = ["red", "blue", "green"]
}

SuperType.prototype.sayName = function () {
  console.log(this.name)
}

function SubType(name, age) {
  SuperType.call(this, name)
  this.age = age
}

SubType.prototype = new SuperType()
SubType.prototype.sayAge = function(){
  console.log(this.age)
}

let instance1 = new SubType("张三",24)
instance1.colors.push('yellow')
instance1.sayAge()
instance1.sayName()
console.log(instance1.colors)

let instance2 = new SubType("李四",35)
console.log(instance2.colors)
instance2.sayAge()
instance2.sayName()

输出结果:

24
张三
[ 'red', 'blue', 'green', 'yellow' ]
[ 'red', 'blue', 'green' ]
35
李四

3.寄生式组合继承

寄生式组合继承就是在组合继承的基础上进行的改良,子类的prototype直接指向一个父类的prototype的副本,这样就保证了仅有子类存在同名的属性,以及在处理原型的时候不触发副本的构造函数执行。
优点:即实现了原型链的继承也解决了引用值的问题,同时避免了调用两次父类的构造函数。

function object(o) {
  function F() {}
  F.prototype = o
  return new F()
}
function inheritPrototype(subType, superType) {
  // 创建父类原型的副本,免去了一次构造函数的调用,直接继承superType的原型链副本
  let prototype = object(superType.prototype)
  // 解决由于重写原型导致默认constructor丢失的问题
  prototype.constructor = subType
  //
  subType.prototype = prototype
}

function SuperType(name) {
  console.log("SuperType")
  this.name = name
  this.colors = ["red", "blue", "green"]
}

SuperType.prototype.sayName = function () {
  console.log(this.name)
}

function SubType(name, age) {
  SuperType.call(this, name) //第二次调用
  this.age = age
}

inheritPrototype(SubType, SuperType)

SubType.prototype.sayAge = function () {
  console.log(this.age)
}
var instance1 = new SubType("张三", 24)
instance1.colors.push("yellow")
instance1.sayAge()
instance1.sayName()
console.log(instance1.colors)

var instance2 = new SubType("李四", 35)
console.log(instance2.colors)
instance2.sayAge()
instance2.sayName()

输出结果:

SuperType
SuperType
24
张三
[ 'red', 'blue', 'green', 'yellow' ]
SuperType
[ 'red', 'blue', 'green' ]
35
李四
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JS对象-原型、原型链和继承 的相关文章

  • 在 ES5 中创建自定义元素 v1,而不是 ES6

    现在 如果您严格遵循自定义元素规范 v1 https html spec whatwg org multipage custom elements html custom elements 无法在不支持类的浏览器中使用自定义元素 有没有办法
  • 在设置后用 Javascript 替换 'var' css 属性

    我有一个元素 其上设置了 var 属性 如下所示 div class divwithbackground div CSS divwithbackground after background image var page header se
  • 如何将 scala 列表转换为 javascript 数组?

    有更简单的方法吗 document ready function var jsArray if scalaList null for id lt scalaList jsArray push id 很简单 如下所示 import play
  • ECMAScript 6 类中的 getter 和 setter 有何用途?

    我对 ECMAScript 6 类中 getter 和 setter 的意义感到困惑 什么目的 下面是我参考的一个例子 class Employee constructor name this name name doWork return
  • Sequelize 关联 - 请改用 Promise 风格

    我正在尝试将 3 张桌子连接在一起Products Suppliers and Categories然后排SupplierID 13 我读过了如何在sequelize中实现多对多关联 https stackoverflow com a 25
  • codePointAt 和 charCodeAt 之间的区别

    有什么区别String prototype codePointAt and String prototype charCodeAt 在 JavaScript 中 A codePointAt 65 A charCodeAt 65 从 MDN
  • 使用 jQuery live() 初始化插件?

    使用 jQuery 在特定类的所有当前和未来元素上自动初始化插件的最佳方法是什么 例如 假设我想要全部
  • Jquery:排除元素

    我有以下代码 document ready function a rel each function this qtip content text img class middle src i icon processing gif alt
  • React Native 中 fontAwesome 图标的圆形轮廓

    我想使用 fontAwesome 图标 使其位于圆圈的中间 我想将它用作一个图标项 我读到我们可以将它与圆形图标一起使用并将其放置在其中 但我无法使其工作 import IconFA from react native vector ico
  • Nodejs 异步 Promise 队列

    我需要使用速率受限的 API 例如 我一秒钟只能进行 10 个 API 调用 因此我需要等待当前秒结束才能进行另一个 API 调用 为了实现这一目标 我想创建一个可以自行管理的异步队列 它的主要功能是让我向队列添加一个新的 Promise
  • 如何将类组件中的 props 发送到功能组件?

    我是 ReactJS 的初学者 需要知道如何将一个页面中的 props 值发送到另一个页面 道具位于第一页上我可以获取类组件值如何获取另一页中的值 提前致谢 墙色 jsx import React Component from react
  • 使标签充当输入按钮

    我怎样才能做一个 a href http test com tag test Test a 就像表单按钮一样 通过充当表单按钮 我的意思是 当单击链接执行操作时method get 或 post 以便能够通过 get 或 post 捕获它
  • Python 中的 Firebase 身份验证时出现 KeyError:“databaseURL”

    相信你做得很好 我是 firebase 的新手 正在尝试进行用户身份验证 我已经安装了pyrebase4并在firebase控制台上创建了一个项目 我还启用了使用 电子邮件和密码 登录并尝试连接我的应用程序 下面是我正在尝试的代码 impo
  • 如何在 Node.js 中打开 Windows-1255 编码文件?

    我有一个 Windows 1255 希伯来语 编码的文件 我希望能够在 Node js 中访问它 我尝试使用打开文件fs readFile 它给了我一个Buffer我无能为力 我尝试将编码设置为Windows 1255 但这没有被识别 我还
  • JavaScript 中的安全数据

    我必须为 Web 测试创建生成器 使用 HTML 和 JavaScript 测试必须离线和在线进行 正确答案和分数评估必须是生成的测试的一部分 最终用户的分数仅发送到服务器 无法在服务器上进行评估 并且服务器对问题一无所知 它只保存最终分数
  • 分配函数后如何删除 onmouseout 事件?

    我有一个问题 我正在为 onmouseout 事件分配一个函数 但运行该事件后 我需要将其删除 将非常感谢您的帮助 这取决于你的代码 如果你用 d3 这样做 那么你可以说 在 onmouseout 事件函数中 element on mous
  • 阻止 PM2 上不同时运行的请求

    在我的 Express 应用程序中 我在应用程序中定义了 2 个端点 一种用于 is sever up 检查 另一种用于模拟阻塞操作 app use status req res gt res sendStatus 200 app use
  • 如何在 getStaticPaths 内添加 params 值数组

    我有一个页面 其结构如下 read slug number 我想要得到slug每个对应的值number in the getStaticPaths这是代码 export async function getStaticPaths const
  • 如何在 JavaScript 中将日期时间微格式转换为本地时间?

    我有一个页面当前正在使用日期时间微格式 http microformats org wiki datetime design pattern显示时间戳 但我只显示我自己的时区的人类可读时间
  • 调用一个从 AngularJS 表达式本地计算值的函数是不是很糟糕?

    我读了关于使用范围的一些 AngularJS 陷阱的文章 http thenittygritty co angularjs pitfalls using scopes 并且它指出您不应在表达式中使用函数 并且我知道每次框架认为需要时都可能会

随机推荐