先看一个简单的例子:
function Foo() {...};
let f1 = new Foo();
以上代码表示创建一个构造函数 Foo(),并用 new 关键字实例化该构造函数得到一个实例化对象 f1。
这里稍微补充一下 new 操作符将函数作为构造器进行调用时的过程:函数被调用,然后新创建一个对象,并且成了函数的上下文(也就是此时函数内部的 this 是指向该新创建的对象,这意味着我们可以在构造器函数内部通过 this 参数初始化值),最后返回该新对象的引用。
虽然是简简单单的两行代码,然而它们背后的关系却是错综复杂的,如下图所示:
图例说明:
红色箭头表示 __proto__ 属性指向、
绿色箭头表示 prototype 属性的指向、
棕色实线箭头表示本身具有的 constructor 属性的指向,
棕色虚线箭头表示继承而来的 constructor 属性的指向;
蓝色方块表示对象,浅绿色方块表示函数
(这里为了更好看清,Foo() 仅代表是函数,并不是指执行函数 Foo 后得到的结果,图中的其他函数同理)。
1、__proto__ 属性
首先牢记两点:
①__proto__
和 constructor
属性是对象所独有的;
② prototype
属性是函数所独有的。但是由于JS中函数也是一种对象,所以函数也拥有 __proto__
和 constructor
属性。
上图有点复杂,按照属性分别拆开,然后进行分析:
__proto__ 属性是对象所独有的,可以看到 __proto__ 属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象)。
那么这个属性的作用是什么呢?它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的 __proto__ 属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往上找,直至原型链顶端null 结束。
以上这种通过 __proto__ 属性来连接对象到 null 之间一条链即我们所谓的原型链。
而我们平时调用的字符串方法、数组方法、对象方法、函数方法等都是靠 __proto__ 继承而来的。
2、prototype 属性
prototype 属性是函数所独有的,它是从一个函数指向一个对象。也就是指向这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象。由此可知:f1.__proto__ === Foo.prototype,它们两个完全一样。
那 prototype 属性的作用又是什么呢?它的作用就是让该函数所实例化的所有对象都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的 prototype 对象。
3、constructor 属性
constructor 属性是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数。
每个对象都有构造函数(本身拥有或继承而来,继承而来的要结合 __proto__ 属性查看会更清楚点,如下图所示),从上图中可以看出 Function 这个对象比较特殊,它的构造函数就是它自己(因为 Function 可以看成是一个函数,也可以是一个对象),所有函数和对象最终都是由 Function 构造函数得来,所以 constructor 属性的终点就是 Function 这个函数。
总结:
1、牢记两点:①__proto__和 constructor属性是对象所独有的;② prototype 属性是函数所独有的,因为函数也是一种对象,所以函数也拥有 __proto__ 和 constructor 属性。
2、 __proto__ 属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的 __proto__ 属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往上找,直至原型链顶端 null 结束。而通过 __proto__ 属性来连接对象到 null 之间一条链即我们所谓的原型链。
3、prototype 属性的作用就是同一个构造函数所实例化的所有对象都可以找到公用的属性和方法。
4、constructor属性的含义就是指向该对象的构造函数,所有对象最终的构造函数都指向 Function。