javascript之prototype

前言

本文将围绕 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

这样避免了实例数据相互干扰,实现了方法复用。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注