JS炼化:焚诀进阶! 原型/原型链/Class抽象解析——领取你的“官方外挂”

0 阅读6分钟

焚诀威能暴涨:JS面向对象进阶,系统级“外挂”破局复杂开发

前言

上篇我们踏入JS面向对象的基础修行,把this比作无坚不摧的利剑new比作承载剑的剑鞘构造函数比作一套基础剑法,而三者结合的执行流程,便是我们修炼的核心功法——焚诀

那时只靠基础三件套,已经能创建实例、做简单封装,应付一般场景完全足够。小怪打够了,就想干boss,可真正往下走才发现,一旦面对大批量对象、重复方法、代码复用、工程化结构时,基础版焚诀明显力不从心。方法不能共享、内存白白浪费、继承没有章法,代码越写越乱,自己写着都觉得别扭。瞬间暴汗😰。

本来准备硬着头皮死磕到底, 结果系统轻轻拍了拍我的肩膀:

您有新手福利外挂,尚未领取。

🎁 可领取进阶外挂:

  • 原型 ·(用来存放所有招式共享的“公共武学库”) 方法共享、节省内存... :待领取

  • 原型链 ·(用来构建武学层级,让招式能“继承父辈武功”) 继承机制、能力传承...* *:待领取

  • Class 语法糖 ·(把底层原型机制包装成更易用的“门派体系”,让开发者更快搭建门派结构。)
    优雅写法、工程友好... :待领取

所以这一篇,我一步步把这些“系统外挂”拆开,把它们真正变成自己的进阶剑法。 从为什么需要它们,到它们解决了什么,再用最简单的代码验证原理。 不是为了背概念,而是让自己的焚诀进阶

基础焚决!面向对象三件套的实战弊端

在上一篇内容里,我们把this比作剑、new比作剑鞘、构造函数比作剑法,靠着这套「焚诀基础版,初步实现了JS面向对象的基础功能,能批量创建实例对象,完成简单的业务逻辑。但这套基础三件套,只能应对极简场景,一旦放到真实开发的复杂需求里,弊端会被无限放大,不仅代码冗余,还会造成内存浪费、逻辑无法复用等致命问题,根本扛不住复杂业务的考验。也就是打小怪好使,刷boss吃力。 下面我们就结合具体代码,逐一拆解构造函数+this+new这套基础组合的“刷boss”核心缺陷,让大家直观看到问题所在。

问题一、方法重复创建,内存资源严重浪费

我们先写一个最常见的构造函数,模拟创建武侠角色,每个角色拥有姓名、门派属性,以及出招、修炼两个方法,依靠new关键字生成实例,用this指向当前实例对象。

// 构造函数:武侠角色剑法
function MartialArtist(name, sect) {
  // this=剑,指向当前创建的实例
  this.name = name;
  this.sect = sect;
  // 实例方法:出招
  this.attack = function() {
    console.log(`${this.name}使出门派招式!`);
  }
  // 实例方法:修炼
  this.practice = function() {
    console.log(`${this.name}潜心修炼武功!`);
  }
}

// new=剑鞘,创建两个实例
const zhangSan = new MartialArtist('张三', '武当派');
const liSi = new MartialArtist('李四', '武当派');

// 调用实例方法
zhangSan.attack(); // 张三使出门派招式!
liSi.attack(); // 李四使出门派招式!

// 验证两个实例的方法是否为同一个
console.log(zhangSan.attack === liSi.attack); // false

从代码运行结果能清晰看到,张三和李四都是武当派的角色,attack和practice方法的逻辑完全一致,但两个实例的方法却不是同一个引用。

核心弊端:每通过new创建一个实例,构造函数内部的方法都会重新创建一遍,生成全新的函数副本。如果创建100个、1000个同门派角色,就会在内存中创建1000个功能一模一样的方法,大量占用内存空间,就像每个武者都要重新抄写一遍相同的剑法秘籍,完全没有必要,造成极大的资源浪费。

问题二、属性与方法无法共享,复用性极差

真实开发中,同一类实例往往有大量共享的属性和方法,比如武当派所有弟子的门派宗旨、基础剑法都是相同的,只需要一份即可。但基础三件套完全做不到公共内容共享,所有属性和方法都绑定在当前实例this上,每个实例都要单独存储一份,导致代码复用性为零。

我们尝试给两个实例添加共享的门派宗旨属性,看看基础写法的问题:

function MartialArtist(name, sect) {
  this.name = name;
  this.sect = sect;
  // 试图写共享属性,结果依然是实例私有
  this.sectTenet = '以武入道,行侠仗义';
  this.attack = function() {
    console.log(`${this.name}使出${this.sect}招式,秉持:${this.sectTenet}`);
  }
}

