大家好呀,这是我的第一篇文章用于总结我的所学,可能有不对的地方,请大佬们多多见谅!!!
引言
原型链是JavaScript独特的核心继承机制,不依赖类的定义,而是通过对象间的原型关联,贯穿对象创建、属性查找、继承实现的全流程,是JS面向对象编程的底层逻辑支撑。
理解原型链是我们在JS的里程碑,不仅是其能帮助我们突破"面向对象编程"的认知瓶颈,掌握对象复用、继承对象复用、继承设计的核心思路,还能精准规避开发中原型污染。继承属性冲突等常见漏洞。所以接下来我会聊一下它的原理以及深入的一些东西。
原型链的底层认识
原型链的核心是js中对象与构造函数、原型对象之间的关联机制,这是js中面向对象特性的基石,所以我会从概念、形成、特性、示例四个维度去剖析:
基础认知
以下是一些基础的定义:
- 原型对象(prototype) :仅构造函数(如
Function、自定义构造函数)自带的属性,本质是一个普通对象。用于存储该构造函数所有实例共享的方法和属性,减少内存占用(无需每个实例重复创建相同方法)。 - 实例的隐式原型(proto) :所有对象(包括实例、原型对象、构造函数)天生自带的属性,指向其 "创建者"(构造函数)的
prototype。ES6 推荐用Object.getPrototypeOf(实例)替代__proto__(后者是非标准属性,存在兼容性风险)。 - 构造函数(constructor) :原型对象(
prototype)上的默认属性,指向该原型对象对应的构造函数。用于标识对象的 "创建来源",可通过实例.constructor追溯其构造函数。
三者的关系:实例通过 __proto__ 关联构造函数的 prototype,原型对象通过 constructor 反向关联构造函数,形成 "实例→原型对象→构造函数" 的三角闭环,即:实例.__proto__ === 构造函数.prototype,构造函数.prototype.constructor === 构造函数
AI绘图: 
原型链的出生以及找它的规则
原型链的本质是 "对象原型的链式关联",其形成逻辑与属性查找规则直接决定了 JS 的继承行为:
形成逻辑
- 实例的
__proto__指向其构造函数的prototype(如const obj = new Fn(),则obj.__proto__ = Fn.prototype); - 构造函数的
prototype本身是对象,它也有__proto__,指向其父构造函数的prototype(如Fn.prototype.__proto__ = Object.prototype,因Fn.prototype是Object的实例); - 以此类推,层层向上追溯,直到
Object.prototype的__proto__指向null(无更高层级原型),最终形成 "实例→子原型→父原型→...→Object.prototype→null" 的链状结构,即原型链。 小见解:可以把他具象化为梯子,逐级向上;
查找规则(委托机制)
当访问对象的某个属性 / 方法时,JS 会遵循 "就近原则" 的委托查找逻辑:
- 优先查找对象自身属性 (通过对象字面量定义、
this.xxx赋值的属性); - 若自身不存在,沿
__proto__向上遍历原型链,依次查找各层级原型对象上的属性; - 找到匹配属性 / 方法则立即返回,若遍历至
null仍未找到,返回undefined。
原型链的 "终点与特性"
终点:Object.prototype.__proto__ = null
Object是 JavaScript 中所有对象的 "顶层构造函数",其原型对象Object.prototype是原型链的最顶层节点;- 为避免无限循环查找,
Object.prototype.__proto__被设计为null,表示 "无更高层级原型",是原型链的最终终点。
核心特性
- 继承性 :所有实例可共享其原型链上所有原型对象的属性和方法,无需重复定义,是 JS 实现继承的核心原理(如数组实例可调用
Array.prototype.push(),也可调用Object.prototype.toString())。 - 委托机制 :属性查找是 "委托查找" 而非 "复制",原型链上的属性 / 方法仅存储在原型对象中,实例通过
__proto__委托访问,修改原型会影响所有关联实例(如修改Array.prototype的方法,所有数组实例都会受影响)。 - 单向性:原型链仅支持 "向上查找",无法向下查找(如父原型无法访问子原型或实例的属性)。
下面是一些示例,可以直接运行去控制台看看:
js
// 1. 定义构造函数
function Animal(type) {
this.type = type; // 实例自身属性
}
// 2. 给构造函数的原型添加方法(共享方法)
Animal.prototype.sayType = function() {
console.log(`类型:${this.type}`);
};
// 3. 创建实例
const dog = new Animal("犬科");
// 4. 打印原型链关系
console.log("dog.__proto__ === Animal.prototype:", dog.__proto__ === Animal.prototype); // true
console.log("Animal.prototype.__proto__ === Object.prototype:", Animal.prototype.__proto__ === Object.prototype); // true
console.log("Object.prototype.__proto__:", Object.prototype.__proto__); // null
console.log("dog.constructor === Animal:", dog.constructor === Animal); // true(原型对象的constructor指向构造函数)
// 5. 验证属性查找
dog.sayType(); // 输出"类型:犬科"(委托原型链查找方法)
console.log(dog.toString()); // 输出"[object Object]"(委托Object.prototype的方法)
深入应用:原型链的 "实战场景"
原型链并非抽象概念,而是 JavaScript 开发中实现继承、优化性能、封装逻辑的核心工具。以下从继承实现、实际场景、避坑技巧三个维度,拆解其落地应用:
原型链与继承实现(开发核心)
JavaScript 无原生 "类继承",所有继承行为均基于原型链实现。以下是四种核心继承方案的原理、代码示例与优劣分析:
原型链继承(最基础)
核心逻辑:直接让子类原型指向父类实例,使子类实例通过原型链访问父类的属性和方法。
js
// 父类
function Animal(name) {
this.name = name;
this.features = ["呼吸", "进食"]; // 引用类型属性
}
Animal.prototype.sayName = function() {
console.log(`我是 ${this.name}`);
};
// 子类
function Dog() {}
Dog.prototype = new Animal(); // 子类原型指向父类实例(核心)
Dog.prototype.constructor = Dog; // 修复 constructor 指向
// 测试
const dog1 = new Dog();
dog1.name = "旺财";
dog1.features.push("汪汪叫");
const dog2 = new Dog();
console.log(dog2.features); // 输出 ["呼吸", "进食", "汪汪叫"](共享属性被污染)
优点 :实现简单,直接继承父类原型的方法。缺点:1. 父类引用类型属性被所有子类实例共享,易污染;2. 子类实例创建时无法向父类构造函数传参。
组合继承(最常用)
核心逻辑:结合 "构造函数继承"(传参、独立属性)和 "原型链继承"(共享方法),互补优缺点。
js
// 父类
function Animal(name) {
this.name = name;
this.features = ["呼吸", "进食"];
}
Animal.prototype.sayName = function() {
console.log(`我是 ${this.name}`);
};
// 子类
function Dog(name, breed) {
Animal.call(this, name); // 构造函数继承:传参+独立属性(第一次调用父类构造)
this.breed = breed;
}
Dog.prototype = new Animal(); // 原型链继承:共享方法(第二次调用父类构造)
Dog.prototype.constructor = Dog;
Dog.prototype.sayBreed = function() {
console.log(`品种:${this.breed}`);
};
// 测试
const dog1 = new Dog("旺财", "柯基");
dog1.features.push("汪汪叫");
const dog2 = new Dog("小白", "柴犬");
console.log(dog2.features); // 输出 ["呼吸", "进食"](属性独立)
dog2.sayName(); // 输出 "我是 小白"(方法共享)
优点 :解决传参和属性污染问题,方法共享高效。缺点 :父类构造函数被执行两次,子类原型上存在冗余的父类属性(如 name、features)。
寄生组合继承(最优方案)
核心逻辑 :通过 Object.create 创建父类原型的副本,替代 "子类原型指向父类实例",避免父类构造函数重复执行。
js
// 父类
function Animal(name) {
this.name = name;
this.features = ["呼吸", "进食"];
}
Animal.prototype.sayName = function() {
console.log(`我是 ${this.name}`);
};
// 子类
function Dog(name, breed) {
Animal.call(this, name); // 仅调用一次父类构造
this.breed = breed;
}
// 关键:创建父类原型的空副本,作为子类原型(避免执行父类构造)
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.sayBreed = function() {
console.log(`品种:${this.breed}`);
};
// 测试
const dog = new Dog("旺财", "柯基");
dog.sayName(); // 输出 "我是 旺财"
console.log(dog.__proto__.__proto__ === Animal.prototype); // true(原型链正常)
优点 :继承逻辑完整,无冗余属性,性能最优,是 ES6 前推荐方案。缺点 :实现稍复杂(需手动修复 constructor)。
ES6 Class 与原型链的关系
核心结论 :class 是原型链继承的语法糖,底层仍依赖原型链机制,仅简化写法。
| ES6 Class 语法 | 对应的原型链逻辑 |
|---|---|
class Child extends Parent |
Child.prototype.__proto__ = Parent.prototype |
constructor() { super() } |
Parent.call(this, ...args)(调用父类构造) |
class 原型方法 |
挂载到 Child.prototype 上(共享方法) |
代码对应示例:
js
// ES6 Class 写法
class Animal {
constructor(name) {
this.name = name;
}
sayName() { // 挂载到 Animal.prototype
console.log(`我是 ${this.name}`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 对应 Animal.call(this, name)
this.breed = breed;
}
sayBreed() { // 挂载到 Dog.prototype
console.log(`品种:${this.breed}`);
}
}
// 底层原型链验证
const dog = new Dog("旺财", "柯基");
console.log(dog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true(extends 对应原型链指向)
实际开发中的原型链应用
原型链在开发中无处不在,以下是高频场景的落地实践:
场景 1:对象复用与优化(减少内存占用)
核心思路:将公共方法存入构造函数原型,而非实例自身,所有实例共享同一方法,降低内存消耗。
js
// 反例:每个实例都创建独立方法(内存浪费)
function Person(name) {
this.name = name;
this.sayHi = function() { // 每个实例都有独立的 sayHi 方法
console.log(`Hi, ${this.name}`);
};
}
// 正例:原型共享方法(所有实例复用同一方法)
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() { // 仅存储在 Person.prototype 中
console.log(`Hi, ${this.name}`);
};
const p1 = new Person("张三");
const p2 = new Person("李四");
console.log(p1.sayHi === p2.sayHi); // true(方法复用)
场景 2:原生对象的原型扩展(谨慎使用)
核心思路 :可给原生对象(如 Array、Object)原型添加工具方法,但严禁修改原生方法,避免全局污染。
js
// 安全扩展:添加自定义工具方法(不覆盖原生)
Array.prototype.myFilter = function(callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) result.push(this[i]);
}
return result;
};
const arr = [1, 2, 3];
console.log(arr.myFilter(item => item > 1)); // 输出 [2, 3]
// 危险操作:修改原生方法(导致全局异常)
Array.prototype.push = function() {
console.log("被篡改了");
};
arr.push(4); // 输出 "被篡改了",无法正常添加元素
替代方案 :封装独立工具函数(如 arrayUtil.filter),而非扩展原生原型。
场景 3:原型链与闭包的结合(封装私有属性)
核心思路:利用闭包隐藏私有属性,通过原型暴露公有方法,兼顾封装性和复用性。
js
function Person(name, age) {
// 闭包私有属性(外部无法直接访问)
const _privateProp = "私有值";
this.name = name;
this.age = age;
// 闭包私有方法
function _privateMethod() {
return _privateProp;
}
// 公有方法(通过原型暴露,可访问闭包属性)
Person.prototype.getPrivate = function() {
return _privateMethod();
};
}
const p = new Person("张三", 20);
console.log(p._privateProp); // undefined(无法访问私有属性)
console.log(p.getPrivate()); // 输出 "私有值"(通过公有方法访问)
场景 4:框架中的原型链应用
-
Vue 组件的原型继承 :Vue 实例的方法(如
$emit、$watch)均挂载在Vue.prototype上,所有组件实例通过原型链继承这些方法。js// 全局挂载工具方法(所有组件可访问) Vue.prototype.$utils = { formatTime: (time) => new Date(time).toLocaleString() }; // 组件中使用(通过原型链查找) export default { mounted() { console.log(this.$utils.formatTime(Date.now())); } }; -
jQuery 的链式调用 :jQuery 实例的方法(如
find、css)执行后返回this(实例本身),而这些方法均存储在jQuery.prototype上,通过原型链实现链式调用。js// jQuery 核心逻辑简化 function jQuery(selector) { this.elements = document.querySelectorAll(selector); } jQuery.prototype.css = function(key, value) { this.elements.forEach(el => el.style[key] = value); return this; // 返回实例,支持链式调用 }; jQuery.prototype.find = function(selector) { // 逻辑实现... return this; }; // 链式调用(基于原型链的方法复用) new jQuery("div").css("color", "red").find("span").css("font-size", "16px");
##开发避坑:原型链的 "常见问题"
原型污染(最危险)
定义 :修改原生对象(如 Object、Array)的原型,导致所有继承自该原型的对象受影响,引发全局异常。污染案例:
js
// 恶意修改 Object 原型
Object.prototype.toString = function() {
return "被污染了";
};
// 正常业务代码受影响
const obj = { name: "张三" };
console.log(obj.toString()); // 输出 "被污染了"(预期输出 "[object Object]")
解决方案:
- 禁止直接修改原生对象原型,使用
Object.create(null)创建无原型的纯净对象; - 冻结原生原型:
Object.freeze(Object.prototype),禁止修改; - 使用命名空间隔离自定义方法(如
myUtil.toString而非Object.prototype.toString)。
属性查找陷阱
陷阱 1:自身属性与原型属性重名
- 规则:自身属性优先级高于原型属性,修改自身属性不影响原型。
js
function Person() {}
Person.prototype.name = "默认名";
const p = new Person();
p.name = "张三"; // 自身属性
console.log(p.name); // 输出 "张三"(优先自身)
delete p.name; // 删除自身属性
console.log(p.name); // 输出 "默认名"(原型属性)
陷阱 2:delete 无法删除原型上的属性
- 规则:
delete仅能删除对象自身属性,无法删除原型链上的属性。
js
function Person() {}
Person.prototype.name = "默认名";
const p = new Person();
delete p.name; // 无效果(name 是原型属性)
console.log(p.name); // 输出 "默认名"
继承漏洞(共享属性污染)
问题:子类原型若直接引用父类原型对象,修改子类原型会同步修改父类原型,影响所有父类实例。
javascript
// 问题代码
function Parent() {}
Parent.prototype.info = { age: 20 };
function Child() {}
Child.prototype = Parent.prototype; // 直接引用(共享同一对象)
Child.prototype.info.age = 30; // 修改子类原型的属性
const parent = new Parent();
console.log(parent.info.age); // 输出 30(父类实例受影响)
解决方案:
- 使用
Object.create创建父类原型的副本(浅拷贝); - 若原型上有引用类型属性,使用深拷贝隔离:
Child.prototype = JSON.parse(JSON.stringify(Parent.prototype))。
面试拷打:原型链高频考点与实战
原型链是前端面试 "必考题",以下从概念、代码、编程三个维度,拆解考点与答题思路:
面试题分类解析(带思路 + 答案)
概念理解题(基础必问)
问题 1:请解释原型链的定义,以及 prototype、__proto__、constructor 的关系。
答题思路 :先定义原型链,再分别解释三个概念,最后总结三角关系。答案:
- 原型链:JavaScript 中对象通过
__proto__关联原型对象,原型对象再通过__proto__关联父原型,层层向上直到Object.prototype.__proto__ = null,形成的链状结构,是继承的核心机制。 - 关系:①
prototype是构造函数的属性,存储实例共享的方法 / 属性;②__proto__是所有对象的隐式原型,指向其构造函数的prototype;③constructor是原型对象的属性,指向对应的构造函数。 - 核心等式:
实例.__proto__ === 构造函数.prototype,构造函数.prototype.constructor === 构造函数。
问题 2:ES6 的 class 和 ES5 的原型链继承有什么区别?class 本质是什么?
答题思路 :先说明 "语法糖" 本质,再对比写法差异,最后强调底层一致。答案:
- 区别:① 写法不同:
class用class、extends、super等关键字,更简洁;ES5 需手动操作原型和构造函数。② 特性差异:class构造函数必须用new调用,ES5 构造函数可直接调用(易出错);class方法不可枚举,ES5 原型方法默认可枚举。 - 本质:
class是原型链继承的语法糖,底层仍依赖prototype、__proto__实现继承,extends对应原型链指向,super对应父类构造函数调用。
问题 3:Object.prototype 和 Function.prototype 的关系是什么?
答题思路 :明确两者的原型链关联,结合 "所有构造函数都是 Function 实例" 分析。答案:
Function.prototype.__proto__ === Object.prototype:Function.prototype是对象,其隐式原型指向顶层对象原型Object.prototype。- 特殊点:
Function既是构造函数也是对象,Function.__proto__ === Function.prototype(自引用),而Object作为构造函数,Object.__proto__ === Function.prototype(所有构造函数都是Function的实例)。
代码分析题(考察查找逻辑)
示例 1:分析以下代码输出结果,并说明原因。
js
function Fn() {
this.a = 1;
this.b = function() {
console.log("自身方法");
};
}
Fn.prototype.b = function() {
console.log("原型方法");
};
Fn.prototype.c = function() {
console.log("原型方法 c");
};
const f = new Fn();
console.log(f.a); // 输出?
f.b(); // 输出?
f.c(); // 输出?
delete f.b;
f.b(); // 输出?
答题思路 :按 "自身属性→原型属性" 的查找规则分析,逐步拆解。答案:
console.log(f.a):输出1(a是实例自身属性);f.b():输出自身方法(自身b方法优先级高于原型b方法);f.c():输出原型方法 c(c是原型属性,自身无);delete f.b后调用f.b():输出原型方法(删除自身b方法,沿原型链查找)。
以下代码是否存在原型污染?若有,会导致什么问题?
js
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === "object") {
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
merge({}, { __proto__: { toString: () => "污染" } });
console.log({}.toString()); // 输出?
答题思路 :判断是否修改原生对象原型,分析影响范围。答案:
- 存在原型污染。
merge函数遍历source的键时,会遍历到__proto__(原型属性),并修改target的__proto__,即Object.prototype。 - 后果:所有对象的
toString方法被篡改,{}.toString()输出污染,导致全局代码异常。
编程题(考察实战能力)
题目 1:用原型链实现 "动物→狗→柯基" 的三层继承,要求柯基实例能调用狗和动物的方法,且可传参初始化属性。
答题思路 :用寄生组合继承实现三层继承,确保每层构造函数可传参,方法共享。答案:
js
// 1. 顶层父类:动物
function Animal(type) {
this.type = type; // 动物类型
}
Animal.prototype.sayType = function() {
console.log(`动物类型:${this.type}`);
};
// 2. 子类:狗(继承动物)
function Dog(type, breed) {
Animal.call(this, type); // 传参给父类
this.breed = breed; // 狗的品种
}
// 寄生组合继承:继承动物的原型方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.sayBreed = function() {
console.log(`狗的品种:${this.breed}`);
};
// 3. 孙类:柯基(继承狗)
function Corgi(type, breed, name) {
Dog.call(this, type, breed); // 传参给父类(狗)
this.name = name; // 柯基名字
}
// 寄生组合继承:继承狗的原型方法
Corgi.prototype = Object.create(Dog.prototype);
Corgi.prototype.constructor = Corgi;
Corgi.prototype.sayName = function() {
console.log(`柯基名字:${this.name}`);
};
// 测试
const corgi = new Corgi("哺乳动物", "柯基", "旺财");
corgi.sayType(); // 输出 "动物类型:哺乳动物"(继承动物)
corgi.sayBreed(); // 输出 "狗的品种:柯基"(继承狗)
corgi.sayName(); // 输出 "柯基名字:旺财"(自身方法)
题目 2:封装一个工具函数,判断一个对象的属性是 "自身属性" 还是 "原型链上的属性"。
答题思路 :用 hasOwnProperty 判断自身属性,结合 in 运算符判断原型链属性。答案:
js
/**
* 判断对象属性的来源
* @param {Object} obj - 目标对象
* @param {string} key - 属性名
* @returns {string} - "自身属性"、"原型链属性"、"不存在"
*/
function judgePropertySource(obj, key) {
// 先判断属性是否存在
if (!(key in obj)) {
return "不存在";
}
// 判断是否为自身属性(hasOwnProperty 不遍历原型链)
if (obj.hasOwnProperty(key)) {
return "自身属性";
}
// 存在且非自身,即为原型链属性
return "原型链属性";
}
// 测试
function Person() {}
Person.prototype.name = "默认名";
const p = new Person();
p.age = 20;
console.log(judgePropertySource(p, "age")); // 自身属性
console.log(judgePropertySource(p, "name")); // 原型链属性
console.log(judgePropertySource(p, "gender")); // 不存在
题目 3:基于原型链实现单例模式(确保一个构造函数只能创建一个实例)。
答题思路:利用原型对象存储唯一实例,在构造函数中判断,若已存在则返回该实例。
答案:
js
function Singleton(name) {
// 判断原型上是否已存在实例
if (Singleton.prototype.instance) {
return Singleton.prototype.instance; // 直接返回已有实例
}
this.name = name;
// 将实例存储在原型上(唯一)
Singleton.prototype.instance = this;
}
// 测试
const instance1 = new Singleton("实例1");
const instance2 = new Singleton("实例2");
console.log(instance1 === instance2); // true(仅创建一个实例)
console.log(instance2.name); // 输出 "实例1"(第二个参数无效)
面试易错点与答题技巧
高频易错点
-
混淆
__proto__和prototype:- 记忆口诀:"实例用
__proto__,构造函数用prototype",所有对象都有__proto__,只有构造函数有prototype。
- 记忆口诀:"实例用
-
认为 "ES6
class无原型链" :- 误区:
class是语法糖,底层仍依赖prototype和__proto__,extends本质是原型链指向。
- 误区:
-
null和undefined的原型链:null无原型(__proto__不存在),undefined不是对象,无__proto__,访问会报错。
-
Function和Object的原型关系:- 易错点:
Function.prototype是对象,其__proto__指向Object.prototype;Object是构造函数,其__proto__指向Function.prototype。
- 易错点:
答题技巧
-
概念题:先画 "三角图" :
- 遇到
prototype、__proto__、constructor相关问题,先在草稿纸上画 "实例→原型对象→构造函数" 的三角关系图,再推导结论。
- 遇到
-
代码分析题:分步拆解查找流程:
- 步骤:① 确定对象的自身属性;② 沿
__proto__梳理原型链层级;③ 按 "自身→原型→父原型" 的顺序分析属性查找结果。
- 步骤:① 确定对象的自身属性;② 沿
-
编程题:先搭框架,再补细节:
- 如继承题:先写父类→子类构造函数(
call传参)→ 原型关联(Object.create)→ 修复constructor→ 扩展方法,最后测试边界情况(如传参、属性污染)。
- 如继承题:先写父类→子类构造函数(
总结与拓展
核心知识点回顾
- 原型链本质 :对象通过
__proto__形成的链式关联,终点是Object.prototype.__proto__ = null,是 JS 继承的底层支撑。 - 核心规则:属性查找遵循 "自身优先→向上委托",继承实现的最优方案是寄生组合继承。
- 开发原则:原型存储共享方法,实例存储私有属性;禁止修改原生原型,避免污染。
开发实战口诀
"原型共享方法,自身存私有属性;避免修改原生原型,寄生组合继承最优;属性查找看层级,delete 只删自身属。"
拓展方向
原型链与 ES6 Proxy、Reflect
-
Proxy可拦截原型链查找:通过get陷阱,自定义属性查找逻辑(如拦截原型链上的属性访问)。jsconst obj = { a: 1 }; const proxy = new Proxy(obj, { get(target, key) { if (!(key in target)) { return "属性不存在"; } return Reflect.get(target, key); // 反射获取属性 } }); console.log(proxy.b); // 输出 "属性不存在"(拦截原型链查找)
TypeScript 中对原型链的类型约束
-
TypeScript 通过接口和泛型,为原型链继承添加类型校验,避免类型错误。
ts// 父类接口 interface Animal { type: string; sayType(): void; } // 子类实现 class Dog implements Animal { type: string; constructor(type: string) { this.type = type; } sayType(): void { console.log(this.type); } } const dog: Animal = new Dog("哺乳动物"); // 类型约束生效
参考文章
《js 原型与原型链详解 (万文总结,一文搞懂原型链!)》 blog.csdn.net/Yi_qian1000...
《寄生组合继承:JavaScript 继承模式的优化方案》 blog.csdn.net/weixin_4255...
《JavaScript 核心概念 - 原型、原型链》 blog.csdn.net/weixin_3999...
《js 基础之阮一峰的面向对象编程》 www.cnblogs.com/lxq3280/p/1...
《MDN Web Docs - 继承与原型链》 developer.mozilla.org/zh-CN/docs/...
以上就是本文的所有内容了,如果你感觉有用的话,给主播点点赞和关注呗。