原型与原型链
原型
构造函数创建对象
1 | function Person(name) { |
在这个例子中,Person 就是一个构造函数,我们使用 new 创建了一个实例对象 person。很简单吧,接下来进入正题:
prototype
每个函数或者类(其实本质就是一个函数)都天生自带prototype属性,prototype是对象数据类型。
1 | function Person() { |
那这个函数的prototype属性到底指向什么呢?是这个函数的原型吗?
其实,函数的prototype属性指向了一个对象,这个对象正是调用该构造函数而创建实例的原型,也就是上述例子中person1和person2的原型。
那什么是原型呢?你可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型”继承”属性。
让我们用一张图表示构造函数和实例原型之间的关系:
在这张图中我们用 Object.prototype 表示实例原型。
那么我们该怎么表示实例与实例原型,也就是 person 和 Person.prototype 之间的关系呢,这时候我们就要讲到第二个属性:
proto
这是每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。
为了证明这一点,我们可以在火狐或者谷歌中输入:
1 | function Person() { |
于是我们更新下关系图:
既然实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?
constructor
指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。
为了验证这一点,我们可以尝试:
1 | function Person() { |
所以再更新下关系图:
综上我们已经得出:
1 | function Person() { |
简述
以上内容可以用以下简述
1 | /* |
原型链
原型链是一种查找机制,当前实例用到某个属性或方法,当前实例自己有先用私有的,私有的没有,当前实例会通过__proto__这个属性,往当前这个实例所属类的原型进行查找,找不不到再通过__proto__这个属性往原型的原型进行查找,直到找不到为止就结束了;(找不到是null)
1 | function Person() { |
在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 Daisy。
但是当我们删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.proto ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 Kevin。
但是万一还没有找到呢?原型的原型又是什么呢?
原型的原型
在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:
1 | var obj = new Object(); |
其实原型对象(Person.prototype)就是通过 Object 构造函数生成的,结合之前所讲,实例的 proto 指向构造函数的 prototype ,所以我们再更新下关系图:
那Object.prototype 的原型呢?
null,我们可以打印:
console.log(Object.prototype.__proto__ === null) // true
所以查找属性的时候查到 Object.prototype 就可以停止查找了。最后一张关系图也可以更新为:
顺便还要说一下,图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
后续补充
本质上,所有的对象都是通过
new 函数
的方式创建的;1
2
3
4// 例如
const obj = new Object();
obj.name="fsllala";
obj.age=18;隐式原型(proto)是所有对象都有的,然而(实例)原型也是个对象,所以(实例)原型也有隐式原型(proto);
(实例)原型本身也是一个对象,默认情况下,他是通过
new Object()
创建的;所以原型的隐式原型(proto)指向Object实例对象的显示原型(prototype);上述也可以用下图表示: