javascript 7种继承-- 原型式继承分析(4)

文章详细介绍了JavaScript中的原型式继承,包括概念、实现方式、优缺点,并通过实例代码解析了原型链和原型式继承的差异。作者指出,原型式继承是一种基于对象的继承方法,虽然存在无法传参和可能的数据混乱问题,但在理解JS继承机制上具有重要意义。

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

概要

这阵子在整理JS的7种继承方式,发现很多文章跟视频,讲解后都不能让自己理解清晰,索性自己记录一下,希望个位发表需要修改的意见,共勉之。
部分文章总结出来的数据可能是错误的,网络上太多数据了,有些不严谨,当然我也可能是其中一人,如果有问题,欢迎提出来,共同进步。


javascript 7种继承 文章导航
由于避免篇幅过长导致阅读疲倦等原因,这边进行内容拆解成专栏,并且添加了导航。
:)

文章名
1new操作符的原理以及实现
2[[Prototype]] 与 proto 与 .prototype
31. 原型链继承分析
42. 构造函数继承分析
53. 组合式继承分析
64. 原型式继承分析
75. 寄生式继承分析
86. 寄生组合式继承
97. class继承

继承的进化史

JS的7种并不是逐级进化的(个人觉得~),可以参考下图:

在这里插入图片描述

技术名词解释

  • 构造函数:构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数。在其他面向的编程语言里面,构造函数是存在于类中的一个方法。虽然es6 中的class也是解决了旧的JS继承方式,但是无奈考官要提问,只能记住了 :) …
  • 原型:原型就是一个对象,也叫原型对象,不同的人叫法不一样,很容易搞懵逼初学者,原型===原型对象。
  • 原型链:就是实例对象和原型对象之间的链接,每一个对象都有原型,原型本身又是对象,原型又有原型,以此类推形成一个链式结构,称为原型链。
  • prototype :子类构造函数 所指向的原型(实例化的父类)Children.prototype = new Parent(); 这个是手动指向
  • __proto__:子类实例对象 所指向的原型(实例化的父类) 这个是new 操作符里面的操作

原型式继承

道格拉斯·克罗克福德在一篇文章中介绍了一种实现继承的方法,这种方法并没有使用严格意义上的构造函数。它的想法是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

原型式继承1
let obj = {
	name: "Penk是个码农",
	data: {
	  a: 1,
	  b: 2,
	  c: 3,
	},
	sayData: function () {
	  console.log(this.data);
	},
};
function createObj(obj) {
	function Fun() {}
	Fun.prototype = obj;
	return new Fun();
}

let parent = createObj(obj);
console.log(parent);
原型式继承2

es5新增了一个类似功能的函数,Object.create();

let parentObj = {
 name: "未初始化的人",
  data: {
    a: 1,
    b: 2,
    c: 3,
  },
  sayData: function () {
    console.log(this.data);
  },
  setDataA: function () {
    this.data.a = 666;
  },
};

let parent = Object.create(obj);
console.log(parent);
对比图

如下,可见基本一样了…
在这里插入图片描述

原型链继承 vs 原型式继承

之前讲的其他文章,原型链继承,构造函数继承,组合式继承都是以构造函数为基础的继承,原型式继承感觉就是通过对象,所以在这里可能会难以理解,但是也确实是实现了继承,没办法,开发用不上,但是考官可不理你... : <

  • 原型链继承,是将子类的prototype指向父类实例,父类是子类的原型对象,从而实现原型链继承。
  • 原型式继承,返回一个空的构造函数所创建的实例,并该构造函数的prototype指向需要继承的对象,说白了就是直接浅拷贝copy一个新的对象给你用,可以参考原型式继承的一句话代码:let parent2 = Object.create(obj);

案列分析

之前的案例都是用父子类,这边依旧用它,不能因为换了技术方案就要换个场景。

  • 父类:Parent,要被继承的类,可以理解为人。
  • 子类:Children,要继承父类的类,可以理解为不同的工种,但是还是一个人,所以继承了父类的一些基础属性。
  • 实例:person1 ,person2,实例化的子类,一般实例化后数据就分开了,复合数据类型要注意…

