原型:对象的"爹"
想象每个JavaScript对象都有一个"爹"(原型),当你问这个对象要东西(属性或方法)时:
- 它先摸摸自己口袋,有就直接给你
- 如果自己口袋没有,它就会问它爹要
- 它爹也没有?那就继续问它爷爷要...
这样一代代往上找,直到找到或者家族谱系到头了(null)
// 爷爷辈
const 爷爷 = { 家族姓氏: '张' };
// 爸爸辈
const 爸爸 = { 名字: '张三' };
Object.setPrototypeOf(爸爸, 爷爷); // 设定爸爸的原型是爷爷
// 儿子辈
const 儿子 = { 小名: '小明' };
Object.setPrototypeOf(儿子, 爸爸); // 设定儿子的原型是爸爸
console.log(儿子.小名); // "小明" - 自己的
console.log(儿子.名字); // "张三" - 爸爸的
console.log(儿子.家族姓氏); // "张" - 爷爷的
为什么要有这个机制?
- 省内存:所有儿子可以共用爸爸的方法,不用每人复制一份
function 手机(型号) {
this.型号 = 型号;
}
// 所有手机共用同一个拍照方法
手机.prototype.拍照 = function() {
console.log(`${this.型号}拍了一张照片`);
};
const 我的手机 = new 手机('小米13');
const 你的手机 = new 手机('iPhone15');
我的手机.拍照(); // "小米13拍了一张照片"
你的手机.拍照(); // "iPhone15拍了一张照片"
- 实现继承:就像现实中的家族传承
三个关键点
- _ _ proto_ _:每个对象都有的隐藏属性,指向它的"爹"(原型)
>现代浏览器可以用,但不是标准用法
>推荐用 Object.getPrototypeOf(obj) 获取
这里拓展一下Object.getPrototypeOf(obj)
//Object.getPrototypeOf() 是官方推荐的获取对象原型的方法,
//比直接使用 __proto__ 更规范和安全。
//下面我用几个具体例子来说明如何使用:
基本用法
// 创建一个普通对象
const animal = {
eats: true
};
// 创建另一个对象,并以animal作为其原型
const rabbit = {
jumps: true
};
Object.setPrototypeOf(rabbit, animal);
// 使用Object.getPrototypeOf()获取rabbit的原型
const rabbit的原型 = Object.getPrototypeOf(rabbit);
console.log(rabbit的原型 === animal); // true
console.log(rabbit的原型.eats); // true
构造函数场景示例
// 定义一个构造函数
function Person(name) {
this.name = name;
}
// 在构造函数的原型上添加方法
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
// 创建实例
const john = new Person('John');
// 获取实例的原型
const john的原型 = Object.getPrototypeOf(john);
console.log(john的原型 === Person.prototype); // true
john的原型.sayHi(); // "Hi, I'm undefined" (因为this指向原型对象)
john.sayHi(); // "Hi, I'm John" (正确调用)
- prototype: 只有函数有的特殊属性
>当这个函数被当作构造函数(new 函数())时,新对象的proto会指向这个prototype - 原型链的顶端:所有对象的祖宗都是Object.prototype,它的proto是null
日常比喻
图书馆借书:
你先在自己房间找书(自身属性)
找不到去家里书房找(原型)
还找不到去社区图书馆找(更上一级原型)
最后国家图书馆都找不到就返回"没找到"(undefined)
公司组织架构:
你有问题先自己解决
解决不了问组长(你的原型)
组长解决不了问经理(组长的原型)
一直到CEO(Object.prototype)
CEO上面没人了(null)
记住这个链条,你就理解了JavaScript对象如何"找人办事"的机制!