Skip to content

Latest commit

 

History

History
258 lines (190 loc) · 9.5 KB

继承的几种方法.md

File metadata and controls

258 lines (190 loc) · 9.5 KB

继承的几种方法

继承是面向对象语言中的一个最为人津津乐道的概念。许多面向对象都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。如前所述,由于函数没有签名,在 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继承(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的结构如下;

call继承的优点

对实例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"
call继承的缺点

只继承了A的私有属性,没有继承A的原型; 如果仅仅是call继承,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在父类的原型中定义的方法,对子类而言也是不可见的,结果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的。

所以引出了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);

call继承加原型继承解决的事情

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 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原来的原型被重写了;

知识点四、冒充对象继承(call继承的升级版)

冒充对象继承:在子类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,类似数组排序中的借用的那个对象,仅仅只是内部处理所需的,没有实际的意义;

test