js的几种继承

本文深入探讨JavaScript中的多种继承方式,包括原型链继承、借用构造函数、组合继承及寄生组合继承等。每种继承方式均有详细的实现代码示例,并讨论其优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

翻笔记的时候,翻到js继承的一张图,想起js继承的方式,写个总结记录一下~

先说一个老生常谈的问题,_proto_和prototype属性的区别

_proto_和prototype

一图解所有:

图片来自:www.zhihu.com/question/34…

解释

首先,要明确几个点:

  • 对象具有属性_proto_,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型。

  • 方法这个特殊的对象,除了和其他对象一样有上述

    proto
    属性之外,还有自己特有的属性——原型属性(prototype),它指向一个原型对象。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。

现在看看上面这副图

  • 构造函数Foo()

构造函数的原型属性Foo.prototype指向了原型对象,在原型对象里有共有的方法,所有构造函数声明的实例(这里是f1,f2)都可以共享这个方法。

  • 原型对象Foo.prototype

Foo.prototype保存着实例共享的方法,有一个指针constructor指回构造函数。

  • new foo()

f1和f2是Foo这个对象的两个实例,这两个对象有属性_proto_,指向构造函数Foo()的原型对象。也是因为这样,实例才可以用Foo这个对象的原型链上的方法和属性;

  • 构造函数Foo()除了是方法,也是对象,它也有_proto_属性。它指向它的构造函数的原型对象。函数的构造函数就是Function,因此这里的_proto_指向了Function.prototype。

步入正题

简单原型链的继承

  • 基本思想就是让子类的原型对象指向父类的实例;

    function Father() {
        this.v = "father";
        this.aa = [1,2];
    }
    ​
    function Son() {
        this.a = "son";
    }
    ​
    Son.prototype = new Father();
    ​
    var son1 = new Son();
    var son2 = new Son();
    son1.aa.push(3);//改变son1的原型的数组也会改变son2
    console.log(son1.aa);//[ 1, 2, 3 ]
    console.log(son2.aa);//[ 1, 2, 3 ]复制代码
  • 这种方法就是要注意给子类的原型添加方法时,不用采用字面量形式,防止覆盖了子类的原型对象;

  • 缺点:这种方式对值类型的修改没有问题,而对引用类型的赋值会带来问题;

  • 如果你通过点运算:

    son1.aa = ["a"];

这种方式是你自己给自己的实例增加了一个aa属性;

借用构造函数

  • 在子类中借住apply()和 call()方法来改变对象的执行上下文;

function Father() {
    this.v = "father";
    this.aa = ['a','b','c'];
}
​
function Son() {
    Father.call(this);//引用类型也变成私有了
}
​
var son1 = new Son();
var father1 = new Father();
son1.a.push("d");
console.log(son1.a); //[ 'a', 'b', 'c', 'd' ]
console.log(father1.a); //[ 'a', 'b', 'c' ]复制代码
  • 由上面也可见,对于引用类型,子类实例的改变并没有造成其他影响;

  • 借用构造函数还有一个好处就是可以传递参数;

  • 缺点:但是这种方法无法实现函数的复用,对于属性指向函数的,每次子类实例都要重新生成此函数;

组合继承(原型链+构造函数)

  • 使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承;

  • 解决了函数复用的问题;

  • 缺点:通过这种方法要生成一个父类的实例,而子类通过super.call又要调用一次父类构造函数,浪费内存;

function Father(name) {
    this.name = name;
    this.a = ['a','b','c'];
}
​
Father.prototype.sayName = function () {
    console.log(this.name)
}
​
function Son(name) {
    //继承属性
    Father.call(this,name);//第一次调用父类构造函数
}
​
//继承方法
Son.prototype = new Father();//第二次调用父类构造函数复制代码

寄生组合继承

  • 基于已有的对象创建新对象;

  • 砍掉了组合继承里那份多余的父类实例;

function object(o) {
  function F() {}
  F.prototype = o
  return new F()//F的_proto_属性指向F.prototype
}
​
function Super(){
    // 只在此处声明基本属性和引用属性
    this.val = 1;
    this.arr = [1];
}
//  在此处声明函数
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
//Super.prototype.fun3...
function Sub(){
    Super.call(this);   // 核心
    // ...
}
var proto = object(Super.prototype); // 核心
proto.constructor = Sub;            // 核心
Sub.prototype = proto;              // 核心
var sub = new Sub();复制代码

原型式

  • 子类实例需要自己逐步增强;

function object(obj){   // 生孩子函数 beget:龙beget龙,凤beget凤。
    var F = function(){};
    F.prototype = obj;
    return new F();
}
function Super(){
    this.val = 1;
    this.arr = [1];
}
​
// 拿到父类对象
var sup = new Super();
// 生孩子
var sub = object(sup);   // 核心
sub.attr1 = 1;
sub.attr2 = 2;复制代码

  • ES5提供了Object.create()函数,内部就是原型式继承,IE9+支持;

  • 缺点是原型引用属性会被所有实例共享,因为是用整个父类对象来充当了子类原型对象,所以这个缺陷无可避免

其他

剩下一种寄生式就不讲啦,下面的链接有,有兴趣的可以看看,个人觉得上面几种理解透就够啦~

www.ayqy.net/blog/%E9%87…/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值