这是测试文本,单击 “编辑” 按钮更改此文本。
JavaScript原型(prototype)详解
面向编程小白的原型对象知识点汇总 – 用大白话解释JavaScript核心概念
什么是原型?
原型的概念
在JavaScript中,每个对象都有一个原型(prototype),原型也是一个对象。
你可以把原型想象成对象的”父母”或”模板”。当你创建一个新对象时,它可以从它的原型那里继承属性和方法。
大白话解释:
假设你有一个手机工厂(构造函数):
- 工厂的设计图纸就是原型(prototype)
- 按照图纸生产出来的手机就是实例
- 所有手机都可以使用图纸上定义的功能(方法)
代码示例:
// 构造函数(工厂)
function Phone(model) {
this.model = model;
}
// 原型(设计图纸)添加方法
Phone.prototype.makeCall = function() {
console.log(`用${this.model}打电话!`);
};
// 创建实例(生产手机)
const myPhone = new Phone(“iPhone 14”);
myPhone.makeCall(); // 用iPhone 14打电话!
function Phone(model) {
this.model = model;
}
// 原型(设计图纸)添加方法
Phone.prototype.makeCall = function() {
console.log(`用${this.model}打电话!`);
};
// 创建实例(生产手机)
const myPhone = new Phone(“iPhone 14”);
myPhone.makeCall(); // 用iPhone 14打电话!
原型链
原型链的概念
原型链是JavaScript实现继承的机制。
当你访问一个对象的属性或方法时:
- JavaScript先在对象自身查找
- 如果没找到,就去对象的原型上查找
- 如果还没找到,就去原型的原型上查找
- 直到找到属性或到达原型链顶端(null)
大白话解释:
想象你有一个问题需要解决:
- 你首先尝试自己解决(对象自身)
- 自己搞不定就问爸爸(原型)
- 爸爸搞不定就问爷爷(原型的原型)
- 直到有人能解决或追溯到祖先尽头
原型链示意图:
实例(myPhone) → 原型(Phone.prototype) → Object.prototype → null
原型链示例:
// 创建一个数组
const arr = [1, 2, 3];
// arr自身没有toString方法
// 查找Array.prototype → 找到toString方法
console.log(arr.toString()); // “1,2,3”
// Array.prototype也没有hasOwnProperty方法
// 继续向上查找Object.prototype → 找到方法
console.log(arr.hasOwnProperty(‘length’)); // true
const arr = [1, 2, 3];
// arr自身没有toString方法
// 查找Array.prototype → 找到toString方法
console.log(arr.toString()); // “1,2,3”
// Array.prototype也没有hasOwnProperty方法
// 继续向上查找Object.prototype → 找到方法
console.log(arr.hasOwnProperty(‘length’)); // true
构造函数
构造函数与原型的关系
在JavaScript中,构造函数是用来创建对象的函数。
每个构造函数都有一个prototype属性,它指向构造函数的原型对象。
当使用new关键字创建实例时:
- 新对象的内部链接(__proto__)指向构造函数的prototype
- 因此实例可以访问原型上的属性和方法
代码示例:
function Dog(name) {
this.name = name;
}
// 添加到Dog的原型
Dog.prototype.bark = function() {
console.log(`${this.name}:汪汪!`);
};
const myDog = new Dog(“旺财”);
myDog.bark(); // 旺财:汪汪!
// 检查关系
console.log(myDog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.constructor === Dog); // true
this.name = name;
}
// 添加到Dog的原型
Dog.prototype.bark = function() {
console.log(`${this.name}:汪汪!`);
};
const myDog = new Dog(“旺财”);
myDog.bark(); // 旺财:汪汪!
// 检查关系
console.log(myDog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.constructor === Dog); // true
重要关系图
构造函数(Dog) → prototype属性 → 原型对象(Dog.prototype)
↑ ↑
| constructor属性 |
| |
实例(myDog) → __proto__属性 → 原型对象(Dog.prototype)
↑ ↑
| constructor属性 |
| |
实例(myDog) → __proto__属性 → 原型对象(Dog.prototype)
原型继承
如何实现继承?
JavaScript使用原型链实现继承。
实现继承的关键步骤:
- 子构造函数调用父构造函数(使用call/apply)
- 设置子构造函数的原型为父构造函数的实例
- 重置子构造函数原型的constructor属性
继承实现代码:
// 父类
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name}在吃东西。`);
};
// 子类
function Cat(name, color) {
// 调用父类构造函数
Animal.call(this, name);
this.color = color;
}
// 设置原型链
Cat.prototype = Object.create(Animal.prototype);
// 修复constructor指向
Cat.prototype.constructor = Cat;
// 添加子类方法
Cat.prototype.meow = function() {
console.log(`${this.name}:喵喵!`);
};
// 创建实例
const myCat = new Cat(“小花”, “白色”);
myCat.eat(); // 小花在吃东西。
myCat.meow(); // 小花:喵喵!
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name}在吃东西。`);
};
// 子类
function Cat(name, color) {
// 调用父类构造函数
Animal.call(this, name);
this.color = color;
}
// 设置原型链
Cat.prototype = Object.create(Animal.prototype);
// 修复constructor指向
Cat.prototype.constructor = Cat;
// 添加子类方法
Cat.prototype.meow = function() {
console.log(`${this.name}:喵喵!`);
};
// 创建实例
const myCat = new Cat(“小花”, “白色”);
myCat.eat(); // 小花在吃东西。
myCat.meow(); // 小花:喵喵!
ES6的class语法
class与原型的关系
ES6引入了class关键字,但它只是原型的语法糖。
class语法更清晰,但底层仍然使用原型机制:
- class中的constructor对应构造函数
- class中定义的方法会添加到prototype上
- extends关键字实现原型继承
class示例:
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name}在吃东西。`);
}
}
class Cat extends Animal {
constructor(name, color) {
super(name); // 调用父类constructor
this.color = color;
}
meow() {
console.log(`${this.name}:喵喵!`);
}
}
const myCat = new Cat(“小花”, “白色”);
myCat.eat(); // 小花在吃东西。
myCat.meow(); // 小花:喵喵!
// 检查原型关系
console.log(myCat instanceof Cat); // true
console.log(myCat instanceof Animal); // true
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name}在吃东西。`);
}
}
class Cat extends Animal {
constructor(name, color) {
super(name); // 调用父类constructor
this.color = color;
}
meow() {
console.log(`${this.name}:喵喵!`);
}
}
const myCat = new Cat(“小花”, “白色”);
myCat.eat(); // 小花在吃东西。
myCat.meow(); // 小花:喵喵!
// 检查原型关系
console.log(myCat instanceof Cat); // true
console.log(myCat instanceof Animal); // true
class语法与传统原型的对比
传统原型 | class语法 |
---|---|
function Animal() {} | class Animal {} |
Animal.prototype.eat = function() {} | eat() {} |
使用call/apply实现继承 | 使用extends和super |
原型使用注意事项
原型修改与性能
原型修改的时机
在创建实例前修改原型:
// ✅ 正确做法:先修改原型,再创建实例
Phone.prototype.makeCall = function() {};
const myPhone = new Phone();
// ❌ 避免:创建实例后再添加方法
const myPhone = new Phone();
Phone.prototype.makeCall = function() {}; // 可行但不推荐
Phone.prototype.makeCall = function() {};
const myPhone = new Phone();
// ❌ 避免:创建实例后再添加方法
const myPhone = new Phone();
Phone.prototype.makeCall = function() {}; // 可行但不推荐
原型与性能优化
使用原型的好处:
- 节省内存 – 方法在原型上只有一份拷贝
- 动态扩展 – 修改原型会影响所有实例
动态扩展示例:
function Car() {}
const car1 = new Car();
const car2 = new Car();
// 最初没有drive方法
console.log(car1.drive); // undefined
// 在原型上添加方法
Car.prototype.drive = function() {
console.log(“行驶中…”);
};
// 所有实例都可以访问
car1.drive(); // 行驶中…
car2.drive(); // 行驶中…
const car1 = new Car();
const car2 = new Car();
// 最初没有drive方法
console.log(car1.drive); // undefined
// 在原型上添加方法
Car.prototype.drive = function() {
console.log(“行驶中…”);
};
// 所有实例都可以访问
car1.drive(); // 行驶中…
car2.drive(); // 行驶中…
原型使用原则
- 将方法放在原型上(节约内存)
- 将属性放在构造函数内(每个实例独立)
- 避免在原型上存放引用类型值(可能被意外修改)