原型链1:原型与原型链

Why???

在上面写了一篇博文介绍了普通类型和对象的区别。下面就深究一下原理。

公用属性(原型)

什么是公共属性?
__proto__:object //__proto__这个属性不用写就有,每一个对象的 __proto__存储「公用属性组成的对象」的地址。

所有对象都有 toString 和 valueOf 属性,那么我们是否有必要给每个对象一个 toString 和 valueOf 呢?

看图理解JS标准库里几个构造函数之间的关系

image.png

每个对象都有toString()valueOf()函数,如果每个对象里都存这样的相同的函数,那会浪费内存.
问题解决方法:原型链

toString()valueOf()等函数单独存放在一个对象里,原来对象里的toString()valueOf()只存地址,指向这个地址

image.png
image.png

JS 的做法是把 toString 和 valueOf 放在一个对象里(暂且叫做公用属性组成的对象)
然后让每一个对象的 __proto__存储这个公用属性组成的对象的地址。

image.png
image.png

通过四个全局对象来了解原型和原型链
这四个全局对象为:Number()、String()、Boolean()、Object()。
上述四个对象都可以通过下边两种方法实现:

var n1=1
var n2=new Number(1)
var s1='sss'
var s2=new String('sss')
var b1=true
var b2=new Boolean(true)
var o1={}
var o2=new Object()

他们的区别在于内存:

1.toString()会报错,但是这里我们通过n1.toString()返回的就是字符串'1'n1.toString()方法不会报错,但是n1只是通过赋值的方法的来的,并没有.toString()方法。这个时候就是JS通过创建临时对象tempvar temp=new Number(1)在这个临时对象中对n1的值进行.toString()方法,再将得到的返回给n1.toString(),临时对象用完就自动抹杀,被垃圾回收。
在上面的博文中,曾写过:我们对n1进行n1.xxx=2这会不会报错,结果是不会报错。原因就是用临时方法在heap内存上创建的,但是再取n1.xxx时为undefined。就验证了用完就抹杀,即不存在的原理。
为什么new Number().toString()方法呢?我们打印出n2看看他有没有这个属性。

image.png

可以看到除了打印出n2的值还有一个__proto__:Number,点开这个属性可以看到很多属性,其中就包括.toString(),所以n2可以调用.toString()方法。
__proto__:Number所表示的就是number共有的属性。

从上面的图中发现,在这个__proto__:Number中还有一个__protp__属性。

image.png

image.png

此时在图中我们看到n2.__proto__===Number.prototype的结果为true。就是指n2__proto__指向了Number.prototype,即Number的共有属性中去,可以从第一张图中可以看到n2的__proto__中还有一个__proto__:Object的隐藏属性。点开就可以看到关于Object的共有属性。

根据同样的方法,依次获取String,Boolean,Object,发现:

image.png
image.png

综上总结:
以上四个全局函数构造的对象都有一些公有的属性,比如:toString()、valueOf()。不可能每声明一个对象都在对象里加上这些公有属性,这样内存太浪费,Js的做法是把共用属性放在一起,然后每个对象都有一个键名为proto的键,它的值就是这些共用属性所在的内存地址。
但是Number、String和Boolean这三个全局函数都有一些自己的属性,比如Number函数有toFixed()、toExponential()、toString(16)等等这些只有Number函数才有的属性,并不属于Object共有的属性。所以Js的方法是在Number对象的proto中放Number共有属性的地址,在Number共有属性对象的proto中放对象共有属性的地址,对象Object的proto中放的是Null,如下图所示:

image.png

我们可以总结出来,当我们new出来一个对象的时候,再去使用一个方法时,会首先从它自身的构造函数(或者系统自带函数)的原型对象(函数.prototype)中去找,如果没有改属性是就会通过__proto__属性去下一个Object共有属性中去寻找,直到最后为null

image.png
var n = new Number();
n.__proto__===Number.prototype//true
n.__proto__.__proto__===Object.prototype//true
n.__proto__.__proto__.__proto__===null//true

流程是这样的

  1. 浏览器先看n是不是对象,不是做临时转换。
  2. 是的话就先去看看n对应的函数公共类型里找有没有toString()这个操作符
  3. 如果没有,就进入prote对应的公共属性里找有没有toString()操作符
  4. 有的话,就调用这个toString()

这样的,形成的穿过多个节点的流程,就叫原型链
原型===共有属性

image.png

当解析一句代码时,如果这个不是对象,就包装成一个对象,自动生成__proto__

所有对象的公有属性(也叫原型): Object.prototype;

所有number的公有属性(也叫原型): Number.prototype

所有string的公有属性(也叫原型): String.prototype

所有boolean的公有属性(也叫原型): Boolean.prototype

image.png

prototype__proto__的区别:前者的函数的属性,后者是对象的属性。
__proto__prototype的关系
[__proto__指向prototype]

内存图理解

var o1 = new Number(8)
var o2 = 8
o1 === o2 // false
o1.toString === o2.toString  //  true
因为他们调用的方法是一样的

image.png