const zhangSan = new MartialArtist('张三', '武当派');
const liSi = new MartialArtist('李四', '武当派');

// 修改张三的门派宗旨,李四的不受影响
zhangSan.sectTenet = '武当太极,以柔克刚';
console.log(zhangSan.sectTenet); // 武当太极,以柔克刚
console.log(liSi.sectTenet); // 以武入道,行侠仗义

本意是让所有武当派弟子共享同一个门派宗旨,修改一次就能全局生效,可结果是每个实例都有独立的sectTenet属性,修改一个实例的共享属性,其他实例完全不受影响。想要统一修改共享内容,只能逐个实例遍历修改,工作量翻倍,还极易出现代码漏洞,完全违背了面向对象代码复用的核心思想。

(代码示例的小瑕疵:说一下代码里面的小瑕疵。看完代码很多人就会觉得修改张三不影响李四,这不是很正常的吗?没有错,实例自己的属性就应该互相独立,真正想要解决的问题是对于门派宗旨通用招式这样全门派共用的内容,基础构造函数没有给我们一个统一存放,统一修改的地方。

这个时候又会有疑问,那你直接在函数中改不就行了吗?可以,没有问题。但是这种用构造函数写死方法和共享属性就等于把代码焊死在里面。后面你想改想去扩展。就要回到原来这个大代码里,翻来覆去去找。对于小项目无所谓,但是对于大项目构造函数几百行方法几十个,改一个方法要翻回构造函数最里面改改完还要担心影响所有实例,这就是维护灾难。让你来修改维护的话,你头大不头大。

所以这个例子可能用的并不是十分恰当。意思理解就好。)

问题三、继承逻辑难以实现,复杂业务无从下手

面向对象的核心价值之一就是继承也就是子类可以复用父类的属性和方法,同时拓展自身功能。 但依靠构造函数+this+new的基础组合,想要实现继承极其繁琐,没有原生的语法支持,只能通过手动拷贝属性和方法的方式实现,代码臃肿、可读性差,还无法实现原型层面的继承,复杂业务场景直接束手无策。

我们尝试手动实现简单的继承,看看问题:

// 父类:基础武者
function MartialArtist(name, sect) {
  this.name = name;
  this.sect = sect;
  this.attack = function() {
    console.log(`${this.name}使出基础招式!`);
  }
}

// 子类:武当弟子,想要继承基础武者的属性和方法
function WudangDisciple(name) {
  // 借用构造函数,只能复制父类的属性和方法,不是真正的继承
  MartialArtist.call(this, name, '武当派');
  // 子类独有方法
  this.wudangAttack = function() {
    console.log(`${this.name}使出太极剑法!`);
  }
}

const zhangSan = new WudangDisciple('张三');
const liSi = new WudangDisciple('李四');

// 看似能调用父类方法,但每个实例都有独立的方法副本
zhangSan.attack(); // 张三使出基础招式!
liSi.attack();     // 李四使出基础招式!
console.log(zhangSan.attack === liSi.attack); // false ❌ 内存严重浪费

核心问题拆解:

1. 不是真正的继承: call  只是把父类的代码在子类里重新执行了一遍,相当于给子类实例复制了一份父类的属性和方法,父子类之间没有任何原型关联;

2. 内存浪费爆炸:每创建一个子类实例,就会生成一份全新的父类方法副本,实例越多,内存占用越大;

3. 多层继承灾难:如果要实现「武当弟子 → 武当掌门 → 武林盟主」的多层继承,只能层层  call ,代码无限冗余,维护成本极高。

四、总结:基础三件套的核心局限

综上, this 、 new 、构造函数这套基础面向对象三件套,只是JS面向对象的入门级玩法,它的核心弊端可以总结为三点:

1. 内存浪费严重:相同方法重复创建,实例越多,内存占用越大;

2. 代码复用性差:无法实现公共属性和方法的共享,重复代码堆积;

3. 继承能力缺失:无原生继承机制,复杂业务逻辑无法高效实现。

正是因为这些无法忽视的弊端,JS语言设计者才提供了原型、原型链这套底层解决方案,后续又推出了 class 语法糖做封装优化,这些本质都是语言层面给予开发者的辅助开发能力,专门为了解决基础三件套的痛点而生的外挂。下一部分,我们就顺着这些问题,详解原型与原型链以及Class语法糖这些后来加上的“官方外挂“如何弥补缺陷,实现高效的面向对象编程。

进阶焚诀:原型、原型链与Class,官方外挂辅助进阶

