【JS进阶-Day3】原型与原型链

3 阅读3分钟

【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
├── 继承
│   ├── ES5Object.create + 借用构造函数
│   └── ES6class + 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 - 深浅拷贝与防抖节流