【JS进阶-Day3】原型与原型链
📺 对应视频:P177-P187 | 🎯 核心目标:彻底理解 prototype、proto、constructor、原型继承与原型链
一、原型对象(prototype)
1.1 每个函数都有 prototype
function Person(name) {
this.name = name
}
// 每个函数都自带 prototype 属性,指向一个对象
console.log(typeof Person.prototype) // 'object'
console.log(Person.prototype)
// { constructor: [Function: Person] }
1.2 把方法挂到原型上
// ✅ 正确做法:方法挂 prototype,所有实例共享一份
Person.prototype.greet = function() {
console.log(`我叫${this.name}`)
}
Person.prototype.species = '智人' // 也可以共享数据
const p1 = new Person('张三')
const p2 = new Person('李四')
// 两个实例共享同一个 greet 函数
p1.greet === p2.greet // true(同一个函数引用,节省内存!)
p1.greet() // 我叫张三
p2.greet() // 我叫李四(this 不同)
二、实例的 proto
2.1 proto 是什么?
每个对象实例都有一个 __proto__ 属性(隐式原型),指向其构造函数的 prototype。
const p = new Person('张三')
p.__proto__ === Person.prototype // true!
// 这就是为什么实例能访问原型上的方法
💡
__proto__是非标准属性(浏览器实现),标准方式是:
Object.getPrototypeOf(p)获取原型Object.setPrototypeOf(p, proto)设置原型
2.2 属性查找规则
const p = new Person('张三')
// 访问属性时,先找实例自身,找不到就沿 __proto__ 向上找
p.name // '张三'(实例自身的属性)
p.greet // function(在 Person.prototype 上找到)
p.toString // function(在 Object.prototype 上找到)
p.noExist // undefined(原型链终点 null,找不到)
三、constructor 属性
// prototype 对象上默认有一个 constructor 属性,指回构造函数本身
Person.prototype.constructor === Person // true
const p = new Person('张三')
p.constructor === Person // true(沿 __proto__ 找到 Person.prototype.constructor)
// 重要:批量添加原型方法时,注意维护 constructor
// ❌ 问题写法(constructor 丢失)
Person.prototype = {
greet() { ... },
eat() { ... }
}
// 此时 Person.prototype.constructor === Object(不再是 Person!)
// ✅ 正确写法
Person.prototype = {
constructor: Person, // 手动补回 constructor
greet() { ... },
eat() { ... }
}
四、原型链
4.1 原型链是什么?
对象通过 __proto__ 连成的链条,就是原型链。
实例 p
↓ __proto__
Person.prototype
↓ __proto__
Object.prototype
↓ __proto__
null(链条终点)
4.2 原型链图示
function Person(name) { this.name = name }
const p = new Person('张三')
// 完整的原型链
p.__proto__ === Person.prototype // true
Person.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // true(终点)
// 函数本身也有原型链(函数是对象)
Person.__proto__ === Function.prototype // true
Function.prototype.__proto__ === Object.prototype // true
4.3 Object.prototype 上的方法
// 所有对象都能用这些方法(来自原型链顶端)
const obj = { name: '张三' }
obj.hasOwnProperty('name') // true(自身属性)
obj.hasOwnProperty('greet') // false(原型上的不算)
obj.toString() // '[object Object]'
obj.valueOf() // 对象本身
obj.isPrototypeOf(p) // 判断是否在某对象的原型链上
五、原型继承
5.1 ES5 继承(原型链继承)
function Animal(name) {
this.name = name
}
Animal.prototype.speak = function() {
console.log(`${this.name} 发出声音`)
}
function Dog(name, breed) {
Animal.call(this, name) // 继承实例属性(借用构造函数)
this.breed = breed
}
// 继承原型方法
Dog.prototype = Object.create(Animal.prototype) // 关键!
Dog.prototype.constructor = Dog // 修复 constructor
// 添加子类自己的方法
Dog.prototype.bark = function() {
console.log('汪汪!')
}
const d = new Dog('旺财', '柴犬')
d.speak() // 旺财 发出声音(继承自 Animal)
d.bark() // 汪汪!(Dog 自己的)
d instanceof Dog // true
d instanceof Animal // true
5.2 ES6 Class 继承(推荐)
class Animal {
constructor(name) {
this.name = name
}
speak() {
console.log(`${this.name} 发出声音`)
}
}
class Dog extends Animal { // extends 继承
constructor(name, breed) {
super(name) // 必须先调用 super()(相当于 Animal.call(this, name))
this.breed = breed
}
bark() {
console.log('汪汪!')
}
// 重写父类方法
speak() {
super.speak() // 调用父类的 speak
console.log('(狗狗)')
}
}
const d = new Dog('旺财', '柴犬')
d.bark() // 汪汪!
d.speak() // 旺财 发出声音 \n (狗狗)
💡 Class 的本质:Class 是语法糖,底层仍然是构造函数 + 原型链,只是写法更优雅。
六、Class 详解
class Person {
// 静态属性(类本身的属性,实例访问不到)
static species = '智人'
static count = 0
// 构造函数
constructor(name, age) {
this.name = name // 实例属性
this.age = age
Person.count++
}
// 实例方法(挂在 prototype 上)
greet() {
console.log(`我叫${this.name}`)
}
// 静态方法(类本身的方法)
static create(name, age) {
return new Person(name, age)
}
// Getter / Setter
get info() {
return `${this.name}(${this.age}岁)`
}
set info(val) {
[this.name, this.age] = val.split(',')
}
// 私有属性(ES2022,# 前缀)
#secret = '秘密'
getSecret() { return this.#secret }
}
// 使用
const p = Person.create('张三', 18)
p.greet() // 我叫张三
Person.species // '智人'(静态)
p.info // '张三(18岁)'(getter)
p.info = '李四,20' // setter
Person.count // 创建了几个实例
七、内置对象的原型扩展
// 可以给内置对象的原型添加自定义方法(不推荐,会污染全局)
Array.prototype.sum = function() {
return this.reduce((a, b) => a + b, 0)
}
[1, 2, 3].sum() // 6
// String 原型扩展
String.prototype.reverse = function() {
return this.split('').reverse().join('')
}
'hello'.reverse() // 'olleh'
// ⚠️ 实际开发中不要随意扩展内置原型,容易与其他库冲突
// 更好的方式:写工具函数
const arrUtils = {
sum: arr => arr.reduce((a, b) => a + b, 0)
}
八、知识图谱
原型与原型链
├── prototype(函数的原型对象)
│ ├── 方法挂原型 → 实例共享
│ └── constructor 属性 → 指回构造函数
├── __proto__(实例的隐式原型)
│ └── 指向其构造函数的 prototype
├── 原型链
│ └── 实例 → 构造函数.prototype → Object.prototype → null
├── 属性查找:实例自身 → 原型链向上 → undefined
├── 继承
│ ├── ES5:Object.create + 借用构造函数
│ └── ES6:class + extends + super(推荐)
└── Class 语法
├── constructor / 实例方法 / 静态方法
├── static 属性/方法
├── getter / setter
└── # 私有属性(ES2022)
九、高频面试题
Q1:prototype 和 proto 的区别?
prototype是函数特有的属性,指向该函数的原型对象;__proto__是所有对象(包括函数)特有的属性,指向创建它的构造函数的 prototype。关系:p.__proto__ === Person.prototype。
Q2:如何实现 JS 继承?
ES5:通过
Object.create设置原型链 + 借用构造函数;ES6:使用class+extends+super(推荐)。Class 本质上是语法糖,底层仍是原型链继承。
Q3:什么是原型链?有什么作用?
原型链是对象通过
__proto__连成的链条,终点是null。作用:实现属性和方法的继承查找——访问对象属性时,先找自身,再沿原型链向上找,直到null。这是 JS 实现"继承"的核心机制。
Q4:for...in 会遍历原型链上的属性吗?
function Person(name) { this.name = name }
Person.prototype.greet = function() {}
const p = new Person('张三')
for (let key in p) {
console.log(key) // 'name' 和 'greet' 都会出现!
}
// 用 hasOwnProperty 过滤
for (let key in p) {
if (p.hasOwnProperty(key)) {
console.log(key) // 只有 'name'
}
}
// 更好:用 Object.keys()(只返回自身可枚举属性)
⬅️ 上一篇:JS进阶Day2 - 构造函数 ➡️ 下一篇:JS进阶Day4 - 深浅拷贝与防抖节流