基础焚诀三件套的弊端我们已经看得明明白白,内存浪费、无法共享、继承无门,这套基础焚诀终究只能止步于新手阶段。而JS语言开发者早已为我们备好的三大官方辅助外挂,正是进阶的关键——它们本质就是系统赋予的高阶功法,帮我们打通面向对象的任督二脉。

接下来,我们先吃透每个外挂的抽象概念、核心作用、使用规范,再用对应的代码,针对性解决前面的每一个问题,让进阶焚诀彻底落地。

一、原型(prototype)

(原型就是系统给构造函数新加的门派公共武学藏经阁,区别于每个武者私有的剑囊(实例自身)。它不属于任何一个单独的实例,而是归属于整个门派(构造函数),所有弟子(实例)都能来这里借阅武学,不用各自手抄秘籍。)

官方概念

每个构造函数都有一个 prototype 属性,指向一个原型对象,通过 new 该构造函数创建的所有实例,都会自动继承这个原型对象上的属性和方法。

核心作用

1. 存放全实例共享的属性和方法,彻底避免重复创建,大幅节省内存

2. 实现公共内容统一管理,修改一次,全部门弟子同步生效

3. 解决基础三件套方法重复创建、属性无法共享的核心痛点

使用规范

1. 构造函数内只存放实例私有属性(姓名、门派等独属于个人的特征)

2. 所有共享属性、通用方法,全部挂载到 构造函数.prototype 上

3. 不随意在原型上篡改复杂引用类型,避免实例间互相干扰

(使用外挂解决问题一、二:内存浪费+无法共享)

// 构造函数:只存每个武者的私有属性(个人专属物品)
function MartialArtist(name, sect) {
  this.name = name; // 私有:姓名
  this.sect = sect; // 私有:所属门派
}

// 原型=公共藏经阁:存放全门派共享的宗旨、招式
MartialArtist.prototype.sectTenet = '以武入道,行侠仗义';
// 共享方法:所有弟子共用一套招式,无需各自抄写
MartialArtist.prototype.attack = function() {
  console.log(`${this.name}使出${this.sect}招式,秉持:${this.sectTenet}`);
};
MartialArtist.prototype.practice = function() {
  console.log(`${this.name}潜心修炼武功!`);
};

// 创建两个武当弟子实例
const zhangSan = new MartialArtist('张三', '武当派');
const liSi = new MartialArtist('李四', '武当派');

// 验证:两个实例的招式来自同一个藏经阁,是同一个方法
console.log(zhangSan.attack === liSi.attack); // true ✅ 内存仅存一份,无浪费

// 修改藏经阁里的门派宗旨,所有弟子同步更新
MartialArtist.prototype.sectTenet = '武当太极,以柔克刚';
console.log(zhangSan.sectTenet); // 武当太极,以柔克刚
console.log(liSi.sectTenet);     // 武当太极,以柔克刚 ✅ 共享生效,无需逐个修改

二、原型链(Prototype Chain)

(原型链是武林中的武学传承脉络,小辈弟子(子类实例)学艺,先学本门藏经阁(自身构造函数原型)的武功,若是本门没有,就去往上追根溯源,找师祖、祖师爷的藏经阁(父级原型、顶级原型),直到找到武学本源(既是null空:可以说是无招胜有招),这条层层向上的传承路径,就是原型链。)

官方概念

每个实例都有隐式原型 proto ,指向其构造函数的显式原型 prototype ;访问实例属性/方法时,会先查自身,再沿 proto 向上逐层查找,直至 null ,这条查找链路即为原型链。

核心作用

1. 构建父子级的武学传承关系,实现真正的继承,子类可复用父类所有能力

2. 让共享方法全程只存一份,多层传承也不会重复创建

3. 解决基础三件套继承繁琐、并非真正继承的痛点

使用规范

1. 用 call() 继承父类的私有属性,用 Object.create() 搭建原型链继承方法

2. 搭建原型链后,必须修正 constructor 指向,避免传承脉络混乱

3. 子类独有武功,挂载到子类自身原型,不污染父类藏经阁

(使用外挂解决问题三:继承无章法)

// 父类:基础武者(武林祖师级功法)
function MartialArtist(name, sect) {
  this.name = name;
  this.sect = sect;
}
// 父类原型:基础通用招式,所有武者都能学
MartialArtist.prototype.attack = function() {
  console.log(`${this.name}使出基础招式!`);
};

// 子类:武当弟子(继承基础武者,再学专属武功)
function WudangDisciple(name) {
  // 借用call:继承祖师的个人属性
  MartialArtist.call(this, name, '武当派');
}

