很多同学学了多年JavaScript,却对原型链一知半解。其实,理解了原型链,你才能真正读懂JavaScript的设计哲学!
一、从现实生活理解原型
想象一下制造业的模具生产:有一个标准模具(原型),通过这个模具生产出多个产品(实例),所有产品都继承模具的特性,如果想修改所有产品,只需修改模型。
这就是JavaScript原型思想。
二、3分钟搞懂原型链核心概念
2.1 什么是原型(Prototype)
每个JavaScript对象都有一个隐藏属性[[Prototype]],这就是它的"原型"。
// 创建一个简单的对象
const person = {
name: "小明",
age: 25
};
console.log(person.toString()); // [object Object]
为什么person对象可以调用toString方法?这就是原型在起作用!
2.2 原型链查找机制
当我们访问对象的属性或方法时,JavaScript会:
- 先在对象自身查找
- 如果找不到,沿着原型链向上查找
- 直到找到属性或到达原型链尽头(null)
const animal = {
eats: true
};
const rabbit = {
jumps: true
};
// 设置rabbit的原型为animal
rabbit.__proto__ = animal; // 实际开发中不要直接使用__proto__
console.log(rabbit.jumps); // true (自身属性)
console.log(rabbit.eats); // true (从原型继承)
Important: __proto__已废弃,推荐使用Object.getPrototypeOf()和Object.setPrototypeOf()。
三、创建对象的方式
3.1:字面量创建(最常用)
const person = {
name: "小明",
sayHello() {
console.log(`你好,我是${this.name}`);
}
};
优:简单直观
缺:创建多个相似对象时代码重复
该方式创建对象的原型是:Object.prototype;
其原型链是:对象person → Object.prototype → null
其对应关系是:person.proto === Object.prototype
3.2 工厂模式
function createPerson(name) {
var o = new Object();
o.name = name;
o.getName = function () {
console.log(this.name);
};
return o;
}
var person1 = createPerson('大王');
var person2 = createPerson("小王");
优:简单
缺:对象无法识别,因为所有的实例都指向一个原型
该方式创建对象的原型是:Object.prototype;
其原型链是:对象person1 → Object.prototype → null
其对应关系是:person1.proto === Object.prototype
3.3 构造函数模式
function Animal(name) {
this.name = name;
this.sayHello = function() {
console.log(`你好,我是${this.name}`);
};
}
const cat = new Animal("小猫");
const dog = new Animal("小狗");
优:实例可以识别为一个特定的类型;
缺:每次创建实例时都要创建新的函数,造成内存浪费;
该方式创建对象的原型是:Animal.prototype;
其原型链是:对象cat→ Animal.prototype → Object.prototype → null
其对应关系是:cat.proto === Animal.prototype
3.4 原型模式(解决内存浪费问题)
function Animal(name) {
this.name = name;
}
Animal.prototype.canEat = []; //所有实例共享同一个数组
// 方法定义在原型上,所有实例共享
Animal.prototype.sayHello = function() {
console.log(`你好,我是${this.name}`);
};
const cat = new Animal("小猫");
cat.canEat.push("carrot");
const dog = new Animal("小狗");
console.log(cat.sayHello === dog.sayHello); // true
console.log(dog.canEat); //['carrot'] 问题!
优:方法共享,节省内存
缺:所有实例共享方法(引用类型属性),可能产生意外
3.5 组合方式(构造函数+原型模式)
function Person(name, age) {
// 实例特有属性放在构造函数
this.name = name;
this.age = age;
this.friends = []; // 引用类型,每个实例独立
}
// 共享方法放在原型
Person.prototype.sayHello = function() {
console.log(`你好,我是${this.name}`);
};
👍 最常用方式:结合了前两种方式的优点,初始化简单,方法共享,节省内存。
3.6 Class语法糖(ES6+)
本质:Class就是构造函数的语法糖,底层还是基于原型
class Animal {
constructor(name) {
this.name = name;
}
// 自动添加到原型
sayHello() {
console.log(`你好,我是${this.name}`);
}
// 静态方法
static createAnonymous() {
return new Animal("匿名");
}
}
//使用
const Cat = new Animal("小猫");
Cat.sayHello(); //你好,我是小猫
const annoymous = Animal.createAnonymous();
annoymous.name = "小狗";
annoymous.sayHello(); //你好,我是小狗
四、面向对象的继承方式
继承指的是一个类(子类)基于另一个类(父类)来构建,自动获得父类的属性和方法,并可以添加新的功能或修改现有功能。原型链则是继承的实现基础。
4.1 原型链继承
function Parent() {
this.name = "父级";
this.colors = ["red", "blue"];
}
function Child() {
this.age = 10;
}
// 关键:设置原型
Child.prototype = new Parent();
// 定义两个实例
const child1 = new Child();
const child2 = new Child();
child1.colors.push("green");
console.log(child2.colors); // ["red", "blue", "green"] 问题!
缺:所有子类实例共享引用类型的属性
4.2 构造函数继承
// 父类构造函数
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
this.sayHello = function() {
console.log('Hello, ' + this.name);
};
}
// 父类原型上的方法
Parent.prototype.sayName = function() {
console.log('My name is ' + this.name);
};
// 子类构造函数
function Child(name, age) {
// 关键步骤:借用父类构造函数
// 在子类实例的上下文中执行父类构造函数
Parent.call(this, name);
this.age = age;
}
// 使用子类创建实例
var child1 = new Child('大王', 20);
var child2 = new Child('小王', 10);
console.log(child1.name); // "大王"
console.log(child2.name); // "小王"
// 引用类型属性是独立的
child1.colors.push('black');
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
console.log(child2.colors); // ['red', 'blue', 'green'](没有相互影响)
child1.sayHello(); // Hello, 大王
child1.sayName(); // TypeError: child1.sayName is not a function (无法访问原型上的方法)
缺:无法继承父类原型上的方法和属性,方法无法复用
4.3 组合继承(最常用)
function Parent(name) {
this.name = name;
this.colors = ["red", "blue"];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 第二次调用Parent
this.age = age;
}
Child.prototype = new Parent(); // 第一次调用Parent
Child.prototype.constructor = Child;
const child = new Child("小明", 10);
缺:虽完美解决两种方案缺陷,但构造函数被调用了2次
4.4 原型式继承
// 核心函数:基于已有对象创建新对象
function object(o) {
function F() {} // 创建一个临时构造函数
F.prototype = o; // 将传入对象作为原型
return new F(); // 返回这个构造函数的实例
}
const person = {
name: '匿名',
colors: ["red", "blue"],
sayName: function() {
console.log(this.name);
}
};
// 创建新对象
var person1 = object(person);
// 或用现代的 Object.create()
var person2 = Object.create(person);
person1.name = '大王';
person2.name = '小王';
person1.colors.push("green");
console.log(person1.name); // "大王"
console.log(person2.name); // "小王"
console.log(person2.colors); // ['red', 'blue', 'green']
缺:同原型链继承,所有子类实例共享引用类型的属性
4.5 寄生式继承
称之为寄生式继承的原因:它不直接创建新对象,而是基于现有对象进行扩展,通过"寄生"在已有对象上,添加新的属性和方法本质上是对原有对象的增强和改造。
function createChild(parent) {
const clone = Object.create(parent);
clone.sayHello = function() {
console.log("Hello");
};
return clone;
}
缺:函数方法无法复用,内存效率极低(与构造函数继承的问题相同);共享引用类型(parent)的属性。
4.6 寄生组合式继承(最理想)
// 父类
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
// 子类
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
// 寄生组合式继承
function inheritPrototype(child, parent) {
// 创建父类原型的纯净副本,建立原型链连接
const prototype = Object.create(parent.prototype);
// 修复构造函数的指向,保持正确的继承关系
prototype.constructor = child;
// 将修复好的原型对象设置为子类的原型
child.prototype = prototype;
}
// 关键步骤
inheritPrototype(Child, Parent);
const child = new Child("小明", 10);
👍 最佳实践:只调用一次父类构造函数,原型链保持正确
4.7 Class继承(ES6+)
底层原理:基于寄生组合式继承
class Parent {
constructor(name) {
this.name = name;
this.colors = ["red", "blue"];
}
sayName() {
console.log(this.name);
}
static staticMethod() {
console.log("父类静态方法");
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 必须调用super
this.age = age;
}
// 方法重写
sayName() {
super.sayName(); // 调用父类方法
console.log(`年龄: ${this.age}`);
}
}
五、实际项目应用场景
场景1:Vue组件扩展(原型链的应用)
// 基础组件
const BaseComponent = {
data() {
// ***
},
methods: {
// ***
}
};
// Vue2的mixin机制就是基于原型链的
// 具体页面组件
const UserPage = {
mixins: [BaseComponent],
data() {
// ***
},
methods: {
// ***
}
};
Vue2 在处理 Mixin 和组件选项的合并时,其核心的覆盖和查找逻辑,完全遵循并利用了 JavaScript 对象原型链的规则——即“自身属性优先于原型上的属性”。它没有创造新规则,只是巧妙地将 JavaScript 语言本身的特性应用在了框架的代码组织上。
场景2: 工具类继承体系
class ApiClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const response = await fetch(`${this.baseURL}${endpoint}`, options);
return this.handleResponse(response);
}
async handleResponse(response) {
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
return response.json();
}
}
// UserApi 继承 ApiClient
class UserApi extends ApiClient {
constructor() {
super('/api');
}
async getUsers() {
return this.request('/users');
}
async createUser(userData) {
return this.request('/users', {
method: 'POST',
body: JSON.stringify(userData)
});
}
}
场景3: 错误处理继承体系
class AppError extends Error {
constructor(message, code = 'UNKNOWN_ERROR') {
super(message);
this.name = this.constructor.name;
this.code = code;
this.timestamp = new Date();
}
// JSON格式化
toJSON() {
return {
name: this.name,
message: this.message,
code: this.code,
timestamp: this.timestamp
};
}
}
// ValidationError 继承 AppError
class ValidationError extends AppError {
constructor(field, message) {
super(message, 'VALIDATION_ERROR');
this.field = field;
}
}
// 使用
try {
throw new ValidationError('email', '邮箱格式不正确');
} catch (error) {
if (error instanceof AppError) {
console.error(error.toJSON());
}
}
最佳实践说明:
- 方法共享:将公共方法放在原型上,节省内存
- 继承层次不宜过深:最多2-3层继承,否则难以维护
- 合理使用Class语法:ES6 Class让原型操作更直观
六、面试常见问题解析
问题1: instanceof的原理是什么?
instanceof原理是检查对象的原型链上是否存在构造函数的 prototype属性。
它会沿着对象的原型链向上查找,若找到了某个原型等于构造函数的 prototype,就返回 true,直到原型链尽头(null)还没找到就返回 false。
// instanceof 检查原型链
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true
// 手动实现instanceof
function myInstanceof(obj, constructor) {
let proto = Object.getPrototypeOf(obj);
while (proto) {
if (proto === constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
问题2: 如何实现深拷贝?
深拷贝指的是创建一个全新的对象,并递归地复制原对象的所有属性(包括嵌套的对象和数组),使得新对象与原对象完全独立,修改新对象不会影响原对象。
function deepClone(obj, hash = new WeakMap()) {
// 非对象直接返回
if (obj === null || typeof obj !== 'object') {
return obj;
}
// has(WeakMap):用于缓存已拷贝对象,解决循环引用
// 若已拷贝过该对象,返回缓存
if (hash.has(obj)) {
return hash.get(obj);
}
//辨别是数组或普通对象,创建对应的空容器;并缓存原对象和缓存结果
const result = Array.isArray(obj) ? [] : {};
hash.set(obj, result);
// 递归复制所有属性
for (let key in obj) {
//只处理对象自身的属性(不处理原型链上的属性)
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key], hash);
}
}
return result;
}
// 使用
const obj = {
name: '大王',
hobbies: ['reading', 'swimming'],
}
const cloneObj = deepClone(obj);
七、总结
原型链是JavaScript面向对象的灵魂,理解它能帮助我们
- 更好地使用框架(React/Vue的组件系统基于原型思想)
- 编写更优雅的代码(合理的继承体系)
- 深入理解语言特性(包括ES6 Class的本质)
- 解决复杂问题(如深拷贝、类型判断等)
记住:Class只是语法糖,原型是根本
八、扩展
JavaScript面向对象思想除了继承(作用:代码复用,层次关系)外,还包括以下思想:
- 🎯 封装 - 信息隐藏,访问控制
- 🧩 组合 - 灵活组装,功能混合
- 🌈 多态 - 接口统一,实现多样
- 📨 消息传递 - 事件驱动,松耦合通信
- 🤝 委托 - 职责转移,行为代理
- 📝 抽象 - 隐藏细节,暴露本质
- 💉 依赖注入 - 控制反转,解耦管理
这些思想共同构成了 JavaScript的完整体系,以及丰富且灵活的面向对象编程范式,方便开发者根据具体需求选择适合的模式来构建可维护、可扩展的应用程序。
下期预告
下一次我们将深入探讨作用域链和闭包,这是JavaScript的另一个核心概念。
如果觉得有帮助,请关注+点赞,这是对我最大的鼓励!
1128

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



