原型的由来
考虑如下代码
var s = '1'
var n = 1
var obj = {}
s.toString()
n.toString()
obj.toString()
直观上,我们很容易觉得s, n, obj
都应该共享toString()
函数,但是如果有很多个变量呢?那不是要每个变量都对应一个toString()
,这也太麻烦了。所以JS就提出了不如我们将一些共有的变量或者函数放在一起,干脆就用一个对象存起来好了。
但是是放在一起也有问题,字符串不能有parseInt()
的函数,而数字不应该有endWith()
函数呀,所以对这个共同的对象进行一下分类变成了我们所知道的Number, String, Boolean, Object
等。这个共同的对象就是我们所说的原型。
原型链
好了,现在我们有一个共同的地方可以放东西了,那怎么取这些东西呢?难道我们每个变量都要存对应的方法名字?不,这里我们在每个对象里存一个指向原型的地址就好了,当我们访问一些共有的属性时,用这个引用不好了了么?这个引用就是我们所说的__proto__
// 访问对应的toString方法
var num = new Number(1)
num.toFixed()
===
// 等价于
num.toFixed()
那么对于toString()
方法怎么使用呢?还记得我们之前说的要分类么?而toString()
又是一个Number和String共有的属性,所以这就是原型链的由来
var num = new Number(1)
num.toString()
// 等价于
num.__proto__(Number.prototype找不到,那就去更加共有的地方去找吧).__proto__(Object.prototype,在这就定义了toString()方法,就用这个了)
对于别的共有函数访问也是一样的,慢慢地,我们就发现这其实是一棵大树
Object
/ | \
Number String Boolean ...
当当前对象找不到某个属性值或者函数时,就从__proto__
指向的对象去找,找不到就继续找__proto__
里的__proto__
,直到找不到为止,就像我们学算法的DFS一样。
关键的公式
说了一堆有点晕,其实很容易理解
对象.__proto__ === 创造者.prototype
=>
// Number() 创造了数字
num.__proto__ === Number.prototype // true
// Boolean() 创造了布尔
bool__proto__ === Boolean.prototype // true
// String() 创造了字符串
str__proto__ === String.prototype // true
// Object() 创造了对象
obj__proto__ === Object.prototype // true
注意:函数有奇怪的地方
Function = {
__proto__: Function 共用的属性 -> prototype
prototype: Function 共用的属性 -> __proto__
}
注意:Number, String, Object 是构造函数,他们所指向的原型是Function.prototype,因为是由Function来构造他们的
// 构造Object的是一个构造函数,所以是Function.prototype
Object.__proto__ === Function.prototype // true
// 和下面的不一样,下面的是由Object构造出来的
obj.__proto__ === Object.prototype // true
// 因为Object已经是有最高级的存放共有属性的地方了,再往上就没东西找了
Object.prototype.__proto__ === null