博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
理解原型其实是理解原型链
阅读量:7065 次
发布时间:2019-06-28

本文共 4383 字,大约阅读时间需要 14 分钟。

前言

原型和原型链,说是两个词,其实理解一个就可以了。这两个概念是同时存在的,不可能抛开一个去谈论另外一个,或者说这两个概念结合在一起才会发挥作用,甚至原型的存在是因为有原型链的存在,不在原型链上的原型只能称之为对象。

原型链

先来说说原型链是个什么东东,说起链我们现在脑海中描绘一下自己对链这个字的第一反应是什么,是社会我大哥的大金链?

是二哈的大狗链?
还是数据结构链表?

皮一下,下面我们正经说原型链,原型链从本质上来讲应该是个链表结构,也就是和上面的单链表有点像,我们把上图中的next换成__proto__属性,data换成键值对集合,这样经常在控制台输出对象的同学会不会有点熟悉的感觉?

举个栗子

下面举个庸俗的例子,有一个对象人,有move和sleep属性,人中又有男人具有sex属性,男人中又有一类人程序员具有code和hair(其值为less)属性,现在我们想用一个对象来表达程序员,那么这个对象应该同时具备人,男人,程序员的属性,我们用原型链来表达他们,就像这样

原型链具备的特征是能够从下往上查找属性,利于当我在要programmer对象中读取sex属性时,浏览器引擎会先在programmer对象中查找该属性,如果未查找到,那么通过其__proto__找到man对象,在man对象中去查找,在man对象中查找到了sex属性,并获取其值‘man’将其返回,就完成了一次属性查找。同理如果要通过programmer获取move,也是这样层层查找自身属性并通过__proto__往上查找。

程序员的睡觉时间与一般人不同,因此需要定义自己的sleep方法,直接在programmer对象上设置sleep属性,那么programmer对象就具有了自己的sleep属性,当通过程序员获取sleep属性时获取到的就是自己定义的sleep属性,也就是说同样都是person,此刻的programmer的sleep已不是peroson的sleep。

原型链的特征

通过上上面的例子,我们不难得出原型链具有的两个基本特征:

  1. 查找属性时可顺链向上查找
  2. 设置属性时只能设置当前对象的属性,而不会影响其上层链上的对象属性

第一点特征常常被人们称为继承,但是应该不能算是真正的继承,只能说在表现上与继承无异。真正意义上的继承是你从某处学会了某项能力,就算只有你一个人的时候你也是具备这项能力的,但是我们的原型链更应该是一个委托链,你可以通过这个委托链获取这个链上自你之后所有对象的能力,如果这个链发生变化你可能会失去某项能力。继承是对象本身具有这个能力或者特性,而原型委托是你及你身后的委托链具备这个能力。当然这对于对象的使用者我们来说是无所谓的,我们不必过分纠结到底是继承还是委托,但是了解事情的本质也是一件不错的事。

其实原型链具备这两点特征实际上是很自然而然的,这样的表现形式并没有太多刻意的违背正常逻辑的人为规定,我们只需稍微思考其在实际中的作用就能理解。

__proto__和prototype的关系

原型的英文是什么来着,嗯,prototype,只要说到原型就会被人们提起的一个词。那么它到底和原型有没有关系呢?这里我要说这个词虽然是原型的意思,其实它和原型并没有什么关系,骚年们以后不要直接在对象上去a.prototype了,这样你大多数情况下得到的只会是undefined(在函数对象上可以获取到值)。能在对象上直接获取其原型的是__proto__,你a.__proto__多数一般都能取到值,这个属性记录了该对象的原型对象地址。

prototype

这个词其实和原型链是有关系的,和原型真的一点关系没有,其作用是用来指定你使用new关键字调用函数的时候生成实例对象的原型(这个原型后面可能还藏着一条原型链)的。下面上代码

var person = {  move: function() {    console.log('moving')  },  sleep: function() {    console.log('sleeping')  }};function Man() {  this.sex = 'man'}// 为new Man()得到的对象指定原型对象personMan.prototype = person;function Programmer() {  this.hair = 'less';  this.code = function() {    console.log('coding')  }}// 为new Programmer()得到的对象指定原型对象new Man()Programmer.prototype = new Man();var programmer = new Programmer();console.log(programmer);复制代码

从上面的代码的运行结果中我们不难看出,prototype的作用只是在特定场景下得到的对象的原型(还有其他多种指定对象原型的方式,下面另开小节说明),且这里指定的不仅仅是原型,当指定man为programmer的原型时,同时也意味着man的原型person及person的原型Object这一整个链都被指定给了programmer。

__proto__

上面的运行结果中我们可以看到,在每个对象上都有一个属性__proto__,这个属性不是我们指定的,而且这只是在大多数浏览器中这个属性名是__proto__,这个属性名的作用就是记录对象的原型指向。虽然不一定每个浏览器中都是这个属性名,但是相同的是他们必然都有一个属性用来记录对象的原型。当我们要获取一个对象的原型时应该使用ES的标准API: Object.getPrototypeOf()或者Reflect.getPrototypeOf()(ES6),来获取。

原型链关系图