源代码解析

原型式继承是通过对象浅拷贝,再通过.操作符添加属性已到达继承跟重写的功能。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>原型式继承</title>
  </head>
  <body>
    <h3>原型式继承</h3>
    <h4>相当于把所有的属性跟方法放在原型对象上了,类似于浅拷贝。</h4>
    <h4>缺点:无法传参,对象如果是复合数据类型的数据,会造成数据混乱。</h4>
    <script>
      let parentObj = {
        name: "未初始化的人",
        data: {
          a: 1,
          b: 2,
          c: 3,
        },
        sayData: function () {
          console.log(this.data);
        },
        setDataA: function () {
          this.data.a = 666;
        },
      };

      // 第一种方式,函数传参返回一个实例
      // 该实例对应的构造函数的原型指向了传参
      function createObj(obj) {
        function Fun() {
          // ... 这里不可以有方法以及属性,不然会覆盖Fun的原型对象上的方法以及属性
        }
        // 父类的公共方法以及属性需要放在这里,会被 "Fun.prototype = obj;" 覆盖...
        // ... Fun.prototype.name = "";
        Fun.prototype = obj;
        // 放在这里也不可以,会覆盖传进来的参数obj
        // ... Fun.prototype.name = 111;
        // ... Fun.prototype.sayName = function () {
        // ...   console.log(this.name);
        // ... };
        return new Fun();
      }

      // 就当成一个父类吧
      let parent = createObj(parentObj);
      console.log(parent);

      // 所以说,原型式继承,只能继承传进来的参数obj
      // 第二种方式创建obj,使用object.create()
      let parent2 = Object.create(parentObj);
      console.log(parent2);

      console.log(
        "=====通过上面的对比,可以得知,Object.create(),可以实现原型式继承..."
      );

      console.log("=====================");
      console.log("=====================");
      console.log("接下来要展示原型式继承的BUG...");

      // 就当成一个子类
      let children = Object.create(parent);
      children.job = "未找到工作";
      children.sayJob = function () {
        console.log(`${this.name} 的工作是${this.job}...`);
      };

      // 实例化2个子类对象
      // let person1 = Object.create(children);
      // let person2 = Object.create(children);

      // 真的不知道怎么初始化,没找到相关资料...
      // 这边就随便来了,有好建议欢迎交流.
      // person1.name = "钟先生";
      // person1.job = "程序员";

      // console.log(person1);
      // console.log(person2);

      // person1.sayJob();
      // person2.sayJob();

      // console.log("大概就是这样,一层套一层,还不能传参...");
      // console.log("=====================");
      // console.log("=====================");
      // console.warn("====这里如果使用了复合数据类型的话,也会出现数据混乱...");

      // 实例化2个子类对象
      let person1 = Object.create(children);
      let person2 = Object.create(children);

      // 初始化
      person1.name = "钟先生";
      person1.job = "程序员";

      person2.name = "刘小姐";
      person2.job = "清洁阿姨";

      // 未修改数据
      console.log("===未修改数据的时候...");
      person1.sayData();
      person2.sayData();

      console.log("===修改数据的时候...");
      person1.setDataA();
      person1.sayData();
      person2.sayData();

      console.log(person1);
      console.log(person2);
    </script>
  </body>
</html>


效果图

实例化了person1跟person2 两个实例对象,并且都初始化了,第一次打印数据,相同没问题,第二次打印前,person1修改了原型上的复合数据类型中的变量a=>person1.__proto__.data.a的值,这里是引用类型数据,所以混乱了。

在这里插入图片描述

小结

优点

无。

缺点
  • 无法传参
  • 如果传参的对象有复合数据类型,若是修改,会造成数据混乱。
  • 对父类功能进行重写只能通过.操作符,下一次将用升级版:寄生式继承
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Penk是个码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值