js 的六种继承总结

前言

首先,对于面向对象语言来说,实现继承一般会实现接口继承和实例继承. 但是由于js没有函数签名,js中无法实现接口继承,所以一般所说的js继承是指实例继承.

原型链继承(1/6)

原型链继承的核心思想是 : 一个引用类型继承另一个引用类型的属性和方法:

var {log} = console;
//父类
function Person(){
    this.target = 'person'
}
Person.prototype.callName = function(){
    return "I am "+ this.name;
};
//子类
function Student(name){
    this.name = name;
}
//通过原型链实现继承
Student.prototype = new Person();
var sbc = new Student('shen');
//可以调用父类属性和方法
log(sbc.callName());
log(sbc.target);

上面代码实现了两个类型,person和student类型,而其中继承是通过创建person的示例,并将该实例赋值给Student.prototype实现的.实现的本质是重写了Student的原型对象.现在Student中不仅有Person实例的属性和方法,也有Person原型链上的属性和方法.

注:

  1. 要注意 sbc.constructor 现在指向的是 Person,这是因为原来 Student.prototype 中的 constructor 被重写了的缘故.
  2. 原型链继承会导致父类的实例属性会变成子类的原型属性,同时子类无法像父类传递参数.
  3. 在原型链继承中,如果多个子类继承同一个父类,那么父类中的引用类型属性被所有子类共享.这代表着子类a修改了属性会影响到子类b.

借用构造函数继承(2/6)

借用构造函数核心思想是: 在子类中使用父类的构造函数

var {log} = console;
function Person(name){
    this.name = name;
}
function Student(name,age){
    //核心步骤,借用父类构造函数构造函数
    Person.call(this, name);
    this.age = age;
}
var student1 = new Student('xiao ming',12);
var student2 = new Student('xiao zhang',20);
log(student1.name,student1.age);
log(student2.name,student2.age);

在子类Student中使用了父类Person的构造函数,并且传递了参数name.这种传参的能力是原型链继承所做不到的.

注:
1.这种方法相比于原型链方法,实现了子类向父类传参. 在本例中使用了call函数调用了person的构造函数.
2.构造函数的问题在于方法都在构造函数中定义,因此函数复用性很差.

组合继承(3/6)

组合继承的核心思想是: 用原型链实现原型属性和方法的继承,同时用构造函数继承实现实例属性的继承

var {log} = console;
function Person(name){
    this.name = name;
    this.target = 'person';
}
Person.prototype.sayHi = function () {
    return "hi, I am "+ this.name;
};
function Student(name,age){
    //借用父类构造函数
    Person.call(this, name);
    this.age = age;
}
//原型指向Person类
Student.prototype = new Person();
Student.prototype.constructor = Student;
var student1 = new Student('xiao ming',12);
var student2 = new Student('xiao zhang',20);
log(student1.name,student1.age,student1.sayHi());
log(student2.name,student2.age,student2.sayHi());

这是js 一般常用的继承方式,结合了原型链继承和借用构造函数继承.这样子类分别有了自己的引用类型属性,并且也可以使用相同的方法.

原型式继承(4/6)

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

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

在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,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"

ECMAScript 5 通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与 object()方法的行为相同。

var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"

寄生式继承(5/6)

寄生式继承和原型式继承很像,只不过通过创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

var {log} = console;
function createAnother(origin) {
    var clone = o	bject(origin);
    clone.sayHi = function () {
         log('hi');
    }
return clone;
};
var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

寄生组合式继承(6/6)

前面说过,组合继承是 JavaScript 最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。以刚刚组合式继承的代码为例:

var {log} = console;
function Person(name){
    this.name = name;
    this.target = 'person';
}
Person.prototype.sayHi = function () {
    return "hi, I am "+ this.name;
};
function Student(name,age){
    //第二次调用超类构造函数
    Person.call(this, name);
    this.age = age;
}
//第一次调用超类构造函数
Student.prototype = new Person();
Student.prototype.constructor = Student;
var student1 = new Student('xiao ming',12);
var student2 = new Student('xiao zhang',20);
log(student1.name,student1.age,student1.sayHi());
log(student2.name,student2.age,student2.sayHi());

如上面代码所示,在第一次调用Person构造函数时,Student类已经有了name和target属性,但是此时属性存在于原型中,第二次调用构造函数使name和target存在于实例中,从而屏蔽了原型中的属性. ----这是调用两次构造函数的后果,使用寄生组合式继承可以避免这个问题.

寄生组合式继承核心思想: **使用借用构造函数来继承属性,使用原型链的形式来继承方法.**其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。

function inheritPrototype(subType, superType){
	var prototype = object(superType.prototype); //创建对象
	prototype.constructor = subType; //增强对象
	subType.prototype = prototype; //指定对象
}

这个示例中的 inheritPrototype()函数实现了寄生组合式继承的最简单形式。这个函数接收两个参数:子类型构造函数和超类型构造函数。在函数内部,第一步是创建超类型原型的一个副本。第二步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。

这样,我们就可以用调用 inheritPrototype()函数的语句,去替换前面例子中为子类型原型赋值的语句了,例如:

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(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值