当谈到继承时,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
如果想了解的更多,请参考博客
面向对象和原型链的用法(中)
面向对象和原型链的用法(上)
好了,原型链的继承大概就讲到这里了,如果有什么问题,欢迎指出哦,大家一起进步,哈哈哈