热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

了解JavaScript中的对象原型和原型链

本篇文章带大家介绍一下JavaScript中的对象原型和原型链。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。
本篇文章带大家介绍一下Javascript中的对象原型和原型链。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

对象原型

相信大家都这样用过 map

let arr = [0, 1, 2]
let doubleArr = arr.map(c => c * 2)
console.log(doubleArr) // 0, 2, 4

不知道你有没有想过, arr 本身并没有设定 map 属性,那为什么可以用 map 这个函数呢?

把它打印出来看看:

console.log(arr)
// 0: 0
// 1: 1
// 2: 2
// length: 3
// __proto__: Array(0)

出现了一个名为 __proto__ 的对象,如果再将其展开,就会看到所有 Array 对象可以使用的函数;当然我们也可以在其中找到 map 函数,而这也正是例子中所调用的 arr.map 这个函数:

console.log(arr.map === arr.__proto__.map) // true

这里出现的 __proto__ 对象,也就是所谓的 原型对象(Prototype)

不同于 Java、C# 等基于类(Class) 的面向对象语言,通过定义类、创建实例、指定继承等方式来传递属性及方法;Javascript 则是个基于原型(Prototype)的对语言 ,通过预先建立出的原型对象,当新对象建立时指定对象的原型要参照哪个原型对象。

而当我们调用对象的属性或方法时,若对象本身没有这个属性或方法,Javascript 会自动寻找它原型中的方法,这也就是为什么可以直接调用 arr.map 而不会出错的原因。

原型链

你可能已经发现在前面的例子中,__proto__ 对象里仍然有 __proto__ 属性:

console.log(arr.__proto__) // Array 的 Prototype
console.log(arr.__proto__.__proto__) // Object 的 Prototype
console.log(arr.__proto__.__proto__.__proto__) // null

在上述机制中,每当对象建立时都会绑定原型,既然对象都有原型,对象原型本身也是对象,自然也不例外;由这个例子中我们可以看出:

  • arr 是数组实例,原型是 Array
  • arr.__proto__ 是数组的原型,原型是 Object
  • arr.__proto__.__proto__ 是对象的原型,原型是 null
  • arr.__proto__.__proto__.__proto__ 是 null,没有任何属性

由于每个对象都有原型,这样就形成了一个关联一个、层层相互依赖的从属关系,我们把它们叫做原型链(Prototype Chain) ;通过这种机制,让对象得以使用原型中的属性和方法,并凭借原型链一层一层的按顺序继承,让对象能拥有原型链上所有原型的功能,这就是 Javascript 对象背后的运作机制。

补充:在 Javascript 中,几乎每个原型链的末端都会是 Object,并最后指向到 null

使用原型

说了这么多,该来点代码了,接下来就来练习一下原型的建立、设定及修改吧。

先来创建一个新的对象构造函数:

function Person(name) {
  this.name = name
}Person.prototype.hello = function () {
  console.log(`Hello ${this.name}.`)
}let gary = new Person('Gary')
gary.hello() // Hello Gary.Object.getPrototypeOf(gary) // {hello: ƒ, constructor: ƒ}

上面的例子创建了一个简单的对象构造函数 Person(),并在构造函数中设定对象属性。对象的方法中,由于方法不需要让每个对象都独自拥有一份,以避免造成冗余的内存消耗,应该要像前面 Array.prototype.map 的例子那样把对象的方法设定给原型对象(Person.prototype),让这个构造函数创建出来的对象都可以共用这些方法。最后建立一个新的 Person 对象,并通过 getPrototypeOf(obj) 获取新产生对象的原型。

Q:为什么不直接用 __proto__ 获取原型对象?
A:因为虽然 __proto__ 被几乎所有的浏览器支持,但它是非标准属性;通过 getPrototypeOf 取得对象的原型是正确的方法。

提醒:Person.prototype 不是 Person 的原型,而是构造函数执行后所建立的新对象的原型;千万不要把构造函数的 prototype 属性对象的原型搞混!

原型继承

接着再创建新的对象原型,并继承 Person

function Engineer(name, skill) {
  Person.call(this, name)
  this.skill = skill
}
Engineer.prototype = Object.create(Person.prototype)
Engineer.prototype.cOnstructor= Engineerlet alice = new Engineer('Alice', 'Javascript')
alice.hello() // Hello Alice.
console.log(alice.skill) // JavascriptObject.getPrototypeOf(alice) 
// Person {constructor: ƒ}

这里建立了新的对象 Engineer 的原型,并通过 Engineer.prototype 的指定,让它的原型继承自 Person.prototype,最后再重新设定 Engineer.prototype.constructor,让构造函数重新指回自己;这样就完成了最基本的原型继承。

