这是一个反面教材,它既不支持console.dir()来查看对象,也不允许访问__proto__内部属性。所以,在后面我讲到继承时,需要使用特殊的技巧来避免在我们的代码中使用__proto__内部属性。上面的例子和示意图中,都只说构造函数Object()的prototype属性指向原型对象,没有说构造函数Object()的__proto__属性指向哪里,那么它究竟指向哪里呢?这里先留一点悬念。
下一步,我们自己创建一个构造函数,然后使用这个构造函数创建一个对象,看看它们之间原型的关系,代码是这样的:
functionPerson(name, age, gender){ this.name = name; this.age = age; this.gender = gender; } Person.prototype.sayHello = function(){ return "Hello, my name is " + this.name; }; var somebody = new Person("youxia", 30, "male");
输入到 Chromium 的 JavaScript 控制台中,然后使用console.dir()分别查看构造函数Person()和对象somebody,如下两图:
用图片来表示它们之间的关系,应该是这样的:
我使用蓝色表示构造函数,黄色表示对象,如果是 JavaScript 自带的构造函数和 prototype 对象,则颜色深一些。从上图中可以看出,构造函数Person()有一个prototype属性和一个__proto__属性,__proto__属性的指向依然留悬念,prototype属性指向Person.prototype对象,这是系统在我们定义构造函数Person()的时候,自动创建的一个和构造函数Person()相关联的原型对象,请注意,这个原型对象是和构造函数Person()相关联的原型对象,而不是构造函数Person()的原型对象。当我们使用构造函数Person()创建对象somebody时,somebody的原型就是这个系统自动创建的原型对象Person.prototype,就是说对象somebody的__proto__属性指向原型对象Person.prototype。而这个原型对象中有一个constructor属性,又指回构造函数Person(),形成一个环。这和空对象和构造函数Object()是一样的。而且原型对象Person.prototype的__proto__属性指向Object.prototype。如果在这个图中把空对象和构造函数Object()加进去的话,看起来是这样的:
有点复杂了,是吗?不过这还不算最复杂的,想想看,如果把JavaScript 内置的Object()、Array()、String()、Number()、Boolean()、Function()这一系列的构造函数以及与它们相关联的原型对象都加进去,会是什么情况?每一个构造函数都有一个和它相关联的原型对象,Object()有Object.prototype,Array()有Array.prototype,依此类推。其中最特殊的是Function()和Function.prototype,因为所有的函数和构造函数都是对象,所以所有的函数和构造函数都有构造函数,而这个构造函数就是Function()。也就是说,所有的函数和构造函数都是由Function()生成,包括Function()本身。所以,所有的构造函数的__proto__属性都应该指向Function.prototype,前面留的悬念终于有答案了。如果只考虑构造函数Person()、Object()和Function()及其关联的原型对象,在不解决悬念的情况下,图形是这样的:
可以看到,每一个构造函数和它关联的原型对象构成一个环,而且每一个构造函数的__proto__属性无所指。通过前面的分析我们知道,每一个函数和构造函数的__proto__属性应该都指向Function.prototype。我用红线标出这个关系,结果应该如下图: