继承

继承

因为李阿姨不太明白继承,之前也写过对象做铺垫,今天就来写下继承。

在其他面向对象语言,继承,就是让子类对象可以拥有父类的方法和属性。你也可以认为继承是指在原有对象的基础上,略作修改,得到一个新的对象。其实也就是,让新对象拥有被继承对象的属性和方法。这么理解因为在js中没有类的概念,只有对象的概念。es6中的class只不过是构造函数的语法糖。

原型链继承

本质: 重写原型对象.
我们知道一个对象上没有某一属性和方法的时候,会沿着原型链去做查找。此时,我们可以通过修改原型对象,来实现继承。

// 被继承的对象构造函数
function Super(){
	this.a = 1;
	this.arr = ['1','2','3']
}
Super.prototype.getA = function(){
	return this.a;
}
// 继承对象的构造函数
function Sub(){}
// 重写原型对象,实现继承
Sub.prototype = new Super();
Sub.prototype.constructor = Sub; // 确定对象的类型

var sub = new Sub();
console.log(sub.getA())  // 1

var sub2 = new Sub();
sub.arr.push('4');
console.log(sub2.arr); // ['1','2','3','4']

注意的地方是: 查找方法是按照原型链来查找的。
其实,在上面的例子中,也能看到,包含引用类型值的原型属性会被所有实例共享,而这也正是创建对象时为什么要在构造函数中,而不是在原型对象中定义属性的原因。
在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

借用构造函数继承

借用构造函数(constructor stealing)的技术(有时候也叫做伪类继承或经典继承)。基本思想相当简单,在子类型构造函数的内部调用超类型构造函数,通过使用apply()和call()方法在新创建的对象上执行构造函数(函数只不过是在特定环境中执行代码的对象)。

function Super(name){
	this.name = name;
}

function Sub(name.age){
	this.age = age;
	Super.call(this, name); // 相当于 父类的 代码在这里执行
}

var sub = new Sub('zhangsan',25);

我们发现,此时就可以传递参数了。但是,他还是存在了一些问题。方法也要在构造函数中定义,会造成空间的浪费,无法实现代码复用。

组合继承

组合继承(combination inheritance),有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

function Super(name){
	this.name = name;
	this.arr = ['0','1'];
}
Super.prototype.sayName = function(){
	console.log(this.name);
}

function Sub(name,age){
	this.age = age;
	//  第二次调用Super(),Sub.prototype又得到了name和colors两个属性,并对上次得到的属性值进行了覆盖
	Super.call(this,name); // 继承属性
}

Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    console.log(this.age);
}

//第一次调用Super(),Sub.prototype得到了name和colors两个属性
var sub = new Sub('zhangsan',25);
sub.arr.push('2');
sub.sayName(); // zhangsan 
sub.sayAge(); // 25
console.log(sub.arr) // ['0','1','2']

var sub1 = new('lisi', 24)
console.log(sub1.arr) // ['0','1']

在上面注释中,我们也看懂了,那就是无论什么情况下,都会调用两次父类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。子类型最终会包含父类型对象的全部实例属性,但不得不在调用子类型构造函数时重写这些属性。

寄生组合继承

寄生组合式继承与组合继承相似,都是通过借用构造函数来继承不可共享的属性,通过原型链的混成形式来继承方法和可共享的属性。只不过把原型继承的形式变成了寄生式继承。使用寄生组合式继承可以不必为了指定子类型的原型而调用父类型的构造函数,从而寄生式继承只继承了父类型的原型属性,而父类型的实例属性是通过借用构造函数的方式来得到的.
就是之前我们的子类的原型对象指针指向父类的实例,现在让子类的原型对象指针指向父类的原型对象的副本。这样避免了重复创建。

function Super(name){
	this.name = name;
	this.arr = ['0','1'];
}
Super.prototype.sayName = function(){
	console.log(this.name);
}

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

// 注意Object.create 可能 存在浏览器兼容问题   可以自定义函数类创建对象的副本
Sub.prototype = Object.create(Super.prototype);  // 这里修改了
Sub.prototype.constructor = Sub;

