JS原型链 (prototype chain)

prototype 和原型链

如下代码中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Box(){
this.a=[]; //实例变量
this.fn=function(){ //实例方法
}
}
var box1=new Box();
box1.a.push(1);
box1.fn={};
console.log(box1.a); //[1]
console.log(typeof box1.fn); //object

var box2=new Box();
console.log(box2.a); //[]
console.log(typeof box2.fn); //function

在这段代码中,我们注意到,当修改了box1fn之后box2fn并没有发生改变。由于数组和函数都是对象,是引用类型,所以说明此处不同对象的属性和方法同名但并不指向同一个引用而是对类定义的属性和方法的拷贝。这对于属性来说很正常,但是对于函数来说就很不合理,因为同一个类的对象中的函数做的都是相同的事情,却保存着不同的引用,占据了不该占用的空间。protoype 就是为了解决这个问题出现的。

简单来讲 prototype 是一个指针,指向一个原型对象。这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。可以理解为一类对象的公共部分(是真正的公共部分,因为每一类对象都保持对同一地址的原型对象的引用)。这样的话,我们可以把一类对象的公共的函数挂在 prototype 中,这样的话,每一个实例都只保留一个引用而不是复制整个函数。在创建实例比较多的应用中可以节省内存。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Animal(name)   //积累构造函数
{
this.name = name;//设置对象属性
}

Animal.prototype.behavior = function() //给基类构造函数的prototype添加behavior方法
{
alert("this is a "+this.name);
}

var Dog = new Animal("dog");//创建Dog对象
var Cat = new Animal("cat");//创建Cat对象

Dog.behavior();//通过Dog对象直接调用behavior方法
Cat.behavior();//output "this is a cat"

alert(Dog.behavior==Cat.behavior);//output true;

注意只有函数拥有可以直接调用的 prototype,且被初始化为空值,其他对象拥有的是_proto_这个内部属性,不可以直接使用。但本质来讲同一类对象的_proto_和类的prototype指向的是同一个对象。

原型链:当从一个对象那里调取属性或方法时,如果该对象自身不存在这样的属性或方法,就会去自己关联的prototype对象那里寻找,如果prototype没有,就会去prototype关联的前辈prototype那里寻找,如果再没有则继续查找Prototype.Prototype引用的对象,依次类推,直到Prototype.….Prototype为undefined(Object的Prototype就是undefined)从而形成了所谓的“原型链”。

总结一下,当发生对象的调用时,将会执行以下步骤:
setp1. 搜索本对象中有无此方法,如果有直接调用,没有就进行下一步。
step2. 搜索prototype中,如果有被调用的对象,直接调用。没有的话就去prototype.prototype中去寻找。
step3.如果找到Object.prototype还没有找到,就报出undefine。

prototype模式

使用prototype属性实现继承。
如果”猫”的prototype对象,指向一个Animal的实例,那么所有”猫”的实例,就能继承Animal了。

1
2
3
4
  Cat.prototype = new Animal();
  Cat.prototype.constructor = Cat;
  var cat1 = new Cat("大毛","黄色");
  console.log(cat1.species); // 动物

代码的第一行,将Cat的prototype对象指向一个Animal的实例。

1
  Cat.prototype = new Animal();

第二行是需要注意的地方。

1
Cat.prototype.constructor = Cat;

对象的 prototype 中都有一个 constructor 属性。这个属性指向了对象的构造方法。Cat.prototype 的 constructor 原本应该指向构造函数 Cat。但是经过 Cat.prototype = new Animal(); 之后,constructor 指向了 Animal,然而 cat1 对象显然是通过 Cat 方法来进行创建的,如果不进行修改将造成混乱。这里一定要手动指定一次。
这是很重要的一点,编程时务必要遵守,即如果替换了prototype对象,

1
  o.prototype = {};

那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。

1
o.prototype.constructor = o;

DEMO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var counter = 0;

function Box(){}

Box.prototype.fn = function () {
counter++;
console.log(counter);
};
Box.prototype.a = [];

var box1=new Box();
box1.a.push(1);
console.log(box1.a); //[1]
box1.fn();//1

var box2=new Box();
box2.a.push(1);
console.log(box2.a); //[ 1, 1 ]
box2.fn();//2

其他参见:
JS的prototype和proto