前言
本文将围绕 prototype
, __proto__
, constructor
三个属性展来,它们的关系如下图:
可见 prototype 对象 是构造函数 myFunc 的一个属性,它有个默认属性 constructor 指向 myFunc 构造函数,而 myFunc 的每个实例对象都有个 proto 属性它指向 myFunc 的prototype,下文将逐个分析它们。
prototype
prototype 这个属性是函数独有的,当你声明一个函数JS引擎会自动为这个函数加上 prototype 属性:
function myFunc() {
...
}
console.log(myFunc.prototype)
在chrome控制台打印出来可以看到:
可见这个 prototype 对象内部有两个属性 constructor 跟 proto ,而且可以看到constructor指向的是它的构造函数 myFunc,proto属性指向的是JS的顶层对象 Object。
proto
这个属性是抽像的,使用中并不会去显示调用它,它由ES标准定义由浏览器厂商自己实现,在chrome中它叫 __proto__
, JS中每个对像都有这个属性,它指向了当前对象的构造函数的prototype。
实际作用是防问当前对象属性时,若不存在则会去 proto 上去找,而 proto 又指向了当前对象的构造函数的prototype,所以就是当一个实例不存在某个属性时会往构造函数的 prototype 上查找。
function myFunc() {
}
myFunc.prototype.name = 'bwx'
var f1 = new myFunc()
console.log(f1.name) // bwx
实例 f1 并不存在 name 字段但是也输出了字符串 ‘bwx’ , 因为它通过 proto 属性防问到了构造函数 myFunc 的 prototype 上的 name 属性。
优先级
实例上的属性优先级要高于 prototype 上的属性。
function myFunc() {
this.name = 'mit'
}
myFunc.prototype.name = 'bwx'
var f1 = new myFunc()
console.log(f1.name) // mit
可见实例上已经存在name属性了而不会再去 prototype 上查找了,所以输出了字符串 ‘mit’。
相同引用
同一个构造函数 new 出来的实例引用的 prototype 都是同一个对象, 也就是说一个构造函数的 prototype 只会在内存中创建一次。
function myFunc() {
}
myFunc.prototype.num = 0
var f1 = new myFunc()
var f2 = new myFunc()
f1.__proto__.num ++
f2.__proto__.num ++
console.log(f1.num) // 2
console.log(f2.num) // 2
console.log(f1.__proto__ === f2.__proto__) // true
可见第15行 f1、f2 两个实例的 prototype 完全相等,prototype上的num初始值是 0,我们分别对 f1、f2的 proto .num 进行了一次自增,最后输出它们的 num 值都是 2,因为自增操作的对象是 prototype。
这里为什么对实例的 proto 下的num自境,而不是对实例的 num 自增,是因为实例上的属性优先级要高于 prototype 上的属性,如进行 f1.num ++
操作相当给f1实例添加了一个 num 属性,从而会优先防问实例上的 num 属性, 而不会操作原型上的 num 属性。
function myFunc() {
}
myFunc.prototype.num = 0
var f1 = new myFunc()
var f2 = new myFunc()
f1.num ++
f1.__proto__.num ++
f2.__proto__.num ++
console.log(f1.num) // 1
console.log(f2.num) // 2
可见 f1 的 num 进行了两次 ++ 操作可值还是 1,因为第10行,给实例本身添加了一个 num 属,会优先使用实例本身的 num,而第11、12行操作的是实例共享的 prototype 上的属性,一共对 prototype 上的 num 自增了两次,所以 f2 的 num 值是2。
总结
一个构造函数的 prototype, 被它的所有实例同享,构造函数体中的属性为每个实例独有,因此实际开发中,我们可以将不涉及数据的方法放到 prototype 中, 将存储数据的属性放到构造函数体中。
function myFunc() {
this.num = 0
}
myFunc.prototype.increment = function() {
this.num ++
}
var f1 = new myFunc()
var f2 = new myFunc()
f1.increment()
f2.increment()
console.log(f1.num) // 1
console.log(f2.num) // 1
这样避免了实例数据相互干扰,实现了方法复用。