
原型继承 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 被拆成两步:
- 原型链继承——
Object.create那一套。 - 静态属性继承——
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等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

1221

被折叠的 条评论
为什么被折叠?