Q:为什么需要重新设定 constructor
A:这边功过 Object.create 复制了 Person.prototype 的全部属性,连同 constructor 属性都会被覆盖掉,如果 constructor 属性错误,在做 instanceof 判断时会产生错误的结果;因此这边设定继承时需要再次将 constructor 重新指定回构造函数本身。

修改原型

原型的引用、继承是直接参照到原型对象上,并非是在每个对象都复制一份原型;因此可以利用这个特性,在原型上增加自定义的属性和方法,让所有该类型的对象都能得到新方法;许多针对旧版浏览器的 Polyfill 就是这样实现的。

例如我们在写 Vue 项目的时候,可能都有做过类似的操作,把共用性高的属性方法放到 Vue.prototype 中:

Object.defineProperty(Vue.prototype, '$date', { value: dateTimeFormat })
// 之后就可以这样用
vm.$date(dateObj)

这样的确很方便,但也要提醒开大家,当我们在做原型修改的时候要特别小心。接着刚才的例子,如果尝试对 Person 原型中的方法做个修改:

Person.prototype.hello = function () {
  console.log(`Bye ${this.name}.`)
}gary.hello() // Bye Gary.
alice.hello() // Bye Alice.

如结果所示,当对象原型做修改时,所有原型链上有这个原型的对象,通通都会受到影响,不管对象是在修改前还是修改后创建的。

建议大家除非是 Polyfill,否则应该要极力避免对原生对象的原型进行修改,防止造成可能的意外结果。

ES6 的 Class

看完前面这一大段,是不是觉得心很累?别担心,从 ES6 开始添加了 Class 语法糖,使开发者体验提升了很多。下面把前面的例子用 Class 重构一下:

class Person {
  constructor (name){
    this.name = name
  }
  // 方法会自动放到 Person.prototype
  hello() {
    console.log(`Hello ${this.name}.`)
  }
}class Engineer extends Person {
  constructor (name, skill){
    super(name) // 调用 Person 的构造函数
    this.skill = skill
  }
}let alice = new Engineer('Alice', 'Javascript')
alice.hello() // Hello Alice.Object.getPrototypeOf(alice) 
// Person {constructor: ƒ}

很方便,同样的功能,代码的可读性却提高了不少,繁琐的设定也都能交给语法自动帮你完成。不过方便的语法背后,底层仍然是对象原型及原型链。

总结

以上是 Javascript 中关于对象原型的说明,希望能帮你理解对象原型,在这个什么都是对象的语言中,充分理解并掌握对象原型,是成为专业码农必须要突破的关卡之一。

更多编程相关知识,请访问:编程课程!!

以上就是了解Javascript中的对象原型和原型链的详细内容,更多请关注 第一PHP社区 其它相关文章!


推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Python如何调用类里面的方法
    本文介绍了在Python中调用同一个类中的方法需要加上self参数,并且规范写法要求每个函数的第一个参数都为self。同时还介绍了如何调用另一个类中的方法。详细内容请阅读剩余部分。 ... [详细]
  • 本文介绍了如何将CIM_DateTime解析为.Net DateTime,并分享了解析过程中可能遇到的问题和解决方法。通过使用DateTime.ParseExact方法和适当的格式字符串,可以成功解析CIM_DateTime字符串。同时还提供了关于WMI和字符串格式的相关信息。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 解决VS写C#项目导入MySQL数据源报错“You have a usable connection already”问题的正确方法
    本文介绍了在VS写C#项目导入MySQL数据源时出现报错“You have a usable connection already”的问题,并给出了正确的解决方法。详细描述了问题的出现情况和报错信息,并提供了解决该问题的步骤和注意事项。 ... [详细]
  • 导出功能protectedvoidbtnExport(objectsender,EventArgse){用来打开下载窗口stringfileName中 ... [详细]
  • 本文是关于C#类型系统、值类型和引用类型的概念性笔记。介绍了C#1系统类型的三个特性,静态类型的含义,显式类型和隐式类型的区别。还讨论了类、结构、数组类型、枚举、委托类型和接口类型属于哪一种类型。同时纠正了关于结构、引用类型和对象传递的错误表述。最后提到了C#4中使用动态类型的关键字。 ... [详细]
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • 本文介绍了《中秋夜作》的翻译及原文赏析,以及诗人当代钱钟书的背景和特点。通过对诗歌的解读,揭示了其中蕴含的情感和意境。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • 本文详细介绍了使用C#实现Word模版打印的方案。包括添加COM引用、新建Word操作类、开启Word进程、加载模版文件等步骤。通过该方案可以实现C#对Word文档的打印功能。 ... [详细]
  • 本文介绍了C#中快速生成随机整数的方法。默认的Random类构造函数使用时间作为种子,会生成许多重复的随机数。文章探讨了是否有更快的方案,并讨论了随机数可以出现重复的情况。 ... [详细]
author-avatar
xmli
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有