本文写于 2020 年 5 月 23 日
首先看一个问题:let obj = { name: '孙悟空' }的原型是谁?
是obj.__proto__。
let arr = [1, 2, 3]的原型是谁?
是arr.__proto__。
Object的原型谁是?
是Object.__proto__。
他们都不是prototype,只是正好__proto__等价于某个函数的prototype。
- 对象的
__proto__属性,就是其构造函数的prototype; - 任何函数的
__proto__都是Function的prototype——包括Object()函数、Array()函数、Function()函数等等; Object.prototype是所有对象的直接或者间接原型。
这里有个问题,如果说,Function()函数是造世的函数,那为什么说Object.prototype是所有对象的直接或者间接原型呢?
其实很简单,因为Function()只是构造出了Object.prototype(指针),但是并没有构造出——Object.prototype所指向的真正对象。
让我们从头开始一下:
当我们的 JS 代码还没运行的时候,JS 环境里已经有了一个 window 对象。
window对象有一个Object属性,window.Object是一个函数;window.Object这个函数有一个重要属性是prototype;window.Object.prototype里面有这么几个属性toString(函数)、valueOf(函数)。
然后我们写一点代码
var obj = {}
obj.toString()
这句代码做了啥?
为什么 obj 有 toString() 属性?我们分明没有给她一个toString()方法呀!
在 JS 中,这句话大概是让 obj 变量指向一个空对象,这个空对象有个 __proto__ 属性指向 window.Object.prototype。
这样你在调用 obj.toString() 的时候,obj 本身没有 toString,就会去 obj.__proto__ 上面去找 toString!
所以调用 obj.toString 的时候,实际上调用的是 window.Object.prototype.toString。
那么问题又来了:
window.Object.prototype.toString 是怎么获取 obj 的内容的呢?
**因为 obj.toString() 等价于 obj.toString.call(obj)!**JS 又偷偷的帮我们做事了!
同时 obj.toString.call(obj) 等价于 window.Object.prototype.toString.call(obj)。
正是这样,JS 就悄咪咪的完成了 obj.toString()。
再来看一下数组:
var arr = []
arr.push(1) // [1]
其实和对象是一样的,并且 JS 的数组本来就是对象伪装的!
var arr = [] 会让 arr 指向一个空对象,然后 arr.__proto__ 指向 window.Array.prototype。
(其实 arr 还有一个 length: 0属性)
所以我们在调用 arr.push 的时候,arr 发现自身没有 push 属性,就前往 arr.__proto__ 上找 push。
因此 arr.push 实际上是 window.Array.prototype.push。
并且:
arr.push(1)等价与arr.push.call(arr,1)arr.push.call(arr,1)等价于window.Array.prototype.push.call(arr, 1)
看,JavaScript 的原型模式其实很优美很简单!
反证一下:
假设我们把 __proto__ 去掉,那么
var obj = {}
obj.toString() // 报错,没有 toString 方法
所以只能这样声明一个对象:
var obj = {
toString: window.Object.prototype.toString,
valueOf: window.Object.prototype.valueOf
}
obj.toString() // '[object Object]'
现在知道 __proto__ 帮你省多少代码了吧?
假设我们删掉 prototype,包括 window.Object.prototype 和 window.Array.prototype。
那么 window.Object.prototype.toString 也一并被删除了。
然后我们基本就没法写代码了……全都得自己重新来做!
var obj = {}
obj.toString() // toString 不存在,因为 toString 没有定义过啊
prototype 的意义就是把共有属性预先定义好,给之后的对象用。
总结:
prototype指向一块内存,这个内存里面有共用属性,而__proto__指向同一块内存;prototype和__proto__的不同点在于:prototype是构造函数的属性,而__proto__是对象的属性;- 如果没有
prototype,那么共用属性就没有立足之地; - 如果没有
__proto__,那么一个对象就不知道自己的共用属性有哪些。
(完)