一张图理解原型链
在上图中,Parent()
是构造函数,p1是由 Parent()
创建的对象。
前置知识
任何函数都可以作为 构造函数。当该函数通过 new
关键字调用的时候,我们就称之为 构造函数。
看下图例子:
var Parent = function(){
}
//定义一个函数,那它只是一个普通的函数,我们不能称它为构造函数
var instance = new Parent();
//这时这个Parent就不是普通的函数了,它现在是一个构造函数。因为通过new关键字调用了它
//创建了一个Parent构造函数的实例 instance
理解了上文之后,我们再来理解三个概念:__proto__
、prototype
、 constructor
。
prototype属性
prototype
是函数特有的属性(有人可能有会疑问,函数为什么有属性?因为在JS中函数也是一个对象,所以它有属性。)。prototype
设计的目的是为了实现继承。
一句话概括prototype
的作用:让某一个构造函数实例化的所有对象可以找到公共的方法和属性。
例如上图中的Parent()
构造函数,我如果给它的prototype属性设置一个name
。那么所有通过Parent()
构造出来的实例,比如p1、p2
,都会有name
属性。
Parent.prototype.name = "所有Parent的实例都可以读取到我";
let p1 = new Parent();
let p2 = new Parent();
p1.name //"所有Parent的实例都可以读取到我";
p2.name //"所有Parent的实例都可以读取到我";
你可能会有疑问,我并没有给p1、p2
这两个对象设置 name 属性,为什么它会有 name 属性?请看下节 proto 讲解
proto属性
__proto__
属性是对象特有的属性。它表示当前对象的原型对象是谁。
对象的__proto__
属性就是它的构造函数的 prototype
属性。可以看如下代码,其中Parent是构造函数,p1
是由 parent()
实例化得到的对象。
p1.__proto__ === Parent.prototype; // true
所以我们可以解答刚刚的问题了:为什么我们可以调用很多我们没有定义的方法和属性?这些方法和属性是哪来的呢?
答案是,在原型链上找到的。当我们调用p1.toString()
的时候,先在 p1 对象本身寻找,没有找到则通过p1.__proto__
找到了原型对象Parent.prototype
,也没有找到,又通过Parent.prototype.__proto__
找到了上一层原型对象Object.prototype
。在这一层找到了toString
方法。于是 p1 可以调用该toString
方法。
如果追问:Object.prototype
上也没找到怎么办?那么继续寻找Object.prototype.__proto__
。但是Object.prototype.__proto__ === null
,所以最终会返回 null
。
constructor属性
constructor
是对象特有的属性。它表示当前对象的 构造函数。在刚刚的例子中,我们使用构造函数 Parent()
创建了实例对象 p1
。因此 p1的constructor
就是 Parent()
。可以 console.log() 试试。
console.log(p1.constructor); // ƒ Parent(){}
我们知道,JS中函数也是对象。那么函数是否也有constructor
属性?我们发现构造函数 Parent()
也有constructor
属性。可以 console.log() 试试。
console.log(Parent.constructor); // ƒ Function() { [native code] }
如果继续追问:Function()
是否也有constructor
属性?可以 console.log() 试试。我们发现,Function函数的构造函数就是本身了。
console.log(Function.constructor); // ƒ Function() { [native code] }