这个例子的高效率体现在它只调用了一次Super构造函数,并且因此避免了在Sub.prototype上面创建不必要的、多余的属性。与此同时,原型链还保持不变。还能够正常使用instanceof 和 isPrototypeOf()。普遍认为寄生组合式继承是引用类型最理想的继承范式。

es6的class继承

class只是语法糖。并不是真正的类。

class Super{
	constructor(name){
		this.name = name;
		this.arr = ['0','1'];
	}
	sayName(){
		return this.name;
	}
}

class Sub extends Super{
	constructor(name,age){
		super(name);
		this.age = age;
	}
}

var sub = new Sub("zhangsan",25);

console.log(sub.sayName);

原型继承

道格拉斯·克罗克福德在 2006 年写了一篇文章,题为 Prototypal Inheritance in JavaScript (JavaScript中的原型式继承)。在这篇文章中,他介绍了一种实现继承的方法,这种方法并没有使用严格意义上的构造函数。他的想法是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。为了达到这个目的,他给出了如下函数

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

在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制.

function Super(){
    this.value = 1;
}
Super.prototype.value = 0;
function Sub(){};

Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;

//创建子类型的实例对象
var instance = new Sub;
console.log(instance.value);//0

在es5 使用Object.create() 对原型继承做出了规范。时候可能会混淆原型继承和原型链继承。原型继承虽然只是看上去将原型链继承的一些程序性步骤包裹在函数里而已。但是,它们的一个重要区别是父类型的实例对象不再作为子类型的原型对象。原型继承与原型链继承都存在着子例共享父例引用类型值的问题

寄生式继承

寄生式继承(parasitic)是与原型继承紧密相关的一种思路,并且同样是由道格拉斯·克罗克福德推而广之的。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数内部以某种方式来增强对象,最后再返回对象.

function parasitic(oObj){
	var cloneObj = Object.create(oObj);
	cloneObj.sayHello = function(){
		console.log("hello");
	}
	return cloneObj;
}

var superObj = {
	name:"张三",
	arr:['0','1'],
	sayName(){
		console.log(this.name);
	}
}
var subObj1 = parasitic(superObj);

subObj1.sayHello();  // hello
subObj1.sayName(); // zhangsan

其实很明显的就是对一个对象做了增强,返回一个新对象。 他存在的问题1.引用类型的值被共享的问题。 2.做不到函数的复用。

拷贝继承

拷贝继承不需要改变原型链,通过拷贝函数将父例的属性和方法拷贝到子例即可。

function extend(o){
	return JSON.parse(JSON.string(o))
}

var superObj = {
	arr:['0','1'],
	name:'zhangsan'
}

var subObj = extend(superObj);

其实就是对象的深拷贝。 和原型链 构造函数都没有关系。

拷贝组合继承

就是用对象的深拷贝 来代替 Object.create() 函数。

function extend(o){
	return JSON.parse(JSON.string(o))
}

function Super(name){
	this.name = name;
	this.arr = ['0','1'];
}

Super.prototype.sayName = function(){
	console.log(this.name);
}

function Sub(name,age){
	this.age = age;
	Super.call(this,name); 
}

Sub.prototype = extend(Super.prototype);  
Sub.prototype.constructor = Sub;

总结

其实这里的继承,没有oo语言里的那么严谨。 这里的继承就是让对象A,可以拥有(使用)对象B的属相和方法。 基本遵循的思路是: 对象查找属性和方法时: 首先在自身上查找,或者沿着原型链查找原型对象。所以继承实现基本就是: 1. 添加到自身(构造函数,拷贝,手动添加), 2.原型对象(修改原型对象,重写原型对象)。其实要注意对象类型,有些时候会不确定对象的类型。(类型由原型对象上的constructor 属性决定,它默认是不可枚举的)。 所以根本还是对对象本身的理解。

原文地址:https://www.cnblogs.com/cyrus-br/p/10494660.html