JS原型和原型链

147 阅读2分钟

构造函数

JavaScript 通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。

function Person (name, age) {
  this.name = name
  this.age = age
}

var person1 = new Person('kari', '18')

person1.name // 'kari'
person1.age // '18'

上面代码中,Person 函数是一个构造函数,函数内部定义了 name 属性和 age 属性,所有实例对象(上例是 person1 )都会生成这两个属性,即这两个属性会定义在实例对象上面。

通过构造函数为实例对象定义属性,虽然很方便,但是有一个缺点。同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。

function Person(name, age) {
  this.name = name
  this.age = age
  this.say = function () {
    console.log('hello')
  };
}

var person1 = new Person('老大', '20')
var person2 = new Person('老二', '18')

person1.say === person2.say
// false

上面代码中,person1person2 是同一个构造函数的两个实例,它们都具有 say 方法。由于 say 方法是生成在每个实例对象上面,所以两个实例就生成了两次。也就是说,每新建一个实例,就会新建一个 say 方法。这既没有必要,又浪费系统资源,因为所有 say 方法都是同样的行为,完全应该共享。

这个问题的解决方法,就是 JavaScript 的原型对象(prototype)。

JS原型

JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。

function Person() {

}
var person1 = new Person()
var person2 = new Person()
person1.name = 'kari'

person1.name    // kari
person2.name    // undefined
function Person() {

}
var person1 = new Person()
var person2 = new Person()
person1.__proto__.name = 'kari'

person1.name    // kari
person2.name    // kari

__proto__ 和 prototype

__proto__ 是每个对象都有的一个属性,而 prototype 是函数才会有的属性。
__proto__ 指向的是当前对象原型对象,而 prototype 指向的是以当前函数作为构造函数构造出来的对象原型对象

通过下面这幅图会更好的理解上面的两句话:

当构造了 Person 函数后,函数便会有一个属性(prototype),指向原型对象

Person 函数被实例成 kari 后,kari 同样也会有一个属性(__proto__)指向原型对象

JS原型链

每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的原型对象(prototype)。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null

根据定义,null 没有原型,并作为这个原型链中的最后一个环节。