学习JavaScript,原型是其中的重难点之一,本来以为自己已经掌握得挺好了,但是有时候遇到一些刁钻的问题,还是有一点迟疑,所以通过这篇博客好好总结一下。
一、三个重要知识
先不论什么原型的原理,牢牢记住这三个重要知识:
- 第一个重要知识——JS公式:对象__proto__ === 其构造函数.prototype
- 第二个重要知识——根公理:Object.prototype是所有对象的直接或间接原型,Object.prototype.__proto__===null
- 第三个重要知识——函数公理:所有函数都是由 Function 构造的,根据第一个重要知识,可以得到 任意函数.__proto__===Function.prototype,任意函数包括 Object、Array、Function。
坚信上面三个重要知识,并且牢牢记住,然后根据上面三个重要知识,来解答一些稍微刁钻的问题:
1、请问 Object.prototype.__proto__===?
答:根据根公理,可以得到答案为 null。
2、请问 Array.__proto__===?
答: Array 是一个函数,根据函数公理,可以知道 Array 是由 Function 构造的,因此再根据第一个重要知识,可以推出 Array.__proto__===Function.prototype。
3、请问 Function.__proto__===?
答:与第2问一样,Function 是由 Function 构造的,因此 Function.__proto__===Function.prototype。
4、请问 Array.prototype.__proto__===?
答:Array.prototype 是一个对象,因此 Array.prototype 是由 Object 构造出来的,所以 Array.__proto__===Object.prototype。
二、JS世界创建之初
根据这三个重要知识,我们可以解决所有问题,现在可以细想一下,JS 世界是怎样创建的?它的创建顺序是什么?
1.首先必须创建一个根对象,这是万物之源,里面有 toString、valueOf 等方法,这个时候根对象并没有名字,存放在一个地址中,假设为#101;
2.然后创建函数的原型,里面有 call、apply 等方法,假设存在一个地址 #208 中,而它的原型指向根对象,值为地址 #101;
3.创建数组的原型,里面有 push、pop 等方法,假设存在一个地址 #404 中,它的原型指向根对象,值为地址 #101;
4.再创建一个可以构造其他函数的构造函数 Function,用 Function.prototype 存储第二步中创建的函数的原型,即地址 #208,此时发现 Function的 __proto 和 prototype 的值一样;
5.然后再依次使用 Function 创建 Object、Array,用 Object.prototype 存储对象的原型,值为 #101,用 Array.prototype 存储数组的原型,值为 #404;
6.最后创建 window 对象,用 “Object” “Array” 属性分别指向刚刚创建的函数,从这一刻起刚刚创建的这两个函数便有了名字,需要注意的是 JS 创建一个对象时,是不会给这个对象名字的。
三、new 的时候
当我们 new Object() 创建 obj1 的时候,new 会将 obj1 的原型 __proto__ 设置为 Object.prototype,也就是 #101;用 new Array() 创建 arr1 的时候,new 会将 arr1 的原型 __proto__ 设置为 Array.prototype,也就是#404,;用 new Function 创建 f1 的时候,new 会将 f1 的原型 __proto__ 设置为 Function.prototype,也就是#208。
四、自定义构造函数的时候
当我们自己定义构造函数 Person 的时候,函数里给 this 加属性,Person 自动创建 prototype 属性和对应的对象,假设地址为 #502,我们在 Person.prototype 上面加属性,用 new Person() 创建对象 p 的时候,new 会将 p 的原型 __proto__ 设为 #502 。
五、图示总结
下面给出一个图示,直观地表达一下: