原型继承 VS 类继承——前端新人防秃指南

在这里插入图片描述

原型继承 VS 类继承——前端新人防秃指南

“我照着 MDN 抄的继承,怎么一跑就崩?”
“为什么他的 this 稳如老狗,我的 this 说没就没?”
“老板让我用 ES5 兼容 IE8,我当场想提桶跑路。”

如果你也曾被诸如此类的问题折磨到怀疑人生,恭喜,今天这篇文章就是来给你续命的。接下来我会把原型继承和类继承从“历史包袱”到“底层真相”再到“人话总结”全部拆开讲,顺带塞满你口袋的实战代码和踩坑笔记。看完还不会?那……就再看一遍。


历史包袱:一段只有 10 天怀胎的继承史

1995 年,布兰登·艾克老哥花了 10 天给 Netscape 写出了 JavaScript。
“面向对象”这四个字在当年属于 Java 的禁脔,JS 只能偷偷摸摸地抄:
“你没有 class?那我给你 prototype!”
于是,原型继承就成了 JS 的“亲儿子”,一路跌跌撞撞跑到 ES5。

直到 2015 年,ES6 打着“让 Java 程序员无痛切前端”的旗号,把 class 语法糖端上桌。
糖衣虽甜,可底层还是那一坨 prototype。
历史包袱由此而来:

同一门语言,两种写法,三座大山——语法差异、调试体验、团队撕逼。


原型继承:三剑客的爱恨情仇

先别急着翻篇,把下面这张图刻在脑子里,后面所有代码都围着它转:

构造函数(Fn)  
   ↑  
Fn.prototype ——→ 原型对象  
   ↑  
实例(instance)  

三者关系用一句话总结:

实例的 __proto__ 等于构造函数的 prototype,原型对象里的 constructor 又指回构造函数。
听着像绕口令?直接上代码,边敲边感受。

手写一个“古典”原型链

// 1. 父类:最原始的动物
function Animal(name) {
  this.name = name;
}
// 共享方法挂在原型上,避免每次 new 都重复创建
Animal.prototype.say = function () {
  console.log(`俺是${this.name}`);
};

// 2. 子类:会汪汪的狗
function Dog(name, breed) {
  // 借用父类构造函数完成属性初始化
  Animal.call(this, name);
  this.breed = breed;
}
// 关键步骤:把父类原型抄一份过来,但constructor别丢
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// 3. 子类扩展自己的方法
Dog.prototype.bark = function () {
  console.log('汪汪汪~');
};

// 4. 测试
const ahHuang = new Dog('阿黄', '中华田园');
ahHuang.say(); // 俺是阿黄
ahHuang.bark(); // 汪汪汪~
console.log(ahHuang instanceof Dog); // true
console.log(ahHuang instanceof Animal); // true

一张图看懂内存布局

ahHuang.__proto__  -> Dog.prototype  
Dog.prototype.__proto__ -> Animal.prototype  
Animal.prototype.__proto__ -> Object.prototype  

一条链从头撸到尾,这就是“原型链”。
优点:简单、内存省、ES5 时代唯一选择。
缺点:链太长时属性查找会慢;constructor 容易忘改导致类型判断翻车。


类继承:语法糖里包着还是那块巧克力

ES6 的 class 一出场,不少后端转前端的兄弟直呼“熟悉的味道”。
但请记住:它 100% 不是 Java 的 class,只是 prototype 的 cosplay。
想看清 cosplay 下的真身?让 Babel 给你卸妆。

一段平平无奇的 class

class Animal {
  constructor(name) {
    this.name = name;
  }
  say() {
    console.log(`俺是${this.name}`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 必须在 this 之前调用
    this.breed = breed;
  }
  bark() {
    console.log('汪汪汪~');
  }
}

Babel 转译后(ES5 版)

'use strict';

function _inherits(subType, superType) {
  subType.prototype = Object.create(superType.prototype, {
    constructor: { value: subType, writable: true, configurable: true }
  });
  Object.setPrototypeOf(subType, superType);
}

function Animal(name) {
  this.name = name;
}
Animal.prototype.say = function () {
  console.log(`俺是${this.name}`);
};

function Dog(name, breed) {
  // super(name) 被转译成:
  Animal.call(this, name);
  this.breed = breed;
}
_inherits(Dog, Animal);
Dog.prototype.bark = function () {
  console.log('汪汪汪~');
};

看见没?
extends 被拆成两步:

