第二十六天:原型与继承 — JS 的对象"基因"
蜗牛往上爬 · 前端工业可视化学习笔记 第 26 篇
JS 没有传统语言的class继承(ES6 的 class 也是原型链的语法糖),它的继承靠"原型链"实现。今天学构造函数、prototype、原型链,理解 JS 对象最核心的机制——这是面试必考,也是理解 ES6 class 的前提。
目录
一、为什么需要构造函数
用工厂函数创建对象有个问题:每个实例都会复制一份方法,浪费内存。
// ❌ 工厂函数:每个对象都有一份 check 方法
function createDevice(id, name) {
return {
id: id,
name: name,
check: function () { return this.id + " 正常"; }
};
}
var d1 = createDevice("CNC-001", "数控机床");
var d2 = createDevice("AGV-002", "搬运机器人");
console.log(d1.check === d2.check); // false!两份不同的函数
1000 台设备就有 1000 份 check 函数。构造函数 + 原型能解决这个问题:方法只存一份,大家共享。
二、构造函数 + new
// 构造函数:普通函数,约定首字母大写
function Device(id, name) {
this.id = id;
this.name = name;
this.status = "离线";
}
// 用 new 调用
var d1 = new Device("CNC-001", "数控机床");
var d2 = new Device("AGV-002", "搬运机器人");
console.log(d1.id); // "CNC-001"
console.log(d1 instanceof Device); // true
new 做了 4 件事:
1. 创建空对象 {}
2. 空对象的原型 → 指向构造函数的 prototype
3. this → 指向空对象,执行构造函数体
4. 返回这个对象
三、prototype 原型对象
方法放在 prototype 上,所有实例共享同一份:
Device.prototype.check = function () {
if (this.status === "离线") return this.id + " 已离线";
return this.id + " 运行正常";
};
Device.prototype.start = function () {
this.status = "运行中";
return this.id + " 已启动";
};
var d1 = new Device("CNC-001", "数控机床");
var d2 = new Device("AGV-002", "搬运机器人");
// ✅ 两个实例共享同一个方法
console.log(d1.check === d2.check); // true!
// 属性在实例上,方法在原型上
console.log(d1.hasOwnProperty("id")); // true(实例自己的)
console.log(d1.hasOwnProperty("check")); // false(原型上的)
分工:
属性(id/name/status)→ 放构造函数里(每个实例一份)
方法(check/start)→ 放 prototype 上(所有实例共享)
四、原型链
JS 的继承就是"原型链"——访问属性时沿链向上查找。
console.log(d1.toString()); // "[object Object]" ← 来自 Object.prototype
// 查找过程:d1 自身 → Device.prototype → Object.prototype → null
console.log(d1.__proto__ === Device.prototype); // true
console.log(Device.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null(链条终点)
原型链图示:
d1 ──__proto__──> Device.prototype ──__proto__──> Object.prototype ──> null
自身属性:id/name/status 方法:check/start 方法:toString/hasOwnProperty
五、原型链实现继承
// 父类:设备
function Device(id, name) {
this.id = id;
this.name = name;
}
Device.prototype.check = function () {
return this.id + " 状态正常";
};
// 子类:数控机床
function CNCDevice(id, model, axis) {
Device.call(this, id, "数控机床"); // 调用父构造函数,继承 id/name
this.model = model;
this.axis = axis;
}
// ⭐ 关键:子类原型指向"父类原型的副本"
CNCDevice.prototype = Object.create(Device.prototype);
CNCDevice.prototype.constructor = CNCDevice; // 修正 constructor
// 子类自己的方法
CNCDevice.prototype.startMachining = function () {
return this.name + " " + this.model + " 开始加工";
};
// 覆盖父类方法(多态)
CNCDevice.prototype.check = function () {
return this.id + " 数控系统正常,轴数:" + this.axis;
};
var cnc = new CNCDevice("CNC-001", "VMC850", 5);
console.log(cnc.check()); // "CNC-001 数控系统正常,轴数:5"
console.log(cnc.startMachining()); // "数控机床 VMC850 开始加工"
console.log(cnc instanceof CNCDevice); // true
console.log(cnc instanceof Device); // true(在继承链上)
继承的核心一句话:
子类.prototype = Object.create(父类.prototype)。ES6 的class extends(第 15 周)会自动完成这些,但现在理解原理很重要。
六、查找规则与 hasOwnProperty
// 访问 obj.xxx 的查找规则:
// 1. obj 自身有?→ 用
// 2. 没有 → 沿 __proto__ 找 prototype
// 3. 一路向上到 Object.prototype → 还没有 → undefined
// 判断属性归属
function Device(id) { this.id = id; }
Device.prototype.check = function () {};
var d = new Device("CNC-001");
console.log(d.hasOwnProperty("id")); // true(自身属性)
console.log(d.hasOwnProperty("check")); // false(原型方法)
console.log("check" in d); // true(原型链上能找到)
七、总结
🎯 原型是 JS 对象机制的核心。明天学 DOM——用 JS 操作网页,把原型对象的方法用到真正的页面交互上。