JavaScript 原型是什么
原型(prototype)是 JavaScript 里最让新人头疼的概念之一,很多资料一上来就讲很深。这一篇用入门深度把它讲清楚,只需要记住三件事,每个函数都有 prototype,每个对象都有 proto,实例找属性会沿着原型链从下往上找。
每个函数都有 prototype
在 JavaScript 里函数也是对象,而且每个函数天生自带一个 prototype 属性,它指向一个普通对象。
function User(name) {
this.name = name;
}
console.log(typeof User.prototype); // object
console.log(User.prototype); // 一个普通对象,目前是空的
注意普通对象没有 prototype 属性,只有函数有。这个 prototype 属性的作用,就是为将来 new 出来的实例准备一个”共享仓库”,下一篇会用它共享方法。
每个对象都有 proto
每个对象身上还有一个隐藏的属性 proto(前后各两个下划线),用来指向自己的”原型”。浏览器里可以直接访问 proto,但规范更推荐用 Object.getPrototypeOf() 获取。
function User(name) {
this.name = name;
}
const user1 = new User('张三');
console.log(user1.__proto__); // 可以直接访问
console.log(Object.getPrototypeOf(user1)); // 推荐写法
最关键的规律是,实例的 proto 指向它的构造函数的 prototype,可以用全等比较验证。
function User(name) {
this.name = name;
}
const user1 = new User('张三');
console.log(Object.getPrototypeOf(user1) === User.prototype); // true
一句话总结三者的关系,User.prototype 是挂在函数身上的对象,实例 user1 通过自己的 proto 指向它。构造函数、实例、原型三者靠这条线连在一起。
原型链是什么
访问对象的属性时,JavaScript 不是只在自己身上找,而是按下面的顺序找。
- 先在自己身上找,找到了就直接用。
- 找不到,就去 proto 指向的原型上找。
- 还找不到,就顺着原型的 proto 继续往上找。
- 一路找到 null 都没有,就返回 undefined。
这条从下往上一级一级找的链条,就是原型链。
function User(name) {
this.name = name;
}
User.prototype.city = '北京';
const user1 = new User('张三');
console.log(user1.name); // 张三,自己身上的属性
console.log(user1.city); // 北京,自己身上没有,顺着 __proto__ 在 User.prototype 上找到
console.log(user1.age); // undefined,一路找到 null 也没有
常用方法来自哪
平时用的数组和对象方法,其实都不是数组对象自己身上带的,而是沿原型链从原型上”借”来的。比如 push 方法住在 Array.prototype 上,每个数组实例的 proto 都指向 Array.prototype,所以每个数组都能用 push。
const arr = [];
console.log(arr.push); // 一个函数,方法存在
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true
const obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
console.log(obj.toString); // 对象的方法来自 Object.prototype
这么看,JavaScript 数组基本操作 里用的 push、join 等方法,本质都是实例沿原型链找到的共享方法。这个特性很关键,下一篇会利用它自己给实例”发方法”。
给原型添加属性
直接给 User.prototype 添加属性,所有实例立刻都能访问到。
function User(name) {
this.name = name;
}
User.prototype.city = '北京';
const user1 = new User('张三');
const user2 = new User('李四');
console.log(user1.city); // 北京
console.log(user2.city); // 北京
user1 和 user2 自己身上都没有 city,但都能通过 proto 找到 User.prototype 上的 city。这就是原型共享能力的来源,下一篇就讲怎么用这个能力共享方法。
小结
- 每个函数都有 prototype 属性,它是一个普通对象。
- 每个对象都有 proto,推荐用 Object.getPrototypeOf() 获取。
- 实例的 proto 指向构造函数的 prototype。
- 属性查找沿原型链从下往上,找不到就返回 undefined。
- 数组和对象的常用方法都来自原型,比如 Array.prototype 上的 push。
- 往 prototype 上添加属性,所有实例都能访问到。