  1. 原型链继承——Object.create 那一套。
  2. 静态属性继承——Object.setPrototypeOf 把子类 __proto__ 指向父类,实现 Dog.__proto__ === Animal

结论:class 继承 = 原型继承 + 语法糖 + 静态方法继承补丁。
糖衣好吃,但热量一样不少。


性能、调试、团队协作三维对比

维度原型继承类继承
代码可读性对新手不友好,链容易绕晕语义化强,后端同学一眼懂
性能链太长时属性查找慢一样慢,语法糖不背锅
内存方法挂在原型,省内存同上
调试堆栈匿名函数一堆,抓狂class 名会出现在堆栈,幸福
团队协作需要文档+代码规范双重保险直接上 lint 规则即可

结论:小团队/工具库用原型,大项目/多人协作上 class,谁也别鄙视谁。


什么时候该用哪一种?框架们早就用脚投票了

React:类组件被官方劝退,继承几乎绝迹

// 15 时代的“官方推荐”
class MyComp extends React.Component {
  render() {
    return <h1>Hello</h1>;
  }
}
// 18 时代全部函数式 + Hooks,继承戏份直接杀青

React 团队给出的理由:

“继承太灵活,新手容易放飞;组合+Hooks 更可控。”
所以写 React,99% 场景不需要继承,需要复用逻辑请优先高阶组件 / 自定义 Hooks。

Vue2:option 对象导致“继承”需求旺盛

// 抽离通用表格逻辑
const ListMixin = {
  data() {
    return { list: [], loading: false };
  },
  methods: {
    fetchList() {
      this.loading = true;
      api.get(this.url).then((res) => {
        this.list = res.data;
        this.loading = false;
      });
    }
  },
  created() {
    this.fetchList();
  }
};

export default {
  mixins: [ListMixin], // 组合式复用,不是继承但胜似继承
  data() {
    return { url: '/user' };
  }
};

Vue3 的 Composition API 同理,也是组合 > 继承

工具库:灵活混用,怎么爽怎么来

lodash 源码里大量 Object.create 手写原型链,只为浏览器兼容到 IE6;
axios 则用 class 封装 Axios 类,再 extend 出实例,方便拦截器扩展。
一句话:库要小而快,优先原型;要类型友好,优先 class。


踩坑实录:继承的 108 种死法

1. 修改原型属性,全部实例一起翻车

function Person() {}
Person.prototype.list = [];

const p1 = new Person();
const p2 = new Person();
p1.list.push(1);
console.log(p2.list); // [1]  —— 当场裂开

解决:数组/对象请放构造函数里,不要挂原型。

2. super 顺序错,this 直接爆炸

class A extends Object {
  constructor() {
    this.x = 1; // ReferenceError: Must call super first
  }
}

解决:ES6 强制要求——先 super 才能 this

3. 继承链过深,属性查找成性能瓶颈

// 10 级继承,最底层实例访问 .toString 需要连跳 10 次原型

解决

  • 链别超过 3 级;
  • 高频属性用局部变量缓存;
  • 考虑 Object.create(null) 做字典,斩断链。

高手技巧:继承的“骚操作”现场

1. Object.create(null) 做“绝对干净”的字典

const map = Object.create(null);
map.foo = 123;
console.log(map.foo);        // 123
console.log(map.toString);   // undefined  没有原型链,不怕被污染

2. 动态 Mixin——把多个对象“揉”进一个类

const flyMixin = {
  fly() {
    console.log(`${this.name}起飞啦`);
  }
};
const swimMixin = {
  swim() {
    console.log(`${this.name}游啊游`);
  }
};

function mixin(target, ...sources) {
  Object.assign(target.prototype, ...sources);
}

class Duck {
  constructor(name) {
    this.name = name;
  }
}
mixin(Duck, flyMixin, swimMixin);

const donald = new Duck('唐老鸭');
donald.fly();  // 唐老鸭起飞啦
donald.swim(); // 唐老鸭游啊游

3. 冻结原型,防止被队友“背后捅刀”

class SecureClass {
  constructor() {
    // 自己的属性随便改
  }
}
Object.freeze(SecureClass.prototype);
// 之后任何人想往原型上挂方法都会静默失败,安全++

总结:别让继承成为你的“黑盒”

  • 原型继承是 JS 的根,class 只是西装革履的包装。
  • 选哪种写法,先看团队规模,再看性能敏感点,最后看浏览器脸色。
  • 真正的高手不是“不用继承”,而是在合适的场景用合适的方式,出了问题能一眼定位。

把这篇文章放进收藏夹,下次再遇到 this 指向崩了、super 顺序错了、原型链污染导致的全局翻车,别急着砸键盘——
回来看看代码示例,复制粘贴改两行,老板就会以为你秒修致命 bug。

祝你写码少掉头发,继承不再踩坑,我们源码山顶见。

欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。

推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!


专栏系列(点击解锁)学习路线(点击解锁)知识定位
《微信小程序相关博客》 持续更新中~结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等
《AIGC相关博客》 持续更新中~AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结
《HTML网站开发相关》 《前端基础入门三大核心之html相关博客》前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识
《前端基础入门三大核心之JS相关博客》前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。
通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心
《前端基础入门三大核心之CSS相关博客》介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页
《canvas绘图相关博客》Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化
《Vue实战相关博客》持续更新中~详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅
《python相关博客》持续更新中~Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具
《sql数据库相关博客》持续更新中~SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能
《算法系列相关博客》持续更新中~算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维
《IT信息技术相关博客》持续更新中~作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识
《信息化人员基础技能知识相关博客》无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方
《信息化技能面试宝典相关博客》涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面
《前端开发习惯与小技巧相关博客》持续更新中~罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等
《photoshop相关博客》 持续更新中~基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结
日常开发&办公&生产【实用工具】分享相关博客》持续更新中~分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具

吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

DTcode7

客官,赏个铜板吧

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

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

打赏作者

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

抵扣说明:

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

余额充值