首先,我们创建了一个User函数,和User的实例化对象。
function User() {}
let hd = new User();
console.dir(User);
console.log(hd);
然后我们分别打印User和hd,分别看看他们两之间的原型关系。
首先我们先来看看User这个函数,我们可以看到有prototype(原型对象)和__proto__(对象原型)两个属性,证明了User这个函数在不同的情况扮演着不同的作用。这就好比我们在生活中,在公司的时候我们是员工,回到家我们是父母的孩子。当函数扮演着构造函数的时候,他的父级就为prototype,当函数作为对象调用的时候,他的父级就为__proto__指向的构造函数的原型对象。或者我们可以理解为,__proto__是服务于我们函数对象的,而prototype一般是服务于函数实例化的对象。
让我们再来看看hd的打印结果。
因为hd是一个实例化对象,因此他只有__proto__指向的这个父级。
此时我们可以画一幅关系图帮助我们理解。
我们的User函数因为他扮演者构造函数的角色,因此他有一个父级原型prototype,而我们新创建的对象hd的时候系统会自动为我们的hd对象创建一个父级原型,指定的就是我们的构造函数,换句话说,我们的User构造函数跟hd对象使用的是同一个原型。
console.log(User.prototype === hd.__proto__) //true
我们可以通过打印也能证明上述图表是成立的。
因此我们知道,每一个对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象又__proto__原型的存在。__proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype。
当我们明白了这三者的关系之后,我们继续看一下User的原型对象中的__proto__对象原型指的又是什么呢?
我们给Object的原型对象添加一个show方法,然后再打印User函数看看。
Object.prototype.show=function () {};
function User() {}
console.dir(User)
从打印结果中可以得出结论,User函数中的prototype(原型对象)与__proto__(对象原型)的指向相同,且都指向与Object的原型对象。
console.log(User.prototype.__proto__ === User.__proto__.__proto__) //true
最后我们通过打印Object的原型对象的对象原型指向是null
console.log(Object.prototype.__proto__) //null
因此我们总结得出下图。
最下面的倒三角就是我们刚刚的User与hd对象关系图。
tips:我们用字面量的形式声明变量可以理解为用构造函数声明变量的简写,因此他们的对象原型都指向构造函数的原型对象。
let hd = {}; //new Object
console.log(hd.__proto__ === Object.prototype); //true
let str = ''; // new string
console.log(str.__proto__ === String.prototype); //true
let arr = []; //new Array
console.log(arr.__proto__ === Array.prototype); //true
let bool = true;
console.log(bool.__proto__ === Boolean.prototype); //true