话说本来只是随便画画的,结果就成了你上面看到那个样子,让我们大家一起来找茬,发现有哪个等式不成立的欢迎在评论区打脸。另表达下个人的对JS中的对象起点观念,我认为是Object.prototype指向的这个对象,不认为是null,不接受反驳(傲娇脸)。关于这一点,这里解释一下,在JS中所有对象的原型链追到最后应该都是Object.prototype指向的对象(通过使用下面的指定原型对象为null的方式的对象除外),上图中所表现的想表明的是具备实际使用意义的原型链起点。

Object.prototype.__proto__的值是null也就是表明Object.prototype对象是存在这个属性的,因为它不是undefined,但是这个值应该被理解为一个原型链的结束符号更为准一些,使用这个符号是为了能够更好的实现JS引擎,而不是语言具备的标准特性。

指定对象原型的几种方式

总结了下指定对象的原型的几种方法,大体可分为非标准操作,标准API操作,特定场景操作。为了方便举例,我们设定一个场景,对象a有name属性,其值为a,对象b有color属性,其值为red,现在要求将a指定为b的原型。

非标准操作

这个是最简单粗暴的方式,直接设置对象的__proto__属性,像下面这样

var a = {
name: 'a'};var b = {
id: 'b'};console.log('a的原型是Object.prototype', Object.getPrototypeOf(a) === Object.prototype); //truea.__proto__ = bconsole.log('a的原型是b', Object.getPrototypeOf(a) === b); //true复制代码

这种方式虽然很简单,但是一般不建议在生产代码中使用,这种写法存在兼容性上的问题,这个没有在标准中规定的属性只是靠各浏览器厂商之间的默契维持,兼容性可想而知。其次这种方式在代码的可维护性上不是很好,毕竟不是谁都知道这个属性__proto__(虽然觉得搞前端的同学应该都知道)。

标准API操作

下面来介绍几个指定对象的API(水字数)。

Object.setPrototypeOf

这个是Object对象的一个静态方法,使用方式如下

var a = {
name: 'a'};var b = {
id: 'b'};console.log('a的原型是Object.prototype', Object.getPrototypeOf(a) === Object.prototype); //trueObject.setPrototypeOf(b);console.log('a的原型是b', Object.getPrototypeOf(a) === b); //true复制代码

这个方法使用简单,兼容性好,指定对象的原型首推使用这个方法。

Reflect.setPrototypeOf

此API的方式同上,没什么好说的,只是这个ES6标准中提供的方法。按照阮老师的说法,Reflect对象应该会将Object上定义的一些对象操作方法都接收过来。

Object.create

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 下面上代码

var b = {
id: 'b'};var c = Object.create(b, { name: { value: 'a' }});console.log('a的原型是b', Object.getPrototypeOf(c) === b); //true复制代码

使用Object.create()方法会得到一个指定属性的新对象,这个方法的第一个参数可以指定新得到对象的原型,第二个参数可以指定对象属性值等。

如果你想得到一个纯净的的对象(没有原型),可以在上面三个API使用时指定原型对象那个参数传入null

特定场景操作

将使用new关键词调用函数创建实例对象,通过指定函数的prototype属性来指定对象的方式放在特定场景操作,是因为这种方式不具备上面几种方式的灵活性,不能随时随地的修改对象的原型,使用起来也比较麻烦,怎么用大家应该都懂,这里就不多说了。。

结论

通常我们在谈论原型的时候,应该都是在谈论这种设计模式,这应该是一种思想,一种解决问题的方式,我们对它的理解不应该仅仅停留在对机制的理解上。这种模式的优点在于你只需要在原型上的添加某个属性,指向该原型的所有对象都会具有这个属性,而不用一个一个的去给这些对象添加这个属性。

转载地址:http://fwoll.baihongyu.com/

你可能感兴趣的文章
优化体系结构 - 数据外置减少中间表
查看>>
PAT A1120
查看>>
如何在 Titanic Kaggle Challenge 中获得0.8134分
查看>>
前端新手秘籍丶
查看>>
【跃迁之路】【727天】程序员高效学习方法论探索系列(实验阶段484-2019.2.17)...
查看>>
redux源码解析
查看>>
从理论到实践 全面理解HTTP/2
查看>>
vue2.X 解决同一路由跳转只有参数变化的情况下,组件不刷新的问题
查看>>
深度强化学习DQN(Deep Q Network)原理及例子:如何解决迷宫问题,附源码
查看>>
我是如何设计 Upload 上传组件的
查看>>
weekly 2019-02-15
查看>>
SpringBoot+jsp项目启动出现404
查看>>
Markdown写作中的图床解决方案(基于七牛云、PicGo)
查看>>
再次简单明了总结flex布局,一看就懂...
查看>>
一步步学会用docker部署应用(nodejs版)
查看>>
无root权限新建git仓库进行多人协同工作
查看>>
【跃迁之路】【687天】程序员高效学习方法论探索系列(实验阶段444-2019.1.6)...
查看>>
假装用某米赛尔号的角度看Python面向对象编程
查看>>
RGBA和OPACITY的区别&DISPLAY和VISIBILITY的区别
查看>>
膨胀的template class成员函数
查看>>