理解原型其实是理解原型链

栏目: IT资讯 · 发布时间: 4年前

内容简介:原型和原型链,说是两个词,其实理解一个就可以了。这两个概念是同时存在的,不可能抛开一个去谈论另外一个,或者说这两个概念结合在一起才会发挥作用,甚至原型的存在是因为有原型链的存在,不在原型链上的原型只能称之为对象。先来说说原型链是个什么东东,说起链我们现在脑海中描绘一下自己对链这个字的第一反应是什么,是社会我大哥的大金链?是二哈的大狗链?

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

原型链

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

理解原型其实是理解原型链

是二哈的大狗链?

理解原型其实是理解原型链

还是数据结构链表?

理解原型其实是理解原型链

皮一下,下面我们正经说原型链,原型链从本质上来讲应该是个链表结构,也就是和上面的单链表有点像,我们把上图中的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()得到的对象指定原型对象person
Man.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,不接受反驳(傲娇脸)。

指定对象原型的几种方式

总结了下指定对象的原型的几种方法,大体可分为非标准操作,标准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);    //true
a.__proto__ = b
console.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);    //true
Object.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属性来指定对象的方式放在特定场景操作,是因为这种方式不具备上面几种方式的灵活性,不能随时随地的修改对象的原型,使用起来也比较麻烦,怎么用大家应该都懂,这里就不多说了。。

结论

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

构建之法(第三版)

构建之法(第三版)

邹欣 / 人民邮电出版社 / 2017-6 / 69.00元

软件工程牵涉的范围很广, 同时也是一般院校的同学反映比较空洞乏味的课程。 但是,软件工程 的技术对于投身 IT 产业的学生来说是非常重要的。作者有在世界一流软件企业 20 年的一线软件开 发经验,他在数所高校进行了多年的软件工程教学实践,总结出了在 16 周的时间内让同学们通过 “做 中学 (Learning By Doing)” 掌握实用的软件工程技术的教学计划,并得到高校师生的积极反馈。在此 ......一起来看看 《构建之法(第三版)》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具