继承是面向对象语言中的一个最为人津津乐道的概念。许多面向对象都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。如前所述,由于函数没有签名,在 ECMAScript 中无法实现接口继承。ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
继承的概念:子类继承父类中的属性和方法; 继承的目的:子类直接使用父类的方法,不需要再重写一遍; 继承的实际应用:B想有一个getX的方法,我们发现A中已经有了,我接下来只需要让B作为儿子,让A作为父亲,让B继承A中的属性和方法;
- 知识点一:原型继承
- 知识点二:call继承
- 知识点三:call加原型继承(推荐)
- 知识点四:冒充对象继承
- 知识点五:中间类继承
原型继承的原理:B要继承A,只需要让B的原型等于A的一个实例即可 ->B.prototype=new A;
function A() {
this.x = 100;
}
A.prototype.getX = function () {
console.log(this.x);
};
function B() {
this.test="test"
}
B.prototype.getY = function () {
console.log("B.prototype");
};
B.prototype = new A;
var b = new B;
console.dir(b);
b.x=200;
b.getX();//200;
A.prototype.getX();//undefined
//子类重写父类的方法
b.__proto__.__proto__.getX = function () {
console.log(this.x + 1000);
};
b.getX();//1200;
1)原型继承是我们JS中最常用的一种继承方式,属于改写原生的原型,指向需要继承的实例;;
2)原型继承不是把父类的属性和方法复制一份给自己,而是更改了原型链的查找顺序,我们b想要使用getX这个方法,是通过__proto__一级级的向上查找,最后也找到A的原型上,获取getX并且使用 -> b.proto.proto.getX=A.prototype.getX
3)子类不仅仅可以获取使用父类上的方法,而且还可以把父类原型上的方法进行修改 b.proto.proto.getX=function(){console.log(this.x+1000);} 就是把父类A原型上的方法修改了,这样的话,A的其他的实例也会受到影响 ->"这叫做类的多态(是多态中的重写):子类重写父类的方法"
4)在原型继承中,子类可以把父类中私有和公有的属性/方法都继承过来使用;并且都是作为子类B公有的属性/方法;(因为B每次查找都经过A的实例,所以A中的私有方法和属性,会被B所继承)
写一个方法,求评委打分的平均分(去掉一个最高分,去掉一个最低分)
function fn() {
arguments.__proto__ = Array.prototype;
arguments.sort(function (a, b) {
return a - b;
});
arguments.shift();//去掉最高分
arguments.pop();//去掉最低分
console.log(eval(arguments.join("+")) / arguments.length);
console.log(arguments.join("+") , arguments.length);
}
fn(56,67,78,89,90,12,23,34 ,45);
fn(56, 67, 78, 89, 90, 12, 23, 34, 45 , 45,14,34,67);
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){}
//继承了 SuperType
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"
1、完全继承了A,自己原型上的方法被吃掉了;杀敌1000,自损800的做法; 2、最主要的问题来自包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享;而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。(所有属性都是大家伙的了) 3、由第二个问题导致的就是,在创建子类的实例时,不能向父类的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
所以引出了call继承(apply同样的原理);
call继承的原理:在子类B的私有属性中,把父类A当做一个普通的函数执行,并且把里面的this变为子类B的一个实例b,这样的话,在执行A的时候,A中写的this.xxx=xxx都相当于给b增加的私有的属性
function A() {
this.x = 100;//b.x=100 给b增加一个私有的属性x=100
this.flagA="flagA"
}
A.prototype.getX = function () {
console.log(this.x);
};
function B() {
//this->b
//A() ->作为一个普通的函数执行,this->window,A的原型是起不到作用的
A.call(this);//->执行A函数,把A里面的this变为b
this.flagB="flagB"
}
B.prototype.getBPrototype = function () {
console.log("B.prototype");
};
var b = new B;
console.log(b);
1)call继承首先只能继承父类A中的私有的属性(this.xxx=xxx),并且和原型不一样,call继承是把A中的私有的属性复制一份直接的给了B的实例
2)把父类A中的私有属性继承到子类B中的私有的属性
b的结构如下;
对实例1的操作不会影响实例2,
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
// 继承了 SuperType
SuperType.call(this);
}
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"
只继承了A的私有属性,没有继承A的原型; 如果仅仅是call继承,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在父类的原型中定义的方法,对子类而言也是不可见的,结果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的。
所以引出了call继承+原型继承;
call继承+原型继承
function A() {
this.x = 100;
this.flagA="flagA"
}
A.prototype.getX = function () {
console.log(this.x);
};
function B() {
A.call(this);
this.flagB="flagB"
}
B.prototype.getBPrototype = function () {
console.log("B.prototype");
};
B.prototype = new A;
var b = new B;
console.log(b);
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。而且, instanceof 和 isPrototypeOf() 也能够用于识别基于组合继承创建的对象。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
};
function SubType(name, age){
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;//constructor 需要写回去,否则SubType虽然继承了SuperType的私有属性和公有属性,但是SubType原来的原型被重写了;
SubType.prototype.sayAge = function(){
console.log(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
console.log(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
用这种模式一定要注意原型的constructor重写会子类,否则SubType虽然继承了SuperType的私有属性和公有属性,但是SubType原来的原型被重写了;
冒充对象继承:在子类B的私有函数体中,创建一个父类A的实例temp(temp中就存有父类A中的私有和公有的属性/方法),我们把temp当做一个普通的对象进行遍历,分别把temp中存有的属性都存储到我们子类B的私有中一份
function A() {
this.x = 100;
this.flagA="flagA"
}
A.prototype.getX = function () {
console.log(this.x);
};
function B() {
// A.call(this);
this.flagB="flagB"
var temp = new A;//->temp中现在就存有了A中的私有和公有的属性/方法
for (var key in temp) {
this[key] = temp[key];
}
temp = null;
}
B.prototype.getBPrototype = function () {
console.log("B.prototype");
};
var b = new B;
console.log(b);
- 1)、冒充对象继承首先继承父类中的私有和公有的属性/方法,并且和原型不一样,冒充对象继承是把A中的私有的属性和共公有的方法复制一份直接的给了B的实例
- 2)、把父类A中的私有属性和共公有的方法继承到子类B中的私有的属性
备注:temp只是一个借用的对象,所以用完记得置为null,类似数组排序中的借用的那个对象,仅仅只是内部处理所需的,没有实际的意义;