【100天复习前端】JavaScript对象机制--对象的继承

引言

前面咱们复习了JS建立对象的几种方式,在里面也提到了原型和原型链的概念,而这两个东西在咱们这一篇文章中也会常常用到,JS的继承就是根据原型链来进行的。在这里咱们不讨论ES6ClassExtends继承,只讨论ES5中常见的六种继承方式。git

1.原型链继承

在上一节建立对象中,咱们提到,每个实例都有本身的原型,而这个实例能够经过prototype这个属性找到他对应的原型。原型链继承的原理就来自于这里。这种继承方式应该是全部继承方法里面最简单的一个,固然也会存在不少的缺点。该方法继承的核心在于一个地方:子类型的原型为父类型原型的一个实例。下面咱们举一个例子来讲明:github

// father
function Internet(name, ip) {
	this.name = name;
	this.ip = ip;
	this.setName = function() {
		
	}
}
// 定义一个私有方法
Internet.prototype.getIp = function() {

}
// children
function Iot(mac) {
	this.mac = mac;
	this.setMac = function() {

	}
}

// 子类型的原型为父类型的一个实例,咱们经过new操做符来建立
Iot.prototype = new Internet(); // 物联网继承了互联网的全部属性,替换物联网的原型
let Iotxh = new Iot('6A421');
let Iotce = new Iot('6A410');
console.log(Iotxh);
console.log(Iotce);
复制代码

这里咱们能够在浏览器中去查看一下输出的结果,能够看到打印出来的对象的__proto__属性,也就是上一节提到的指向该实例的构造函数的隐式原型的一个指针,是咱们的Internet实例,这样就能够经过Iotxh.prototype访问到父类Internet上的私有属性,而后再经过父类实例的prototype就能够访问到父类原型上的方法。因此到最后,咱们父类的全部的私有方法和公有方法和属性,均可以当作是子类的公有属性。segmentfault

可是在这里有两个地方是须要咱们注意的,分别是:浏览器

  • 由于在JS中,咱们操做基本数据类型的值的时候是直接修改其对应内存中的值,而当咱们在修改引用类型的时候,咱们修改的是这个地址所对应的值,全部引用这个地址的变量都将会受到影响,这个在咱们上一节的建立对象当中也提到过。例如咱们修改Iotxh中的ip,那么Iotce中的ip也会受到影响。bash

  • 当咱们须要在继承后的子类中添加新的方法或者是重写父类的方法的时候,要放到咱们替换原型以后进行。若是在替换原型以前进行,那么在咱们替换原型以后,原来添加的东西仍是在原来的原型上,最终是无效的。函数

老规矩,前面的方法,确定都是有不少缺陷的,原型链继承的缺陷在于如下几点: 1.没法实现多继承 2.来自原型对象的全部属性被全部实例共享 3.建立子类实例时,没有办法向父类构造函数传参 4.上面提到的两个须要注意的地方post

2.构造函数继承

为了解决部分上面提到的问题,咱们继续复习构造函数继承的继承方式。ui

这个方法的核心在于this指针的指向问题,也就是说,咱们经过修改父类构造函数的this指针指向来实现继承。先撸代码:this

// father
function Internet(name, ip) {
	this.name = name;
	this.ip = ip;
	this.setName = function() {

	}
}
// 给父类的原型添加一个方法
Internet.prototype.getIp() {

}
// children
function Iot(mac, name, ip) {
	Internet.call(this, name, ip);
	this.mac = mac;
}
let Iotxh = new Iot('6A421','Weily','192.168.1.1');
console.log(Iotxh);
复制代码

在这里的继承也有一个地方是须要注意的,单纯的构造函数的继承方式,只是实现部分的继承,由于咱们每次继承的时候都至关于调用了一次父类型的构造函数,那么当父类的原型还有构造函数外的方法和属性,子类是拿不到这些方法和属性的。例如上面的getIp()方法,就是没法经过这种方式继承的。spa

OK,简单的复习这种继承方式,咱们谈一下他的缺点: 1.建立的实例并非父类的实例,只是子类的实例 2.只能继承父类的实例属性和方法,不能继承原型属性和方法 3.没法实现函数复用,每一个子类都有父类的实例函数的副本,浪费内存

3.原型链 + 构造函数 === 组合继承

按照套路,讲完了两种继承方式,咱们确定有一种他们结合的更优的继承方式。这种继承的方式能够归纳为:经过调用父类的构造函数,继承父类的属性并能进行传参,并经过将子类的原型指向父类的实例,实现函数的复用。

老规矩,咱们仍是先撸代码:

