js基础-16-继承

一,原型链继承

核心: 将父类的实例作为子类的原型
将构造函数的原型设置为另一个构造函数的实例对象,这样就可以继承另一个原型对象的所有属性和方法,可以继续往上,最终形成原型链

			// 定义一个动物类
			function Animal (name) {
    
    
			  // 属性
			  this.name = name || 'Animal';
			  // 实例方法
			  this.sleep = function(){
    
    
			    console.log(this.name + '正在睡觉!');
			  }
			}
			// 原型方法
			Animal.prototype.eat = function(food) {
    
    
			  console.log(this.name + '正在吃:' + food);
			};
			//定义子类
			function Cat(){
    
     
			}
			//把子类的原型对象变成父类的实例对象,因为父类的实例对象必然继承了父类及其原型对象上的属性和方法
			Cat.prototype = new Animal();
			//给子类的原型上定义属性,并不会影响父类
			Cat.prototype.name = 'cat';
			
			// Test Code
			var cat = new Cat();
			console.log(cat.name);
			console.log(cat.eat('fish'));
			console.log(cat.sleep());
			console.log(cat instanceof Animal); //true Animal的原型对象在cat的原型链上
			console.log(cat instanceof Cat); //true  Cat的原型对象在cat的原型链上

具体的图示:
在这里插入图片描述
也就是舍弃自身原本的原型对象,找了父类的一个实例做原型对象,从而继承那个父类的属性和方法以及原型链。
通俗点说,就是为了继承那点财产(父类的属性和方法),认贼子作父!!
值得注意的是,构造函数是Function的实例,但是改写的toString()方法是定义在Function.prototype上的,不在构造函数的实例对象的原型链上。也就是说构造函数的实例只能顺着原型链找到一个toString方法,就是定义在Object.prototype上的toString,它是用来精准判断数据类型的。

console.log(Cat.__proto__.hasOwnProperty('toString'))  //true  也就是toString定义在Functiong.prototype上
console.log(Animal.prototype.hasOwnProperty('toString'))   //false 也就是Cat.prototype上没有toString
console.log(cat.toString())       //[object Object]  //所以实例对象,只能访问到最末端的判断数据的toString方法

原型链继承存在的问题:

1,父类的实例会变成子类的原型对象,其中的属性会被所有子类的实例对象所共享,若是一个改变了,全部子类实例对象都会被影响
2,没有办法在不影响所有子类实例对象的情况下,给超类型的构造函数传递参数

二,构造函数继承

用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function SuperType() {
    
    
    this.colors = ["red", "blue", "green"];
}

function SubType() {
    
    
    //继承SuperType
    SuperType.call(this);
}

var instance1 = new SubType();
//new的时候,创建新的实例对象,并将this指向它,于是 SuperType.call(this)的this是instance1
//然后call,再改变this指向,从而获取父类的color属性,并且它是直接重新创建一份(深拷贝)
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green" 

优点:
解决了1中,子类实例共享父类引用属性的问题
缺点:
实例并不是父类的实例,只是子类的实例
只能继承父类的实例属性和方法,不能继承原型属性/方法
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

三,组合继承

将原型链和借用构造函数的技术组合到一块。使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。

function SuperType(name) {
    
    
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function() {
    
    
    alert(this.name);
}

function SubType(name, age) {
    
    
    // 继承属性
    SuperType.call(this, name);
    this.age = age;
}

// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
    
    
    alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27 


避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JS中最常用的继承模式。

四,原型式继承

不自定义类型的情况下,临时创建一个构造函数,借助已有的对象作为临时构造函数的原型,然后在此基础实例化对象,并返回。

function object(o){
    
    
 function F(){
    
    }
 F.prototype = o;
 return new F();
} 

本质上是object()对传入其中的对象执行了一次浅复制

var person = {
    
    
 name: "Nicholas",
 friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

alert(person.friends); //"Shelby,Court,Van,Rob,Barbie" 

原型的引用类型属性会在各实例之间共享。

五,寄生组合继承

组合继承是JS中最常用的继承模式,但其实它也有不足,组合继承无论什么情况下都会调用两次超类型的构造函数,并且创建的每个实例中都要屏蔽超类型对象的所有实例属性。
寄生组合式继承就解决了上述问题,被认为是最理想的继承范式。

function object(o) {
    
    
    function F(){
    
    }
    F.prototype = o;
    return new F();
}

function inheritPrototype(superType, subType) {
    
    
    var prototype = object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

function SuperType(name) {
    
    
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function() {
    
    
    alert(this.name);
};

function SubType(name, age) {
    
    
    SuperType.call(this, name);
    this.age = age;
}

inheritPrototype(SuperType, SubType);   // 这一句,替代了组合继承中的SubType.prototype = new SuperType()

SubType.prototype.sayAge = function() {
    
    
    alert(this.age);
};

1.在inheritPrototype函数中用到了原型式继承中的object()方法,将超类型的原型指定为一个临时的空构造函数的原型,并返回构造函数的实例。

2.此时由于构造函数内部为空(不像SuperType里面有实例属性),所以返回的实例也不会自带实例属性,这很重要!因为后面用它作为SubType的原型时,就不会产生无用的原型属性了,借调构造函数也就不用进行所谓的“重写”了。

3.然后为这个对象重新指定constructor为SubType,并将其赋值给SubType的原型。这样,就达到了将超类型构造函数的实例作为子类型原型的目的,同时没有一些从SuperType继承过来的无用原型属性。

猜你喜欢

转载自blog.csdn.net/weixin_42349568/article/details/109010277