继承
构造函数,原型和实例的关系
- 实例是通过构造函数创建的。实例一创造出来就具有constructor属性(指向构造函数)和proto属性(指向原型对象),
- 构造函数中有一个prototype属性,这个属性是一个指针,指向它的原型对象。
- 原型对象内部也有一个指针(constructor属性)指向构造函数:Person.prototype.constructor = Person;
- 什么是原型?每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型”继承”属性。
function Person( ){
}
var person = new Person();
console.log(person._proto_ === Person.prototype); //true
console.log(Person === Person.prototype.constructor); //true
console.log(Object.getPrototypeOf(person) === Person.prototype) //true
- Object.prototype的原型是什么?null,所以查到Object.prototype就可以停止查找了
原型链继承
- 原型链:从一个实例对象往上找,找构造这个实例的相关联的对象,然后这个关联的对象再往上找,它又有创造它的上一级的原型对象,以此类推,最后找到Object.prototype原型对象终止。Object.prototype是原型链的顶端。
- 原型链的工作原理:通过原型链的方式,找到原型对象,原型对象上的方法是被不同的实例所共有的。
function Father(){
this.property = true;
}
Father.prototype.getFatherValue = function(){
return this.property;
}
function Son(){
this.sonProperty = false;
}
//继承 Father
Son.prototype = new Father();//Son.prototype被重写,导致Son.prototype.constructor也一同被重写
Son.prototype.getSonVaule = function(){
return this.sonProperty;
}
var instance = new Son();
alert(instance.getFatherValue());//true
由上例可得一条原型链:
instance
-->Son.prototype
--> Father.prototype
–> Object.prototype
**注:**所有函数的默认原型都是Object的实例,因此默认原型都会包含式内部指针,指向 Object.prototype
,因此自定义类型都会继承一些类似toString的默认方法。
- 确定原型实例关系的两种方法:
// instanceof
alert(instance instanceof Object);//true
alert(instance instanceof Father);//true
alert(instance instanceof Son);//true
// isPrototypeOf
alert(Object.prototype.isPrototypeOf(instance));//true
alert(Father.prototype.isPrototypeOf(instance));//true
alert(Son.prototype.isPrototypeOf(instance));//true
原型链继承需要注意的点
- 继承时,给原型添加方法或者重写原型某个方法时应当把语句放在替换原型的语句之后
function SuperType () {this.property = true;}
SuperType.prototype.getSuper = function(){return this.property;}
function SubType () {this.subProperty = false;}
// 继承
SubType.prototype = new SuperType();
// 给原型添加新方法
subType.prototype.getSub = function(){return this.subProperty;}
// 覆盖重写原型的某个方法
subType.prototype.getSuper = function(){return false;}
- 通过
SubType
的实例调用getSuper
时,方法是被重写那个,但是如果通过SuperType
调用就是原来未重写的方法。 - 做上述修改或者重写的方法不能通过对象字面量的方法
subType.prototype = {getSuper:function(){...}.....}
来创建,因为这样会重写原型链 - 原型链是有缺陷的,当原型链中包含有引用类型的原型时,该引用类型会被所以实例共享,而且创建子类也无法传递参数。因此使用一些手段来辅助解决原型链中上述两个问题。
借用构造函数
基本思想:即在子类型构造函数的内部调用超类型构造函数,即用.call()和.apply()将父类构造函数引入子类函数
function Father(){
this.colors = ["red","blue","green"];
}
function Son(){
Father.call(this);//继承了Father,且向父类型传递参数
}
var instance1 = new Son();
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
var instance2 = new Son();
console.log(instance2.colors);//"red,blue,green" 可见引用类型值是独立的
特点:
- 只继承了父类构造函数的属性,不能继承父类原型的属性。
- 可以继承多个构造函数属性(call)。
- 子实例可以向父实例传参。
- 无法实现构造函数的复用,每个新实例都有父类构造函数的副本,十分臃肿。
组合继承
基本思路: 使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承
function Father(name){
this.name = name;
this.colors = ["red","blue","green"];
}
Father.prototype.sayName = function(){
alert(this.name);
};
function Son(name,age){
Father.call(this,name);//继承实例属性,第一次调用Father()
this.age = age;
}
Son.prototype = new Father();//继承父类原型上的方法,第二次调用Father()
Son.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new Son("louis",5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();//5
var instance1 = new Son("zhai",10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();//10
特点:
- 实现了复用和传参两个问题
- 每个新实例引入的构造函数属性是私有的
- 调用了两次父类构造函数(耗内存)
其余继承方法放在下一节,见js之各种继承(二)