Javascript如何实现继承?详细解析每一种的核心代码

本文详细解读了JavaScript中的原型链继承、构造函数继承、组合继承、原型式继承和寄生式/寄生组合式继承,探讨了它们的工作原理、优缺点和应用场景,有助于面试准备和理解JavaScript面向对象编程的继承机制。
摘要由CSDN通过智能技术生成

首先还是感谢大佬及其伙伴的文章,对我在面试准备期间帮助巨大面试官:Javascript如何实现继承? | web前端面试 - 面试官系列 (vue3js.cn)

本文主要是对其中的代码及核心代码发表自己的理解(非常深刻)然后就是上述的文章中给出的部分示例代码可能有点狂放,有些没定义的可能他们也没注意,看肯定还是能看懂的,下文大部分的解释和例子都是引用上面的链接文章,原文也是极好的!

前情提要:要了解原型以及原型链的相关知识,不然可能会看懵

可以参考:原型和原型链-CSDN博客

核心代码已经在代码块中标出 (≧∇≦)ノ

1.原型链继承

原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针

function Parent() {
    this.name = 'parent1';
    this.play = [1, 2, 3]
}
function Child() {
    this.type = 'child2';
}

Child.prototype = new Parent(); // 核心代码

console.log(Child.prototype); //Parent

var s1 = new Child();
var s2 = new Child();
console.log(s1.__proto__); //Parent
s1.play.push(4);
console.log(s1.play, s2.play); // [1,2,3,4]

改变s1play属性,会发现s2也跟着发生变化了,这是因为两个实例使用的是同一个原型对象,内存空间是共享的

解析:

① Child.prototype = new Parent(); 改变了 Child 的原型链,name 和 play 属性都定义在其原型链上,当 s1.play.push(4) 运行时,会首先寻找 s1 上的属性,发现没有则往其原型链上寻找,即 s1.__proto__ ,会寻找到 Parent ,发现 Parent 有这个属性,于是拿到,继承完成

② 啰嗦一句,可能有人会觉得 new Parent() 不应该是构造了一个全新的 Parent 吗,怎么会使得他们共享内存空间呢,但这个 Parent 只是单纯的新建一个出来,不然没法进行指向。本质上,两个实例对象新建的还是 Child 函数,而 Child 的原型对象就是 Parent ,所以他们指向的还是同一个

2.构造函数继承

借助 call调用Parent函数

function Parent(){
    this.name = 'parent1';
    console.log("我被执行了");
}

Parent.prototype.getName = function () {
    return this.name;
}

function Child(){
    Parent.call(this); // 核心代码
    this.type = 'child'
}

let child = new Child();
let child1 = new Child();
console.log(child);  // 没问题
console.log(child.getName());  // 会报错

好处:相比第一种原型链继承方式,父类的引用属性不会被共享,优化了第一种继承方式的弊端

坏处:只能继承父类的实例属性和方法,不能继承父类原型属性或者方法

解析:

① 该方法的核心代码在于Parent.call(this); 这句代码我们要拆开来解读,首先是 Parent. 此时其实是 new 了一个新的 Parent 对象来调用 call 方法(请看下面的图片),其次,call 方法就是用来改变 this 指向,所以,新的 Parent 对象的全部的属性以及方法都会转移到新的 Child上

重新梳理一下:即,当 let child = new Child(); 执行时,进入function Child(),执行Parent.call(this); 新建了一个 Parent 对象来调用 call 方法,改变 this 指向,使新的 Parent 对象的全部的属性以及方法都会转移到新的 Child上

 

3.组合继承

前面我们讲到两种继承方式,各有优缺点。组合继承则将前两种方式继承起来

function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
}

Parent3.prototype.getName = function () {
    return this.name;
}
function Child3() {
    // 第二次调用 Parent3()
    Parent3.call(this);
    this.type = 'child3';
}

// 第一次调用 Parent3()
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);  // 不互相影响
console.log(s3.getName()); // 正常输出'parent3'
console.log(s4.getName()); // 正常输出'parent3'

解析:其实就是把上面两种方式的优点结合起来

用第一种原型链方法时,能完善原型链,使得 getName() 方法能成功被获取到

用第二种构造函数方法时,能使数据之间互不影响

4.原型式继承

这里主要借助Object.create方法实现普通对象的继承

let parent4 = {
    name: "parent4",
    friends: ["p1", "p2", "p3"],
    getName: function () {
        return this.name;
    }
};

let person4 = Object.create(parent4); //核心代码
person4.name = "tom";
person4.friends.push("jerry");

let person5 = Object.create(parent4); //核心代码
person5.friends.push("lucy");

/// 重点在这
console.log(person4.__proto__ === parent4); // true
console.log(person5.__proto__ === parent4); // true
///

console.log(person4.name); // tom
console.log(person4.name === person4.getName()); // true
console.log(person5.name); // parent4
console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"]
console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]

解析:

① 首先着重讲一下 Object.create() 方法,其实也是改变原型链,达到继承的效果,在上述代码中,存在 person4.__proto__ === parent4 ,其实可以简单理解为,Object.create() 可以使得左边的变量的 __proto__ 指向右边的变量,这很关键。

举个例子: let a = Object.create(b)    ===>    a.__proto__ === b

这种继承方式的缺点也很明显,因为Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能

5.寄生式继承

寄生式继承在上面继承基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法

let parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
        return this.name;
    }
};

function clone(original) {
    let clone = Object.create(original); // 核心代码
    clone.getFriends = function() {
        return this.friends;
    };
    return clone;
}

let person5 = clone(parent5);

console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // ["p1", "p2", "p3"]

解析:其实本质上还是上一种方法,只是懒得一个个加新的函数(万一要加的函数很多呢),我感觉有点像一种代码复用

其优缺点也很明显,跟上面讲的原型式继承一样

6.寄生组合式继承

寄生组合式继承,借助解决普通对象的继承问题的Object.create 方法,在前面几种继承方式的优缺点基础上进行改造,这也是所有继承方式里面相对最优的继承方式

function clone(parent, child) {
    // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype); // child.prototype.__proto__ = parent.prototype
    child.prototype.constructor = child;
}

function Parent6() {
    this.name = 'parent6';
    this.play = [1, 2, 3];
    this.identify = {
        a: '123'
    }
}

Parent6.prototype.getName = function () {
    return this.name;
}

function Child6() {
    Parent6.call(this);
    this.friends = 'child5';
}

clone(Parent6, Child6);

Child6.prototype.getFriends = function () {
    return this.friends;
}

let person6 = new Child6();
let person7 = new Child6();

// 关键在这
console.log(person7.__proto__ === Child6.prototype);  //true
console.log(Child6.prototype.__proto__ === Parent6.prototype); // true
//

console.log(person6); //{friends:"child5",name:"child5",play:[1,2,3],__proto__:Parent6}
console.log(person7); //{friends:"child5",name:"child5",play:[1,2,3],__proto__:Parent6}
console.log(person6.getName()); // parent6
console.log(person6.getFriends()); // child5

解析:有点像大杂烩,其中的原理上面都有提到,我这里就把原型链画出来供大家参考一下

 

 

  • 31
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值