JS实现继承的7种方式,你都知道哪几种???

题记

ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的。

继承的方式

方式一:原型链

关于原型链实现继承的方式我们在 你清楚JS的原型链吗 这里提到过,是通过让原型对象等于另一个类型的实例来实现继承的,但是原型链继承也存在一些问题:

①最主要的问题来自包含引用类型值的原型

包含引用类型值的原型属性会被所有实例共享,在原型链继承方式中,原型实际上会变成另一个类型的实例,于是,原先的实例属性也就顺理成章的变成了现在的原型属性。

// Parent 父亲的构造函数
function Parent() {
  this.children = ["son1", "son2", "son3"];
}
// Son 儿子的构造函数
function Son() {}

// Son 继承了 Parent
Son.prototype = new Parent();

// 实例化了child1和child2 向child1的children属性push数据,同时会修改child2的children属性
var child1 = new Son();
child1.children.push("daughter");
console.log(child1.children);

var child2 = new Son();
console.log(child2.children);

打印结果:
在这里插入图片描述
②在创建子类型的实例时,不能向超类型的构造函数中传递参数。

方式二:借用构造函数

实现思想:在子类型构造函数的内部调用超类型构造函数

// Parent 父亲的构造函数
function Parent() {
  this.children = ["son1", "son2", "son3"];
}
// Son 儿子的构造函数
function Son() {
  // 继承了 Parent,借调了超类型的构造函数
  Parent.call(this);
}
// 实例化了child1和child2 每个对象都有Parent中所有的初始化代码
var child1 = new Son();
child1.children.push("daughter");
console.log(child1.children);

var child2 = new Son();
console.log(child2.children);

打印结果:
在这里插入图片描述
优点:相比于原型链来说,借用构造函数可以在子类型构造函数中向超类型构造函数中传递参数

// 1. 父构造函数
function Father(uname, age) {
    // this 指向父构造函数的对象实例
    this.uname = uname;
    this.age = age;
}
// 2 .子构造函数 
function Son(uname, age, score) {
    // this 指向子构造函数的对象实例,同时还传递了参数
    Father.call(this, uname, age);
    this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son);

打印结果:
在这里插入图片描述
缺点:仅仅使用借用构造函数,无法避免构造函数模式存在的问题—方法都在构造函数中定义,无法实现函数覆用。

方式三:组合继承

组合继承,也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块的继承模式。实现思想是使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。

// Parent 父亲的构造函数
function Parent(name) {
  this.name = name;
  this.children = ["son1", "son2", "son3"];
}
// 给 Parent 原型对象中添加 sayName 的方法
Parent.prototype.sayName = function() {
  console.log(this.name)
}
// Son 儿子的构造函数
function Son(name, age) {
  Parent.call(this, name);      // 第二次调用Parent()
  this.age = age
}
// Son 继承了 Parent
Son.prototype = new Parent();   // 第一次调用Parent()
// 手动设置 constructor
Son.prototype.constructor = Son;
Son.prototype.sayAge = function() {
  console.log(this.age)
}

// 实例化了child1和child2 
var child1 = new Son("Sam", 23);
child1.children.push("daughter");
console.log(child1.children);
child1.sayName()
child1.sayAge()

var child2 = new Son("Helen", 22);
console.log(child2.children);
child2.sayName()
child2.sayAge()

打印结果:
在这里插入图片描述
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JS中最常用的继承模式。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候;另一次是在子类型构造函数内部。

在上述代码中,第一次调用Parent()构造函数时,Son.prototype会得到两个属性:name和children,他们都是Parent的实例属性,只不过现在位于Son的原型中。当调用Son构造函数时,又会调用一次Parent构造函数,这一次又在新对象上创建了实例属性name和children。于是,这两个属性就屏蔽了原型中的两个同名属性。

方式四:原型式继承

借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

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

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

var person = {
	name: "Helen",
	friends: ['sam', 'sunny']
};
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);   // sam,sunny,Rob,Barbie

这种原型式继承,要求必须有一个对象可以作为另一个对象的基础,在这个例子中,可以作为另一个对象的基础是person对象,于是我们把它传入到object()函数中,然后这个函数就会返回一个新对象。这个新对象将person作为原型,所以它的原型中就包含一个基本类型值属性和一个引用类型值属性。这意味着person.friends不仅属于person所有,而且也会被anotherPerson以及yetAnotherPerson共享。

ES5新增的Object.create()方法规范了原型式继承,这个方法接收两个参数:一个用做新对象原型的对象和一个为新对象定义额外属性的对象(可选)。在传入一个参数的情况下,Object.create()与object()方法的行为相同。在传入两个参数的情况下,定义的任何属性都会覆盖原型对象上的同名属性。

var person = {
	name: "Helen",
	friends: ['sam', 'sunny']
};
var anotherPerson = Object.create(person, {
	name: {
		value: "Greg"
	}
});
alert(anotherPerson.name);  // Greg

在只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的,不过别忘了,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。

方式五:寄生式继承

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

function createAnother(original){
	var clone = object(original);   // 通过调用函数创建一个对象
	clone.sayHi = function(){		// 以某种方式来增强这个对象
		alert("hi"); 
	};
	return clone;   				// 返回这个对象
}

createAnother()函数接受了一个参数,也就是将要作为新对象基础的对象。然后,把这个对象(original)传递给object()函数,将返回的结果赋值给clone。再为clone对象添加一个新方法sayHi(),最后返回clone对象。可以像下面这样来使用
createAnother()函数。

var person = {
	name: "Helen",
	friends: ['sam', 'sunny']
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi();  // hi

任何能够返回新对象的函数都适用于此模式。但是与构造函数模式类似,使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率。

方式六:寄生组合式继承

即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。是为了解决组合继承调用两次超类型构造函数的问题。

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

inheritPrototype函数接收两个参数:子类型的构造函数和超类型的构造函数。在函数内部,第一步是创建超类型原型的一个副本;第二步是为创建的副本添加constructor属性;第三步是将新创建的对象赋值给子类型的原型。这样就可以用调用inheritPrototype函数的语句,去替换组合继承中为子类型原型赋值的语句。

// Parent 父亲的构造函数
function Parent(name) {
  this.name = name;
  this.children = ["son1", "son2", "son3"];
}
// 给 Parent 原型对象中添加 sayName 的方法
Parent.prototype.sayName = function() {
  console.log(this.name)
}
// Son 儿子的构造函数
function Son(name, age) {
  Parent.call(this, name);  
  this.age = age
}

inheritPrototype(Son, Parent);

Son.prototype.sayAge = function() {
  console.log(this.age)
}

开发人员认为寄生组合式继承是引用类型最理想的继承范式,因为它只调用了一次超类型构造函数,并且避免了在子类型的原型上面创建不必要的、多余的属性。与此同时,原型链还能保持不变,还能正常使用instanceof和isPrototypeOf()。

方式七:ES6中的继承

ES6中有了类的概念。

class Father {
    constructor() {}
    money() {
        console.log(100);
    }
}
class Son extends Father {}
var son = new Son();
son.money();    // 100

也支持向超类型构造函数中传参:

class Father {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    sum() {
        console.log(this.x + this.y);
    }
}
class Son extends Father {
    constructor(x, y) {
        super(x, y);     // 调用了父类中的构造函数
    }
}
var son = new Son(1, 2);
var son1 = new Son(11, 22);
son.sum();    // 3
son1.sum();	  // 33
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值