prototype小结

prototye小结

写在前面:虽然js没有类,那些大写字母开头的标识符,其实是普通的函数。但是当它们与new一起使用时,它们就是所谓的构造函数。本文虽然使用“类”等字眼,但是并不是指java里面那种声明式的类。

prototype并不是特别有技术含量的东西,但是特别绕,所以写下来。

在oo的世界里,对于同一个类的不同实例,它们有自己的状态,但是,它们的方法(行为)应该是共通的,所以,对于同一个类,应该要有一个公共的地方来放置方法,也就是说,同一个类的不同对象,应该是共享方法的

为了让事情先不要那么复杂,现阶段,首先假设我们的所有对象都没有任何状态。这个假设十分重要,因为后面会打破这假设来说明一些问题。

先创造一个Animal类,并且,Animal都是会eat的

var Animal = function() {
    this.eat = function() {
       alert("eat");
    }
}

然后我们使用这个Animal来创造两个实例

var animal_1 = new Animal() 
var animal_2 = new Animal() 
animal_1.eat() //#1 
animal_2.eat() //#2 
alert(animal_1.eat === animal_2.eat) //#3

看看输出,#3 会得到“false”,也就是说,animal_1和animal_2的eat方法其实是两个方法,它们在内存里占据两份独立的空间。这个不符合我们上面说的“共享方法“的原则。

为了改进,我们需要有一个专门的对象p,用来存储eat方法,并且,每一个使用Animal函数创建出来的实例,都应该要能通过某种方法引用到这个p。

在javascript里,这个专门的对象p,就是Animal.prototype

每一个构造函数都有一个叫prototype的属性,这个属性所指向的对象是一个普通的javascript对象,这个对象就是专门用来放置(同一个类的)实例之间共享的东西。

回到我们的例子来说,Animal的eat方法是被所有Animal的实例共享的,所以,我们应该把eat方法放在Animal.prototype里面去。 因此,我们的代码要修改成这样子:

var Animal = function() { }; 
Animal.prototype.eat = function() {
    alert("eat");
};

var animal_1 = new Animal() 
var animal_2 = new Animal() 
animal_1.eat() //#1 
animal_2.eat() //#2 
alert(animal_1.eat === animal_2.eat) //#3

这一次, #3 输出的就是true了,皆大欢喜。 现在来看看原理层面的东西。animal_1之所以可以找到eat方法,是因为它有一个__proto__属性, 这个__proto__属性的值就是 Animal.prototype。事实上,当执行 new Animal()的时候,js会创建一个新对象,并且将这个新对象的 __proto__设为 Animal.prototype。

prototype并不是一个特殊的属性,它是可以被篡改的,我们可以修改Animal.prototype所指向的对象,给它加属性,甚至,我们可以用一个全新的对象代替它。

var Animal = function() { }; 
var animal_1 = new Animal()

//给prototype加一个eat方法 
Animal.prototype.eat = function() {
    alert("eat");
};

animal_1.eat(); // #1 成功

var oldPrototype = Animal.prototype;

//把prototype给替换成另一个对象 
Animal.prototype = {
    drink: function() { alert("drink"); }
};

Animal.prototype.constructor = Animal; // #4

// animal_1.drink(); // #2 失败!

alert(animal_1.__proto__ === oldPrototype);

animal_2 = new Animal(); 
animal_2.drink(); // #3 成功
#1 animal_1.__proto__ 实际上就是 Animal.prototype,动态地在上面增加属性
#2 这里之所以会失败,是因为animal_1.__proto__的值,是在animal_1被new出来时就固定下来的,我们在后面把Animal.prototype换成了另一个对象,但是animal_1.__proto__所指向的对象仍然是旧的那个oldPrototype。
#3 理解了#1和#2,这个就能理解了。
#4 事实上,Animal.prototype有一个constructor属性,这个属性应该要指向Animal,也就是说,你有责任维护这个关系:

AClass.prototype.constructor === AClass

#然后,进入一个新的话题——继承。

前面我们说,构造函数的prototype属性是一个普通的js对象,但是,这个普通的js对象其实有一个叫__proto__的属性。看清楚这个有趣的事情:animal_1 有一个__proto__属性(new的时候,这个属性被指向了 Animal.prototype),这个__proto__属性所指向的对象也有一个__proto__属性!

想象一下这个事情继续循环,我们就可以得到一个”链“: animal_1.__proto__ , animal_1.__proto__.__proto__, animal_1.__proto__.__proto__.__proto__ … 这就是所谓的”原型链“

