原型和原型链
📌 为什么需要原型?—— 解决资源浪费问题
想象一下,我们要创建许多个“人”的对象。每个对象都有名字、年龄,并且都能说话。如果没有原型:
// 每次调用这个函数,都会创建一个新的 sayHello 函数
function createPerson(name, age) {
return {
name: name,
age: age,
sayHello: function () {
console.log(`Hello, I'm ${this.name}`);
},
};
}
const p1 = createPerson("Alice", 25);
const p2 = createPerson("Bob", 30);
console.log(p1.sayHello === p2.sayHello); // false
🚨 问题: 虽然 p1 和 p2 的 sayHello 功能一样,但它们却是两个不同的函数实例,占用了两份内存空间。如果有成千上万个对象,这种浪费会非常严重。
💡 解决方案 —— 将共用资源放到一个共享地方
我们希望所有“人”对象都共享同一个 sayHello 函数。这个共享的地方,就是原型(Prototype)。
在 JavaScript 中,每当你创建一个函数时,都会同时创建一个对应的原型对象。
// 1. 我们创建一个构造函数(首字母大写是一种约定)
function Person(name, age) {
this.name = name;
this.age = age;
}
// 2. JS 会自动为 Person 函数创建 prototype 属性
// 这个属性指向一个空对象,我们称之为“原型对象”
// 我们可以在这个共享对象上添加方法
Person.prototype.sayHello = function () {
console.log(`Hello, I'm ${this.name}`);
};
// 3. 使用 new 关键字来创建对象(实例)
// new 关键字会做一件非常重要的事:
// 它将新对象的__proto__属性指向Person.prototype
const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);
// 4. 现在,所有由Person创建的对象,都能访问同一个方法
person1.sayHello(); // Hello, I'm Alice
person2.sayHello(); // Hello, I'm Bob
// 5. 验证它们确实是同一个函数 // true 🎉
console.log(person1.sayHello === person2.sayHello);
关键点:
prototype:是函数才有的属性,它指向一个对象(原型对象),这个对象的用途就是让所有由该函数创建的实例共享方法和属性。
__proto__:是每个对象(含函数对象)都有的属性,它指向创建该对象的构造函数的 prototype。
person1.__proto__ === Person.prototype
- ❗注意:
__proto__ 是一个非标准的历史遗留属性,用于学习和调试。在现代代码中,建议使用 Object.getPrototypeOf(obj) 获取原型。
🔗 什么是原型链?—— 查找机制
当我们访问一个对象的属性(或方法)时,JavaScript 引擎会:
- 先在对象自身上查找
- 如果没找到,就通过
__proto__ 去它的原型对象找
- 如果原型对象上也没找到,就再去原型对象的原型对象上找(因为原型对象本身也是一个对象,它也有
__proto__)。
- 一直这样向上查找,直到找到某个原型对象为
null 为止(这是查找的终点)。
- 如果直到终点都没找到,则返回
undefined。
这种一层一层向上链接、形成链式关系的结果,就叫做原型链。
代码演示如下:
// 1. 在自身查找 name
console.log(person1.name); // 'Alice'(在自身找到)
// 2. 自身没有 sayHello,
// 通过 __proto__ 到 Person.prototype 上找
console.log(person1.sayHello); // 在原型上找到了
// 3. 我们来看看原型链的尽头
// Person.prototype 也是一个对象,它是被 Object()
// 这个内置构造函数创建的,所以
// Person.prototype.__proto__ === Object.prototype
log(person1.__proto__ === Person.prototype); // true
// true,此处应该是 ===,图片显示不下,所以用 = 代替
log((Person.prototype.__proto__ = Object.prototype));
// 4. Object.prototype 是绝大多数对象的原型链终点,
// 它的 __proto__ 是 null
console.log(Object.prototype.__proto__); // null
// 5. 尝试查找一个不存在的属性,查找路径:
// person1 -> Person.prototype ->
// Object.prototype -> null
console.log(person1.notExistProp); // undefined
📊 图示化理解:
下图清晰地展示了实例对象 person1、构造函数 Person 和原型对象之间的完整链条关系:
💬 细心的宝子可能发现了,图中为什么多出了 Function.prototype?
🚀 这是因为在 js 中,函数本身也是对象,所以 Person 是 Function 构造函数的实例。所以 Person 的 __proto__ 指向 Function.prototype
📝 总结
- 原型 (
Prototype)
- 一个简单的对象,用于存储可以被其他对象共享的属性和方法。
- 函数的
prototype属性规定了由它创建的对象能共享什么。
- 对象的
[[Prototype]] (可通过 __proto__ 或 Object.getPrototypeOf() 访问)规定了它自己能使用什么共享资源。
- 原型链 (
Prototype Chain)
- 由多个对象通过
__proto__ 链接形成的链式结构
- 机制:当访问一个对象的属性时,如果自身没有,就沿着这条链向上查找,直到找到或到达终点(
null)。
- 意义:实现了
JavaScript 的“继承”机制。一个对象可以“继承”其原型链上所有对象的属性和方法。
- 补充说明:
constructor 属性
每个函数的 prototype 对象都有一个 constructor 属性,指回函数本身。
- Person.prototype.constructor === Person
- person1.constructor === Person(这样实例就能知道自己是由谁创建的了)
🥳🥳🥳 ==你已经看完了,赶快去挑战下吧!
✨赶快测验下吧~✨
function Vehicle() {
this.wheels = 4;
}
Vehicle.prototype.drive = function () {
console.log("Drive");
};
function Car(brand) {
this.brand = brand;
}
Car.prototype = new Vehicle();
Car.prototype.constructor = Car;
const myCar = new Car("Toyota");
- myCar.hasOwnProperty('wheels') 输出什么?
- myCar.proto === Car.prototype 输出什么?
- Car.prototype.proto === ? // 填写完整等式