// father
function Internet(name, ip) {
	this.name = name;
	this.ip = ip;
	this.setName = function() {
	
	}
}
// add methods
Internet.prototype.setNmae = function() {
		
	}
// children 构造继承
function Iot(mac, name, ip) {
	Internet.call(this, name, ip);
	this.mac = mac;
	this.getMac = function() {
		
	}
}
// 原型继承
Iot.prototype = new Internet();
// 将子类的构造函数从Internet修复到Iot
Iot.prototype.constructor = Iot;
// 为子类的原型添加新方法
Iot.prototype.getName() {
	
}
let iotxh = new Iot('6A421', 'Weily', '192.168.1.1');
let iotce = new Iot('6A410', 'ce', '192.168.1.2');
console.log(iotxh);
复制代码

这种方式融合了前面两种继承方式的优势,是JS中最经常使用的继承模式,可是并非最优的继承模式。固然,这种方式也存在必定的缺点,分别是: 1.不管何时都会调用两次构造函数,第一次调用是在建立子类的原型时,另外一次是在子类的构造函数的内部,子类最终会包含父类的所有实例属性,但咱们不得不在调用子类构造函数时重写这些属性。

4.原型式继承

这种方式就开发来讲,彷佛用得不多,基本上没有看到使用这种方式进行继承的,他的原理是Object.creat()的模拟实现,将传入的对象做为建立的对象的原型,先上代码:

function createObj(o) {
	function F(){}
	F.prototype = o;
	return new F();
}
// father
let internet = {
	name: 'weily',
	ip: '192.168.1.1',
	person:['ybw','wkm']
}
// 继承
let iotxh = createObj(internet);
let iotes = createObj(internet);

iotxh.name = 'Weily';
console.log(iotes.name); // weily

iotxh.person.push('ll');
console.log(iotes.person); // ['ybw', 'wkm', 'll']
复制代码

上面举了两个例子,想必你们也看出来了,这种继承方式的缺点也是会共享值。至于为何修改name属性不影响,是由于那行代码是为iotxh建立了一个本身的name属性。

5.寄生式继承

这种继承方式的核心在于一个地方,构造一个中间件,让中间件的prototype代理父类的prototype。先上代码:

// 构造的中间件
function object(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

function createAnother(original) {
    // 经过调用函数建立一个新对象
    var clone = object(original);  
    // 以某种方式来加强这个对象
    clone.sayHi = function() {
        alert("hi");
    }

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

var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // Hi
复制代码

经过中间件代理的方式,咱们可让子类的原型间接的和父类联系起来。

缺点: 每次建立对象的时候都会建立一遍方法。

6.BOSS--寄生组合式方式

OK,前面复习了五种继承方式,各有各的好处,固然缺点也很多,因此,咱们有了最优的继承方式,寄生组合继承。

这种继承方式咱们要达到的效果是,完美继承父类的属性和方法,且只调用一次父类的构造函数,最终子类的原型链还要保持不变。那么,咱们须要如何来实现这个需求呢?

回想一下,组合继承方式已经能完美的实现除了调用一次父类构造函数以外的所有需求,那么,咱们须要作的就是去解决这一个需求,很巧的是,咱们上面提到的寄生继承方式,偏偏就是只调用了一次构造函数,那么解决办法就有了,经过中间件,间接的实现这个需求。

在组合继承中,咱们第二次调用构造函数,是为了让子类的原型访问到父类的原型,那么咱们经过中间件的方式,也能实现这样的功能,上代码:

// father
function Internet(name, ip) {
	this.name = name;
	this.ip = ip;
	this.person = ['ybw', 'wkm'];
}
// add methods
Internet.prototype.changeName = function() {

}
// children
funciton Iot(mac, name, ip) {
	Internet.call(this, name, ip);
	this.mac = mac;
}
// 中间件
let F = function() {}
F.prototype = Internet.prototype;
Iot.prototype = new F();

let iotxh = new Iot('Weily', '192.168.1.1');
console.log(iotxh);
复制代码

最后,咱们来封装一下这个最优的继承方法,便于之后咱们开发中的使用:

function object(o) {
	function F() {}
	F.prototype = o;
	return new F();
}
function prototype(child, parent) {
	let prototype = object(parent.prototype);
	prototype.constructor = child;
	child.prototype = prototype;
}
prototype(Child, Parent);

复制代码

OK,咱们的ES5中的六种寄生方式就复习完了,ES6中的extend继承,咱们后面在复习ES6的时候再来复习。欢迎关注个人博客博客

参考文献

JavaScript深刻之继承的多种方式和优缺点 JavaScript常见的六种继承方式 JavaScript寄生继承的理解

相关文章
相关标签/搜索