读取属性与原型链的关系: 当我们要读取animal_1的某个属性 x的时候,首先在animal_1自身上找,找不到,就会转到原型链上,从上到下,依次查找。

有了原型链,我们可以尝试实现继承。

首先,我们已经有一个能eat的Animal了:

var Animal = function() { }; 
Animal.prototype.eat = function() {
    alert("eat");
};

然后,我们写一个能fly的bird var Bird = function() { } Bird.prototype.fly = function() { alert("fly"); }

弄个鸟出来: var bird_1 = new Bird() bird_1.fly(); bird_1.eat(); //失败!

这个bird_1不能eat,因为它跟Animal没有关系。为了让它能eat,我们需要从原型链入手,我们知道,当我们读取bird_1的某个属性的时候,查找位置是这样的:

bird_1 bird_1.__proto__
bird_1.__proto__.__proto__

我们知道,bird_1.__proto__其实就是 Bird.prototype(身上有fly方法),但是 bird_1.__proto__.__proto__ 呢? 设想一下,如果我们让 bird_1.__proto__.__proto__ 指向 Animal.prototype(身上有eat方法), 那么,我们的bird_1就能eat了!

但是,事情还不是那么简单。我们要怎么才可以让bird_1.__proto__.__proto__指向Animal.prototype呢?__proto__并不是一个我们可以随便修改的属性! 但是,我们知道,bird_1.__proto__ 其实就是Bird.prototype,于是,我们可以想到,我们可以设置 Bird.prototype.__proto__ = Animal.prototype 来达到同样的效果。 可是,Bird.prototype.__proto__ = Animal.prototype,这一句也是要修改__proto__的,所以我们还要换一个思路。前面说了,我们不仅仅可以修改Bird.prototype本身,我们还可以替换它!

我们可以创建一个对象x用来换掉现在Bird.prototype,这个x需要满足两个条件

  1. 它要有一个fly方法,因为它其实就是bird_1.__proto__
  2. 它的__proto__需要指向 Animal.prototype

第一个条件好办,关键是第二个条件,为了让x的__proto__恰好指向的就是 Animal.prototype, 我们的解决办法是这一行代码: var x = new Animal()

然后,我们给x加上原来的fly方法 x.fly = function() { alert("fly"); }

然后剩下的代码就是: Bird.prototype = x; //把Bird的prototye替换掉 Bird.prototype.constructor = Bird;

var bird_1 = new Bird()
bird_1.eat();
bird_1.fly();

这下子,bird_1就能eat和fly了。

来一个代码片段汇总: var Animal = function() { }; Animal.prototype.eat = function() { alert("eat"); };

var Bird = function() { }
Bird.prototype.fly = function() {
    alert("fly");
}

var x = new Animal()      //#1


x.fly = function() {
    alert("fly");
}

Bird.prototype = x; //把Bird的prototye替换掉
Bird.prototype.constructor = Bird;

var bird_1 = new Bird()
bird_1.eat();
bird_1.fly();

前面我们说,我们假设对象是没有状态的,譬如说,我们前面写的Animal和Bird,他们都没有状态,各自只有eat,fly这些方法。现在打破这个假设,我们给Animal加一个状态

var Animal = function(name) {
    this.name = name;
}; 
Animal.prototype.eat = function() {
    alert("eat");
};

看看这样会带来什么问题。在上面的代码里,当执行到这一句: var x = new Animal() //#1 会有问题,因为现在,Animal是要有一个name参数的。可是,这不科学,因为prototype应该是要放那些共享的东西,而name属性其实应该是一个实例状态才对。

为了解决这个问题,我们要创建一个FakeAnimal,这个FakeAnimal的prototype 跟 Animal的prototype一样,但是,他没有自己的状态,代码变成这样:

// Animal是有状态的
var Animal = function(name) {
    this.name = name;  
}; 
Animal.prototype.eat = function() {
    alert("eat");
};

var Bird = function() { }
Bird.prototype.fly = function() {
    alert("fly");
}

//FakeAnimal 没有状态
var FakeAnimal = function() {};
FakeAnimal.prototype = Animal.prototype; // 他们的prototype一样

var x = new FakeAnimal()      // 现在我们用的是FakeAnimal


x.fly = function() {
    alert("fly");
}

Bird.prototype = x; //把Bird的prototye替换掉
Bird.prototype.constructor = Bird;

var bird_1 = new Bird()
bird_1.eat();
bird_1.fly();

问题解决了。

转载于:https://my.oschina.net/mustang/blog/205452

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值