原型链&&继承

原型链

下面总结了些关于原型链的知识点。

一.名词介绍

引用MDN关于继承和原型链的阐述:

涉及到继承这一块,Javascript 只有一种结构,那就是:对象。在 javaScript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接。这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链(prototype chain)。

  1. 构造函数、原型和实例

    • 构造函数都有一个属性prototype,这个属性是个对象(是object实例,Function除外)
    • 原型对象prototype里面有一个constructor属性,指向原型对象所属的构造函数
    • 实例对象都有一个proto的属性,该属性指向构造函数的原型对象,它是一个非标准属性,不能用来编程,只能在浏览器中看到
  2. prototype 和 proto

    • prototype是构造函数的属性
    • 对象都有一个proto属性

二 . 我有一个问题

如下:

1
2
3
4
5
6
7
8
9
10
11
var F= function(){}
Object.prototype.a=function(){
alert('a');
}
Function.prototype.b=function(){
alert('b');
}
var f=new F();
f.a();
f.b();

先抛开问题,来看看Function与Object之间的爱恨情仇~

1
2
3
4
5
Object instanceof Function
//true
Function instanceof Object
//true

以上代码都返回true,那我能说Object与Function互为实例么?答案是否定的

1.首先ObjectFunction都是构造函数,而所有的构造函数又都是Function的实例对象,所以ObjectFunction的实例对象

2.Function.prototype是Object的实例对象

3.Function.proto指向了 Function.prototype

1
2
console.log(Function.__proto__===Function.prototype); //true

由以上几点大概可以画出以下关系图:

回到我的那个问题:
结合上面的图来看这个问题就比较好理解了

  • 因为f=new F(){};所以f._proto_指向了F.prototype(实例指向原型对象),又因为F.prototype._proto_指向的是Object.prototype(F.prototype是Object的实例对象),所以f继承了Object.prototype上的属性和方法,所以f.a()可以执行。

  • 因为 f 的原型链为f==>F.prototype==>Object.prototype,而b()是在Function.prototype上的,所以f访问不到b();所以会报错:f.b is not a function

还有关于instanceof要说明一点:

如果右侧构造函数的prototype属性能在左侧的对象的原型链中找到, 那么就返回true, 否则就返回false

  • Object instanceof Function返回true是因为:Object.proto === Function.prototype
  • Function instanceof Object返回true是因为:Function.proto.proto === Object.prototype

所以文章开头说的Function与Object互为实例不对,Function.prototypeObject的实例,ObjectFunction的实例

还有一点要注意:
typeof Function.prototype ==》function;Function.prototype虽然是function类型的,但它是Object的实例对象。

关于原型链的问题,画图是比较容易理清思路的,顺着原型链(proto)去找,判断是否拥有相应的方法及属性是比较容易入手的。

继承

关于继承我只记录下三种常用的继承方法

首先定义动物类

1
2
3
4
5
6
7
8
9
10
11
function Animal(name){
this.name=name;
this.sleep=function(){
console.log(this.name+'正在睡觉zzzz');
}
}
//原型方法
Animal.prototype.eat=function(food){
console.log(this.name+'正在吃:'+food);
}

1.原型继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Tiger(color){
this.color=color;
}
Tiger.prototype=new Animal();
Tiger.prototype.name='tiger'; //必须放在new Animal()之后
var tiger=new Tiger('yellow');
console.log(tiger.name); //tiger
console.log(tiger.color); //yellow
tiger.eat('chicken'); //tiger正在吃:chicken
tiger.sleep(); //tiger正在睡觉zzzz
console.log(tiger instanceof Animal); //true
console.log(tiger instanceof Tiger); //true
//父类新增原型方法/原型属性,子类都能访问到
Animal.prototype.wowo=function(){
console.log('wowowo');
}
tiger.wowo(); //wowowo

特点

  • 父类新增原型方法/原型属性,子类都能访问到
  • 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行

缺点

  • 创建子类实例时,无法向父类构造函数传参
  • 无法实现多继承(后者会覆盖前者)
  • 每一个子类实例都会共享父类的原型

2.构造继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function MaoKe(){
this.say=function(){
console.log('miaomiao~~');
}
}
function Cat(name){
Animal.call(this,'Tom');
}
var cat=new Cat();
console.log(cat.name); //Tom
cat.say(); //miaomiao~~
cat.sleep(); //Tom正在睡觉zzzz
cat.eat(); //cat.eat is not a function

特点:

  • 实现了子类向父类传参数的问题,但是只能继承父类的实例属性和方法,不能继承原型属性/方法
  • 可以实现多继承(可以call多个父类对象)
  • 解决了子类实例向父类传参问题

3.组合继承

首先抛出一个问题如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
console.log(instance2.colors) //为什么这里也会是"red,blue,green,black",不是"red,blue,green"

解释:

colors在SubType Prototype中是因为,colors是SuperType的一个实例属性,而SubType PrototypeSuperType的实例。
调用instance1.colors的顺序(沿着原型链进行搜索):

1.搜索该实例本身

2.搜索SubType Prototype

3.搜索SuperType Prototype

搜索到即停止。

所以在instance1给SubType Prototype里的colors属性插入black后,instance2同样也是指向这里,所以会输出四种颜色。

解决方法:使用组合继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function SuperType(){
this.colors = ["red","blue","green"];
}
function SubType(){
//继承了SuperType,同时还可以传递了参数
SuperType.call(this);
}
//继承方法
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors);//red,blue,green,black
var instance2 = new SubType();
console.log(instance2.colors);//red,blue,green

【原型继承+构造函数继承】它的特点就是属性每个实例一份,方法共享

以上即是现阶段对于原型链继承的理解,如有理解不对的地方希望可以指出。