面向对象和原型链的用法(下)

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 __ proto __)指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( __ proto __ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型.

我们现在来说一下,关于原型链的继承吧,这个相信大部分人都会用到,下面请看例子

function Father () {
	this.fatherName = "爸爸";
}
Father.prototype.getFatherValue = function () {
	return this.fatherName;
}
function Sub() {
	this.subName = "儿子";
}
// 继承了 Father
Sub.prototype = new Father();

Sub.prototype.getSubValue = function () {
	return this.subName;
}

let instance = new Sub();
console.log(instance.getSubValue());      // 儿子
console.log(instance.getFatherValue());  // 爸爸

下面我们来看看 重写 父亲的方法

function Father () {
	this.fatherName = "爸爸";
}
Father.prototype.getFatherValue = function () {
	return this.fatherName;
}
function Sub() {
	this.subName = "儿子";
}
// 继承了 Father
Sub.prototype = new Father();

Sub.prototype.getSubValue = function () {
	return this.subName;
}

// 重写父类的方法
Sub.prototype.getFatherValue = function () {
	return "我是子类重写的方法";
}

let instance = new Sub();
console.log(instance.getSubValue());             // 结果是 => 儿子
console.log(instance.getFatherValue());          // 结果是 => 我是子类重写的方法 儿子的实例会覆盖父亲上面的那个方法,但是不会影响原生的父亲实例
let fatherInstance = new Father();               // 我们定义一个父亲 
console.log(fatherInstance.getFatherValue());   // 爸爸

我们来看看下面的这个,引用类型的

function Father () {
	this.color = ["小红", "小黑"];
}

function Sub() {
}
// 继承了 Father
Sub.prototype = new Father();

let instance1 = new Sub();
instance1.color.push("小黄");
console.log(instance1.color);   // 结果是 => ["小红", "小黑", "小黄"]
let instance2 = new Sub();
console.log(instance2.color);    // 结果是 => ["小红", "小黑", "小黄"]
// 为什么会这样呢,因为Sub继承了了Father,Sub.prototype的原型链 拿到了 Father 的属性 color,而原型链上面的属性是共享的,所以会出现上面的结果

那我们怎么解决呢,我们使用 借用构造函数

function Father () {
	this.color = ["小红", "小黑"];
}

function Sub() {
	// 简单的一行代码 这里的作用是, 每次我们执行的话,在调用Sub时,初始化Father 函数定义的对象,这样子Sub每个实例就有自己的color属性副本了,就不会相互影响了
	Father.call(this);
}
// 继承了 Father
Sub.prototype = new Father();

let instance1 = new Sub();
instance1.color.push("小黄");
console.log(instance1.color);   // 结果是 => ["小红", "小黑", "小黄"]
let instance2 = new Sub();
console.log(instance2.color);    // 结果是 => ["小红", "小黑"]

但是 借用构造函数 有一个缺点,就是父类里面的定义的方法,对子类是不可用的,那么有什么改进的方法呢 组合继承 请看例子

function Father (name) {
	this.name = name;
	this.color = ["小红", "小黑"];
}
Father.prototype.sayName = function () {
	console.log(this.name);
}
function Sub(name, age) {
	Father.call(this, name);
	this.age = age;
}

// 继承方法
Sub.prototype = new Father();
Sub.prototype.sayAge = function () {
	console.log(this.age);
}

let instance1 = new Sub("小白", 2);
instance1.color.push("小黄");
console.log(instance1.color);   // 结果是 => ["小红", "小黑", "小黄"]
instance1.sayName();            // 小白
instance1.sayAge();             // 2

let instance2 = new Sub("小黑", 3);   
instance2.color.push("小橙");   
console.log(instance2.color);   // 结果是 =>["小红", "小黑", "小橙"]
instance2.sayName();            // 小黑
instance2.sayAge();             // 3
这样子 就解决了我们父元素不可以访问的问题了,又解决了实例独享this.color 属性

但是 组合继承 有一个缺陷,就是每次调用 都会调用两次父类元素,这样子 会很消耗资源, 请看解释

function Father (name) {
	this.name = name;
	this.color = ["小红", "小黑"];
}
Father.prototype.sayName = function () {
	console.log(this.name);
}
function Sub(name, age) {
	Father.call(this, name);             // 第二次调用
	this.age = age;
}

// 继承方法
Sub.prototype = new Father();            // 第一次调用
Sub.prototype.sayAge = function () {
	console.log(this.age);
}
第一次调用Father 时,Sub会拿到Father的两个属性name 和 color,不过它现在位于Sub.prototype的原型链中,所以实例会共享,第二次调用Father的作用是,
在这个新的对象上创建了name和color属性,所以就覆盖了原型链上的那两个属性,实例就是单独每个模块的了,就不会相互影响了

那么我们有办法,解决两次调用父类的方法吗, 答案是肯定有的, 寄生组合模式,
请看下面的例子

function inheritProperty (subType, fatherType) {
	let prototype = new Object(fatherType.prototype);   // 创建对象  这个作用是创建一个fatherType对象的副本
	prototype.constructor = subType;	                // 增强对象   这个是把constructor 指向给subType,弥补因为重写原型而丢失的constructor 属性
	subType.prototype = prototype;                      // 指定对象   这个是将创建的对象 赋值给子类的原型 这样子sub就可以拿到father上的方法了
} 


function Father (name) {
	this.name = name;
	this.color = ["小红", "小黑"];
}
Father.prototype.sayName = function () {
	console.log(this.name);
}
function Sub(name, age) {
	Father.call(this, name);             // 第二次调用
	this.age = age;
}

// 继承方法
inheritProperty(Sub, Father);          // 第一次调用
Sub.prototype.sayAge = function () {
	console.log(this.age);
}

let instance1 = new Sub("小白", 2);
instance1.color.push("小黄");
console.log(instance1.color);   // 结果是 => ["小红", "小黑", "小黄"]
instance1.sayName();            // 小白
instance1.sayAge();             // 2

let instance2 = new Sub("小黑", 3);   
instance2.color.push("小橙");   
console.log(instance2.color);   // 结果是 =>["小红", "小黑", "小橙"]
instance2.sayName();            // 小黑
instance2.sayAge();             // 3

如果想了解的更多,请参考博客
面向对象和原型链的用法(中)

面向对象和原型链的用法(上)

好了,原型链的继承大概就讲到这里了,如果有什么问题,欢迎指出哦,大家一起进步,哈哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值