很多小伙伴其实对prototype这个东西都是一知半解的,更别说__proto__,今天就让我们来梳理一下他们的关系,首先我们通过一段代码来看下这几个属性的概念
function User(name){
this.name=name
}
User.prototype.getName = function(){
return this.name
}
const user = new User('henry')
console.log(user.getName()) //打印出 henry
console.log(user.constructor) //打印出User(name){this.name=name}这个构造函数
console.log(user.__proto__) //打印出 {getName: ƒ, constructor: ƒ} User.prototype
总结下:
prototype
是构造函数的一个属性。__proto__
是实例的属性,一般意义上的【实例的原型】。- 开发人员对构造函数属性
prototype
的操作,最终会反应到该构造函数的实例上。 - 实例上会有
constructor
属性,指向构造函数
以上,是三者的关系及概念,下面,让我们更清晰的了解他们
首先,我们先看一看以下的内容,方便我们后面的讲解
- 你的 JS 代码还没运行的时候,JS 环境里已经有一个 window 对象了
- window 对象有一个 Object 属性,window.Object 是一个函数对象
- window.Object 这个函数对象有一个重要属性是 prototype
- window.Object.prototype 里面有这么几个属性 toString(函数)、valueOf(函数)
然后我们先来看一段代码
var obj = {}
obj.toString()
为什么这段 obj 会有 toString 这个属性呢?
其实这段代码是这样的:
让 obj 变量指向一个空对象,这个空对象有个 __proto__
属性指向 window.Object.prototype
。
这样你在调用 obj.toString()
的时候,obj 本身没有 toString,就去 obj.__proro__
上面去找 toString。
所以你调用 obj.toString 的时候,实际上调用的是 window.Object.prototype.toString
那么 window.Object.prototype.toString 是怎么获取 obj 的内容的呢?
那是因为 obj.toString() 等价于 obj.toString.call(obj)
, 同时 obj.toString.call(obj) 等价于 window.Object.prototype.toString.call(obj)
,这句话把 obj 传给 toString 了。
同样的,我们来看下数组
var arr = []
arr.push(1) // [1]
和上面一样,我们解析下这段代码:
让 arr 指向一个空数组,然后 arr.__proto__
指向 window.Array.prototype
。
这样你在调用 arr.push
的时候,arr 自身没有 push 属性,就去 arr.__proto__
上找 push
因此 arr.push 实际上是 window.Array.prototype.pusharr.push(1)
等价与 arr.push.call(arr,1)
arr.push.call(arr,1) 等价于 window.Array.prototype.push.call(arr, 1)
再来看看更复杂一点的
var arr = []
arr.valueOf() // []
arr 本身没有 valueOf,于是去 arr.__proto__
上找
arr.__proto__
只有 pop、push 也没有 valueOf,于是去 arr.__proto__.__proto__
上找
arr.__proto__.__proto__
就是 window.Object.prototype
所以 arr.valueOf 其实就是 window.Object.prototype.valueOf
arr.valueOf()等价于 arr.valueOf.call(arr)
arr.valueOf.call(arr) 等价于 window.Object.prototype.valueOf.call(arr)
其实,JavaScript 很优美很简单,理解了就不再那么复杂了:
prototype 指向一块内存,这个内存里面有共用属性__proto__ 指向同一块内存
prototype 和 proto 的不同点在于 prototype 是构造函数的属性,而 proto 是对象的属性
难点在于……构造函数也是对象! 如果没有 prototype,那么共用属性就没有立足之地 如果没有
proto,那么一个对象就不知道自己的共用属性有哪些。
顺便讲解一下伪数组,比如很多人不懂什么是伪数组,很简单:
如果一个数组的 __proto__
直接或间接指向 Array.prototye(用到了数组的共用属性),那么就是真数组
如果一个数组的 __proto__
没有直接或间接指向 Array.prototye,但是拥有部分数组的属性的,那么就是伪数组
*** 最后提醒一句,我们一般说的 原型 是指构造函数的 prototype
,实例的 __proto__