第五章:原型与原型链
- 它是什么 先记住 5 个核心概念:
- 对象
- prototype
- proto
- 原型链
- new
它们之间的关系可以先粗暴记成一句话:对象通过 __proto__ 关联到原型对象,构造函数通过 prototype 提供共享成员,属性查找沿着这条链向上,这就是原型链。
- 为什么这样设计 因为 JS 早期不是传统 class-based 语言,它更偏向
prototype-based。
它要解决两个问题: - 多个对象怎么共享方法,避免每个对象都拷贝一份
- 对象之间怎么建立继承关系 比如:
function Person(name) {
this.name = name
}如果把方法都写到构造函数里:
function Person(name) {
this.name = name
this.say = function () {
console.log(this.name)
}
}那每 new 一个实例,都会新建一个 say 函数,浪费内存。
所以 JS 设计了原型:
function Person(name) {
this.name = name
}
Person.prototype.say = function () {
console.log(this.name)
}这样所有实例共享同一个 say。
所以原型机制的核心目的就是:让对象可以共享属性和方法,并通过链式关系实现继承。
3. prototype、proto、constructor 分别是什么
这是本章第一重点。
3.1 prototype 是什么
prototype 是 函数 才有的一个属性。
function Person() {}
console.log(Person.prototype)它本质上是一个对象,这个对象会作为将来实例的原型对象。
你可以理解为:构造函数的 prototype 是给实例共享成员用的
例如:
function Person(name) {
this.name = name
}
Person.prototype.say = function () {
console.log(this.name)
}这里say就放在原型上,所有Person实例都能访问。
3.2 __proto__ 是什么
__proto__ 是 对象 身上的一个隐式原型引用。
const obj = {}
console.log(obj.__proto__)你可以把它理解成:对象通过 __proto__ 指向它的原型对象
例如:
function Person() {}
const p = new Person()
console.log(p.__proto__ === Person.prototype) // true这句非常重要,你要背下来:实例对象的 __proto__ 指向构造函数的 prototype
3.3 constructor 是什么
原型对象上通常会有一个constructor属性,指回构造函数本身。
function Person() {}
console.log(Person.prototype.constructor === Person) //
true所以一般关系是:
Person.prototype.constructor === Personp.__proto__ === Person.prototype
4. 三者关系图
你可以在脑子里记成这样:
function Person() {}
const p = new Person()关系是:
Person.prototype是原型对象p.__proto__ === Person.prototypePerson.prototype.constructor === Person
这三句是原型章节最基础的骨架。
5. 原型链是什么
它是什么
原型链就是:对象在查找属性时,如果自己没有,就沿着 __proto__ 一层一层往上找,直到 null 为止的链式路径
例如:
function Person(name) {
this.name = name
}
Person.prototype.say = function () {
console.log(this.name)
}
const p = new Person('Tom')
p.say()为什么 p 自己没有 say 也能调用?
因为查找过程是:
- 先看
p自己身上有没有say - 没有就去
p.__proto__,也就是Person.prototype上找 - 在那里找到了,于是执行 这条查找路径就是原型链。
6. 为什么几乎所有对象最后都能找到 Object.prototype
因为大多数普通对象的原型链顶层都会连到:
Object.prototype 再往上是:
Object.prototype.__proto__ === null 也就是说,原型链的终点通常是 null。
所以:
const obj = {}
obj.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // true这就是为什么很多对象都能用: toStringhasOwnProperty 因为这些方法在 Object.prototype 上。
7. new 到底做了什么
这是本章第二重点,也是面试超高频。
function Person(name) {
this.name = name
}
const p = new Person('Tom')
new Person('Tom')大致做了 4 件事:
- 创建一个新对象
- 让这个新对象的 proto 指向 Person.prototype
- 用这个新对象作为 this 执行构造函数
- 如果构造函数没有显式返回对象,就返回这个新对象 你可以粗略理解成伪代码:
function myNew(fn, ...args) {
const obj = {}
obj.__proto__ = fn.prototype
const result = fn.apply(obj, args)
return typeof result === 'object' && result !== null ? result : obj
}这就是为什么:
p.__proto__ === Person.prototype成立。
8. instanceof 的底层原理
这也是超高频。
p instanceof Person 本质判断的是:
Person.prototype 是否出现在 p 的原型链上也就是沿着:
p.__proto__
p.__proto__.__proto__
...一路往上找,看有没有等于 Person.prototype。
如果找到,就是 true;找不到直到 null,就是 false。
例如:
[] instanceof Array // true
[] instanceof Object // true为什么都是真?
因为数组的原型链上既有 Array.prototype,也最终会连到 Object.prototype。
9. 属性查找规则
当你访问:
p.name
p.say()查找顺序是:
- 先找对象自身属性
- 没有就找原型对象
- 再没有继续沿原型链向上
- 直到 null
- 还没有就是 undefined
这叫:就近原则
如果对象自身和原型上都有同名属性,优先用对象自己的。
例如:
function Person() {}
Person.prototype.name = 'proto'
const p = new Person()
p.name = 'self'
console.log(p.name) // self10. 为什么函数也是对象
因为在 JS 里,函数本身也是一种特殊对象。
所以函数既可以:
像对象一样有属性 又可以被调用执行 例如:
function fn() {}
fn.a = 1
console.log(fn.a) // 1同时函数还有 prototype,这是它作为构造函数时提供给实例用的。
所以你要记住:
函数是可调用对象。
11. Object.create() 是什么
这个也常考。
const obj = Object.create(proto)它会创建一个新对象,并让这个对象的原型指向 proto。
你可以理解为:
Object.create(proto)
等价于“创建一个空对象,并把它的 proto 指向 proto”这在手写继承、创建原型关联对象时很常用。
12. hasOwnProperty 和 in 的区别
hasOwnProperty 只看对象自身有没有这个属性
in 会连原型链一起查
例如:
function Person() {}
Person.prototype.say = function () {}
const p = new Person()
p.name = 'Tom'
console.log(p.hasOwnProperty('name')) // true
console.log(p.hasOwnProperty('say')) // false
console.log('name' in p) // true
console.log('say' in p) // true这个面试很常问。
13. class 和原型链的关系
ES6 的 class 看起来像传统面向对象,但本质上还是原型链。
class Person {
constructor(name) {
this.name = name
}
say() {
console.log(this.name)
}
}本质上和下面很接近:
function Person(name) {
this.name = name
}
Person.prototype.say = function () {
console.log(this.name)
}所以你面试里可以说:
class 是基于原型链的语法糖,本质并没有脱离 JavaScript 原有的原型继承模型。
14. 面试高频题:prototype 和 proto 的区别
标准回答prototype 是函数对象上的属性,主要用于给实例提供共享属性和方法; __proto__ 是对象上的隐式原型引用,用来指向该对象的原型对象。 实例对象的 __proto__ 通常指向构造函数的 prototype。
这题你一定要能顺嘴说出来。
15. 面试高频题:原型链是什么
标准回答原型链是对象查找属性时的链式查找机制。访问一个对象属性时,会先查找对象自身,找不到就沿着它的原型对象逐级向上查找,直到 null 为止。这条由原型对象连接起来的查找路径就叫原型链。
16. 面试高频题:new 做了什么
标准回答new 的执行过程可以概括为四步:先创建一个新对象;再把新对象的隐式原型指向构造函数的 prototype;然后以这个新对象作为 this 执行构造函数;最后如果构造函数没有显式返回对象,就返回这个新对象。
17. 面试高频题:instanceof 底层怎么判断
标准回答instanceof 的本质是检查构造函数的 prototype 是否出现在某个对象的原型链上。它会沿着对象的 __proto__ 不断向上查找,直到找到目标 prototype 或查找到 null 为止。
18. 这一章你必须背熟的 8 句话
- prototype 是函数上的属性,用来给实例提供共享成员。
- proto 是对象上的隐式原型引用。
- 实例对象的 proto 通常指向构造函数的 prototype。
- 属性查找会先找对象自身,再沿原型链向上查找。
- 原型链的终点通常是 null。
- new 会创建对象、链接原型、绑定 this、返回实例。
- instanceof 本质上是检查 prototype 是否出现在对象的原型链上。
- class 本质上是基于原型链的语法糖。
19. 给你几个立即判断题
题 1
function Person() {}
const p = new Person()
console.log(p.__proto__ === Person.prototype)答案:true
题 2
function Person() {}
console.log(Person.prototype.constructor === Person)答案:true
题 3
const arr = []
console.log(arr instanceof Array)
console.log(arr instanceof Object)答案: true true
因为数组原型链最终也会到 Object.prototype。
题 4
function Person() {}
Person.prototype.say = function () {
console.log('hi')
}
const p = new Person()
console.log(p.hasOwnProperty('say'))
console.log('say' in p)答案: falsetrue
20. 这章你先自己回答这 6 题
你按自己的话答,我再帮你逐题纠偏:
prototype 和 proto 有什么区别?
prototype 是函数对象上的属性,用来给该构造函数创建出来的实例提供共享属性和方法;__proto__ 是对象身上的隐式原型引用,用来指向它的原型对象。通常实例对象的 __proto__ 会指向构造函数的 prototype。为什么实例对象可以访问构造函数原型上的方法?
因为实例对象的 __proto__ 通常会指向构造函数的 prototype,所以当访问某个属性或方法时,如果实例自身没有,就会沿着 __proto__ 指向的原型对象继续查找,因此可以访问到原型上的共享方法。什么是原型链?
原型链是对象查找属性时的一种链式查找机制。访问对象属性时,会先查找对象自身;如果没有,就沿着对象的 __proto__ 指向的原型对象逐级向上查找,直到 null 为止。这条链式查找路径就叫原型链。new 到底做了什么?
new 的执行过程可以概括为四步:先创建一个新对象;再把新对象的 __proto__ 指向构造函数的 prototype;然后以这个新对象作为 this 执行构造函数;最后如果构造函数显式返回一个对象,就返回该对象,否则返回新创建的实例对象。instanceof 的底层判断逻辑是什么?
instanceof 的本质是检查构造函数的 prototype 是否出现在某个对象的原型链上。它会沿着对象的 __proto__ 不断向上查找,如果找到该 prototype 就返回 true,直到查找到 null 还没找到就返回 false。为什么说 class 本质上还是原型链?
因为 ES6 的 class 只是写法上更接近传统面向对象,但本质上实例仍然是通过原型对象来共享方法,继承关系也仍然是通过原型链建立的,所以 class 并没有改变 JavaScript 基于原型的继承模型,它本质上是语法糖。
