JavaScript中的原型和原型链
上一篇博文总结了JS中的数据类型以及所要注意的事项,这篇博文打算顺着数据类型来理解一下关于原型和原型链的问题。
学习JS中的原型和原型链,要想理解其中的关系和用法,得先搞清楚几个概念。
全局对象
在MDN中,关于全局对象的定义如下:
全局的对象( global objects )或称标准内置对象,不要和 "全局对象(global object)" 混淆。这里说的全局的对象是说在全局作用域里的对象。
ECMAScript 规定全局对象叫做 global,但是浏览器把 window 作为全局对象。
window 的属性就是全局变量。
全局变量
全局变量划分为两类:
- ECMAScript所规定的属性
- global.parseInt
- global.parseFloat
- global.Number
- global.String
- global.Boolean
- global.Object
- 私有属性(即浏览器自己添加的)
- window.alert
- window.prompt
- window.comfirm
- window.console.log
- window.console.dir
- window.document
- window.document.createElement
- window.document.getElementById
所有 API 都可以在 MDN 里找到详细的资料。
现在我们以第一种全局变量为例,说一下全局函数
全局函数
先来看一下这段代码:
var s = "string"
var s2 = new String("string")
变量 s 和 s2 的不同之处在于:两者所对应的内存地址不同,除此之外,s 创建的一个基本类型的值,即只有一个字符串 "string"
;而 s2 创建的是一个对象,里面除了s2的字符串值"string"
之外,还包含了很多可以使用的属性。
如图:
当我们分别对 s 和 s2 进行操作时:
s.charAt(0) //"s"
s2.charAt(0) //"s"
s.name = "here"
s.name //undefined
s2.name = "there"
s2.name //"there"
我们可以看到,在对待 s 时,我们可以像使用对象一样去使用基本类型数据,但是却不能对其属性有效赋值。
之所以能这样去使用基本类型,是因为JavaScript引擎内部在处理对某个基本类型 s进行形如s.sth的操作时,会在内部临时创建一个对应的包装类型(对数字类型来说就是Number类型)的临时对象,并把对基本类型的操作代理到对这个临时对象身上,使得对基本类型的属性访问看起来像对象一样。但是在操作完成后,临时对象就扔掉了,下次再访问时,会重新建立临时对象,当然对之前的临时对象的修改都不会有效了。
现在我们的 s 和 s2 都可以使用类似charAt()、charCodeAt()等等的函数,那么这些函数又保存在哪里呢?我们在对诸如s、s2等对象或是临时对象操作的时候是分别给他们赋予这些函数吗?
接下来我们可以引出理解原型链的关键一步:
__proto__
对应上一个问题,这些函数保存在哪里?
这些函数保存在一个公用属性组成的对象里(暂称为A),
然后让每一个对象的 proto 存储这个 A 的地址。
(__proto__由浏览器生成)
s2.__proto__
如图:
s2.__proto__
代表的是String特有的属性,如果我们要调用的函数在A(即String公有属性)中没有,那还怎么办呢?
他就会上溯一层,来到s2.__proto__.__proto__
(即所有对象的公用属性,暂称为B)中查看:
s2.__proto__.__proto__
如图:
好了,我们的流程已经渐渐清晰:
我们需要对象s2读取一个属性,我们先从s2的本体找,如果他没有被赋予这个属性,那么通过s2.__proto__
所记录的地址,找到A对象,如果这个A对象里面也没有这个属性,那么我们通过A对象中__proto__
所记录的地址,找到B对象,如果B对象是最后一层,那么再找下去就是null。
再来简化一下:
s2
-> s2.__proto__
-> A -> A.__proto__
-> B -> null
现在我们终于可以把原型和原型链带上了:
A 其实就是 String.prototype
B 其实就是 Object.prototype
原型链是:
s2
-> s2.__proto__
-> String.prototype
-> s2.__proto__.__proto__
-> Object.prototype
-> null
自此为止,我们对于原型与原型链的概念已经了解得差不多,每当我们声明一个数据类型(除了undefined和null),他已经存在一条原型链,这条链的起点在于我们刚声明的数据,终点在于null,其中Object.prototype是所有对象的公用属性,我们可以通过__proto__
,来查看属性对象合集中有没有包含某个属性。
总结起来就是:
var 对象 = new 函数();
对象.proto === 对象的构造函数.prototype
再加上一张我用思维导图总结出来的图,希望能帮到对原型、原型链还感到困惑的朋友。