o1 是一个对象,在内存中的地址是125
o2 不是对象,是Number类型的
所以在第三行语句中 o1 ===o2 答案是false
但是在第四行的时候,o1 使用了o1.toString()的时候,他会自动生成一个temp的对象,并且去调用。所以它会在内存中生成一个地址为50的对象,但是因为125地址和50地址都是共有属性中的toString() ,所以最后他们的结果都是true

重要公式-原型与原型链

无代码的时候,即为下面这样,浏览器已经将其初始化好了.

原型图

可以看到prototype用来指向这些共有属性的,不然这些共有属性就被垃圾回收了,所以要用一个线来牵引着.

写代码之后

image.png

在看这张图前,我们先考虑一下浏览器的垃圾回收机制,垃圾回收会回收没有被引用的对象。
这些全局对象如果不被引用的话,就会被浏览器回收掉,如何避免这些全局属性被回收掉呢?
浏览器在打开的时候,就会创建一个名为window的全局属性,这个全局属性包含了所有全局函数的地址值。
这些地址又引用了该函数特有的公共属性(对象),每个函数特有的公共属性(对象)又用_prote_来储存Object对象的地址

所以:

  • prototype是浏览器开始就准备好了的,用来防止共有属性被垃圾回收的,
  • __proto__是在开始写代码的时候用来引用共有函数的.
  • String.prototype是String的公用属性的引用,是JS在初始化的时候就已经存在的,用他是因为如果不用他,那么公用属性就跑了,被垃圾回收了
    *'s'.__proto__String的公用属性的引用,是在声明新对象的时候存在的,有他是因为我要用他,用公用属性

共同点就是都是公共属性的引用.

var o1 = {};

o1.__proto__ ===  Object.prototype//true
Number.prototype.__proto__===Object.prototype//true
var s1 = new String('s1');

s1.__proto__ === String.prototype//true
image.png
String.prototype.__proto__ ===Object.prototype//true

function DOG(name){
    this.name = name;
}
DOG.prototype.__proto__ === Object.prototype//true

对象与构造函数

image.png
var 对象 = new 函数()
对象.__proto__ === 对象的构造函数.prototype

形式

总结的式子

由此,我们可以得出一个公式,结合原型解释图理解

image.png
var number = new Number()
number.__proto__ = Number.prototype
Number.__proto__ = Function.prototype // 因为 Number 是 Function 的实例

var object = new Object()
object.__proto__ = Object.prototype
Object.__proto__ = Function.prototype // 因为 Object 是 Function 的实例

var function = new Function()
function.__proto__ = Function.prototype
Function.__proto__ == Function.prototye // 因为 Function 是 Function 的实例!
image.png
推论图

只有函数才能有prototype
两个属性对比:
共同点:存的地址相同,都指向同一个对象
不同点:一个是对象的属性,一个是函数的属性

面试题

  1. '1'.__proto__
    答:'1'会创建一个临时String对象,然后指向String.prototype
  2. 函数.prototype 是一个对象,那么
var obj = 函数.prototype;
obj.__proto__ === Object.prototype;//true
函数.prototype.__proto__ === Object.prototype;//true

成立(可以看上图无代码的时候)

Number.prototype.__proto__ === Object.prototype
//true
String.prototype.__proto__ === Object.prototype
//true
Boolean.prototype.__proto__ === Object.prototype
//true

JS 原型是什么?

答:举例
var a = [1,2,3]
只有0、1、2、length 4 个key
为什么可以 a.push(4) ,push 是哪来的?
a.__proto__ === Array.prototype(a是实例数组对象,Array是构造函数)
push函数 就是沿着 a.__proto__找到的,也就是 Array.prototype.push
Array.prototype 还有很多方法,如 join、pop、slice、splice、concat
Array.prototype 就是 a 的原型(proto)

举完例子后用new对象举例,说给面试官听:
比如说

  1. 我们新创建一个构造函数
function Person() {}
  1. 然后根据构造函数构造一个新对象
var person1 = new Person();
  1. 每个函数都有一个 prototype 属性,这个构造函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型。
  2. 当我们给Person的prototype的name属性赋值为'Kevin'
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin

每一个新的实例对象对象都会从原型"继承"属性,实例对象拥有该原型的所有属性。

  1. 说白了,原型就是 构造函数 用来 构造 新实例 的 模板对象。
  2. 这就是原型。

开始解释原型链
那么我们该怎么表示实例与实例原型,也就是 person1 和 Person.prototype 之间的关系呢,这时候我们就要讲到第二个属性__proto__

什么是原型链?

参考这篇博文什么是 JS 原型链?
先回答什么是原型。在上面,然后继续从proto开始往下说。
说:
JavaScript对象除了 null 都具有的一个属性,叫__proto__,这个属性会指向该对象的原型对象。
当读取实例的属性时,如果找不到,就会通过__proto__查找原型中的属性,如果还查不到,就去找原型的原型。
例如Person.prototype这个原型的原型就是Object这个构造函数的prototype,既Object.prototype这个原型对象。然后,Person.prototype.__proto__就指向Object.prototype这个原型。然后Object.prototype原型是null
这些原型对象通过__proto__像链子一样连起来,就叫做原型链。
然后给面试官画:

链子上都画上__proto__
person1----->Person.prototype----->Object.prototype----->null

Array.prototype----->Object.prototype----->null

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容