用 98K 狙击步枪实现 JS 继承

三葉Leaves Author

手写继承就像使用 Kar98 狙击步枪,老、繁琐,但是稳固、可靠。

要实现子类继承父类的属性和方法,Java 来的小伙伴可能直接一个 extend 就解决了,JS 里确实也有这个语法糖,可见:ES6 面向对象编程的语法糖

但是手写一遍能帮助你深入理解 JS 的原型链以及面向对象编程,更别提后来的很多新特性也是基于这个实现的

例子介绍

这次我们就来实现一个模态框(Modal),父类模态框仅仅有弹窗标题内容两个属性以及一个 show 方法

子类模态框 Modal_plus 除了有父亲的所有属性和方法,还会有自己独有的 theme 属性和 changeTitle 方法。

源码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function Modal(title, content) {
// 定义父类的属性,这些即将被子类继承
this.title = title;
this.content = content;
}

// 定义父类的实例方法
Modal.prototype.show = function() {
console.log(`title 为${this.title} 的模态框显示了`);
};

// 开始写子类
function Modal_plus(title, content, theme) {
// 先把实例属性拿过来
Modal.call(this, title, content);

// 新的属性自己定义
this.theme = theme;
}

// 完成原型链继承,到这一步父亲的 show 方法也能用了
Modal_plus.prototype = Object.create(Modal.prototype);

// 修复 construct 指针,否则它会指向 modal 而不是期望的它自己
Modal_plus.prototype.constructor = Modal_plus;

// 这时候定义一个实例方法,就不会影响父亲了
Modal_plus.prototype.changeTitle = function () {
this.title = 'JOKE'
}

关键点 1:继承属性

1
Modal.call(this, title, content);

这段代码本质就是把父类里的两条 this 赋值语句拿过来执行一遍,但是是赋值给子类自己,因为第一个参数 this 的执行环境是在子类里。call 改变了执行父类构造函数时候的 this 环境。

这句执行完以后,父类的属性就都给子类了。

有疑问的话请见:

关键点2:实现原型链继承

1
2
// 完成原型链继承,到这一步父亲的 show 方法也能用了
Modal_plus.prototype = Object.create(Modal.prototype);

Object.create() 是一个非常纯粹的方法,它的目的只有一个:
创建一个新对象,并让这个新对象的 proto 指向你传入的参数。

为什么不直接写成 Modal_plus.prototype = Modal.prototype

这样做,Modal_plus.prototypeModal.prototype 会指向同一个对象。之后如果你给子类添加方法,父类也会得到,这显然是错误的。

这一步之后,原型链就此形成。

关键点3:修复 constructor

constructor 属性是什么?

默认情况下,任何函数的 prototype 对象上,都有一个 constructor 属性,这个属性会指回该函数本身。

  • 在代码的最开始,Modal.prototype.constructor 指向 Modal 函数。

  • 在代码的最开始,Modal_plus.prototype.constructor 指向 Modal_plus 函数。

这个属性的主要作用是让实例能够知道自己是由哪个构造函数创建的。例如,modal.constructor 应该返回 Modal。

但是执行完刚才的原型链继承代码后,会产生一个副作用:

Modal_plus.prototype.constructor 值变成了 Modal,而不是期望的 Modal_plus。
这显然是不对的,它理应指向它自己。

所以,我们简单修复一下即可,很好理解:

1
2
// 修复 construct 指针,否则它会指向 modal 而不是期望的它自己
Modal_plus.prototype.constructor = Modal_plus;

使用测试

测试有没有继承到父亲的方法和属性

1
2
3
4
5
// ================ 测试有没有继承到父亲的方法和属性

console.log(modal_plus.title); // 输出:'主题弹窗'
console.log(modal_plus.theme); // 输出: 'atom dark'
modal_plus.show() // 输出: 'title 为主题弹窗 的模态框显示了'

测试父亲会不会受子定义的方法影响

1
2
3
4
5
6
7
8
9
10
11
// ================ 测试父亲会不会受子定义的方法影响

const modal = new Modal('普通弹窗','我是普通弹窗')

const modal_plus = new Modal_plus('主题弹窗','我是主题弹窗','atom dark')

modal_plus.changeTitle()
console.log(modal_plus.title); // 输出 JOKE,子能正常用

modal.changeTitle() // 报错:modal.changeTitle is not a function
console.log(modal.title);

如果刚才没用 Object.create(),而是直接赋值,那这边就会发现父亲也能执行孩子的方法。

测试 construct 情况

1
2
3
4
5
6
7
8
9
10
11
12
13
// ================ 测试 construct 情况

// 下面两句输出的都是它自己:
/*
ƒ Modal_plus(title, content, theme) {
Modal.call(this, title, content);
this.theme = theme;
}
*/
console.log(Modal_plus.prototype.constructor);
console.log(modal_plus.__proto__.constructor);

// 如果把 Modal_plus.prototype.constructor = Modal_plus; 注释掉,则输出的是 Modal 的构造函数内容
  • 标题: 用 98K 狙击步枪实现 JS 继承
  • 作者: 三葉Leaves
  • 创建于 : 2025-07-22 00:00:00
  • 更新于 : 2025-08-15 12:09:51
  • 链接: https://blog.oksanye.com/933053f33e05/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
来发评论吧~
Powered by Waline v3.2.2