// 搭建原型链:让武当弟子继承基础武者的藏经阁武功
WudangDisciple.prototype = Object.create(MartialArtist.prototype);
// 修正传承脉络指向
WudangDisciple.prototype.constructor = WudangDisciple;

// 子类原型:武当弟子专属太极剑法
WudangDisciple.prototype.wudangAttack = function() {
  console.log(`${this.name}使出太极剑法!`);
};

// 创建实例
const zhangSan = new WudangDisciple('张三');
const liSi = new WudangDisciple('李四');

// 沿原型链继承父类方法,共用一份,无内存浪费
zhangSan.attack(); // 张三使出基础招式!
console.log(zhangSan.attack === liSi.attack); // true ✅ 真正的传承,而非复制

// 调用自身专属武功
zhangSan.wudangAttack(); // 张三使出太极剑法!

三、Class 语法糖(包装原型和原型链,更易读易写更规范)

(Class是标准化的门派建制手册,它没有改变底层的武学传承(原型/原型链),只是把零散的藏经阁、传承脉络,整理成了规范、易懂的门派规矩,让开发者不用手动搭建原型、修正脉络,照着手册就能快速创建门派、设定传承,更省心更规范。)

官方概念

ES6推出的原型与原型链的语法糖,底层依旧基于原型机制,将构造函数、原型、继承封装为更贴近传统面向对象的简洁语法,提升代码可读性与工程化友好度。(程序员:class 真香!JS 引擎:呵,不还是原型那点事儿吗。)

核心作用

1. 彻底简化原型、原型链的手动操作,写法优雅简洁,结构清晰

2. 用 extends 一键实现继承, super 调用父类,自动处理原型链

3. 一次性解决基础三件套所有弊端,适配工程化开发

使用规范

1.  constructor 方法等同于构造函数,仅存放实例私有属性

2. 类中直接定义的方法,自动挂载到原型,实现共享

3. 用 static 定义静态属性/方法,作为全门派的共享内容,门派专属

4. 子类继承必须用 extends 声明,且 constructor 内必须先调用 super()调用父类构造函数再使用 this 

(终极外挂解决所有问题)

// Class父类:基础武者门派建制
class MartialArtist {
  // 构造器:初始化弟子私有属性
  constructor(name, sect) {
    this.name = name;
    this.sect = sect;
  }

  // 静态属性:全门派共享宗旨(门派可调用,弟子不行)
  static sectTenet = '以武入道,行侠仗义';

  // 方法自动挂载到原型,全弟子共享
  attack() {
    console.log(`${this.name}使出${this.sect}招式,秉持:${MartialArtist.sectTenet}`);
  }
  practice() {
    console.log(`${this.name}潜心修炼武功!`);
  }
}

// Class子类:武当弟子,extends自动搭建传承脉络
class WudangDisciple extends MartialArtist {
  constructor(name) {
    // super调用父类构造,继承属性
    super(name, '武当派');
  }
  // 子类专属招式
  wudangAttack() {
    console.log(`${this.name}使出太极剑法!`);
  }
}

// 创建实例
const zhangSan = new WudangDisciple('张三');
const liSi = new WudangDisciple('李四');

// 验证方法共享
console.log(zhangSan.attack === liSi.attack); // true ✅
console.log(zhangSan.sectTenet); // undefined 无法访问静态属性
// 调用继承+自有方法
zhangSan.attack(); // 张三使出武当派招式,秉持:武当太极,以柔克刚
zhangSan.wudangAttack(); // 张三使出太极剑法!

 

进阶焚诀总结

- 原型:解决共享与内存问题,打造公共武学库,告别重复造方法

- 原型链:解决继承问题,搭建武学传承,实现真正的能力复用

- Class:简化原型链操作,标准化写法,是工程化开发的最优解

这三大系统外挂,层层递进,彻底补上了基础三件套的短板,让JS面向对象从零散的招式,变成了体系化的高阶功法,这也是语言开发者赋予我们的官方外挂。

三篇小总结

写到这里,我也算把 JS 面向对象这一套彻底捋顺了。从最开始的  this 、 new 、构造函数,到后来解决问题的原型、原型链,再到看起来很高级的 class,其实一路走下来,底层逻辑都是相通的。

原型帮我们共享方法、省内存;

原型链帮我们实现继承、找方法;

class 只是把这些复杂的东西包装得更干净、更好写,本质还是原型那一套。

以前觉得这些概念又多又乱,真正拆开来一步步理解,才发现它们都是为了解决实际问题而出现的。没有那么玄乎,也没有那么难。整理下来,不仅会写会看懂语法,更要看懂为什么这么设计。

如有理解不当和错误的地方,欢迎大家指正,一起学习进步