小白的JS学习之路(五)—— “原型”

小白的JS学习之路(五)------ 原型

学习笔记整理,从 prototype 到 proto 再到原型链,一次性搞懂 JavaScript 的原型机制。

前言

学完了闭包,接下来是 JavaScript 的另一个"大BOSS"------原型

原型这个东西,很多人刚接触时一头雾水:prototype__proto__constructor、原型链......这些概念之间到底是什么关系?

别急,我们一个一个来,从最基础的开始。


一、prototype(显式原型)

1.1 什么是 prototype?

prototype 是函数天生就拥有的一项属性。

注意我说的是函数 ,不是普通对象。只有函数才有 prototype 属性。

javascript 复制代码
function Car() {}
console.log(Car.prototype)  // { constructor: f Car() }
console.log(typeof Car.prototype)  // "object"

const obj = {}
console.log(obj.prototype)  // undefined ------ 普通对象没有 prototype

1.2 prototype 有什么用?

我们可以把公共的属性和方法 挂载到 prototype 上。之后通过这个函数 new 出来的所有实例,都能访问到这些属性和方法。

javascript 复制代码
function Car(color) {
  this.color = color  // 每个实例独有的属性 → 放在构造函数里
}

// 公共属性和方法 → 放在原型上
Car.prototype.name = 'SU7'
Car.prototype.run = function() {
  console.log(`${this.name} 正在跑,颜色是 ${this.color}`)
}

const car1 = new Car('蓝色')
const car2 = new Car('白色')

car1.run()  // SU7 正在跑,颜色是 蓝色
car2.run()  // SU7 正在跑,颜色是 白色

为什么要这样做?

如果把 run 方法写在构造函数里,每 new 一个实例,就会在内存中创建一个新的函数对象。100 个实例 = 100 个 run 方法,浪费内存。

放到 prototype 上,所有实例共享同一个 run 方法,只占一份内存。这就是原型最大的意义------减少重复,节省性能开销

1.3 实例能修改原型上的属性吗?

不能直接修改,但可以"遮蔽"(shadow)。

javascript 复制代码
function Car(color) {
  this.color = color
}
Car.prototype.name = 'SU7'

const car = new Car('蓝色')
console.log(car.name)  // "SU7" ------ 从原型上找到的

// 尝试"修改"
car.name = '蔚来'
console.log(car.name)  // "蔚来" ------ 看起来改了?

// 但原型上的值没变!
console.log(Car.prototype.name)  // "SU7" ------ 没变

car.name = '蔚来' 时,并没有修改原型上的 name,而是car 自己添加了一个新的 name 属性,遮蔽了原型上的同名属性。

总结:实例对象无法修改原型上的属性值,它只能给自身添加同名属性来"遮蔽"原型属性。


二、proto(隐式原型)

2.1 什么是 proto

每一个对象 都拥有一个 __proto__ 属性(注意:是对象,不是函数独有的)。

javascript 复制代码
function Car() {}
const car = new Car()

console.log(car.__proto__)       // { constructor: f Car(), ... }
console.log(typeof car.__proto__) // "object"

2.2 实例的隐式原型 === 构造函数的显式原型

这是一条铁律

javascript 复制代码
console.log(car.__proto__ === Car.prototype)  // true

new Car() 创建实例时,V8 会自动把实例的 __proto__ 指向构造函数的 prototype

2.3 属性查找规则

当 V8 访问对象的某个属性时:

  1. 先找对象自身的属性(显式拥有的属性)
  2. 如果没有,再去 __proto__ 上找(隐式拥有的属性)
javascript 复制代码
function Car(color) {
  this.color = color       // 显式属性
}
Car.prototype.name = 'SU7'  // 隐式属性(来自原型)

const car = new Car('蓝色')

car.hasOwnProperty('color')   // true  ------ car 自己的属性
car.hasOwnProperty('name')    // false ------ 不是 car 自己的
console.log(car.name)         // "SU7" ------ 从原型上找到的
  • 显式属性 :来自构造函数中通过 this.xxx = ... 定义的属性,实例自己拥有
  • 隐式属性 :来自构造函数的 prototype 上定义的属性,实例通过 __proto__ 访问

2.4 constructor(构造器)

每个原型对象上都有一个 constructor 属性,它指向创建该实例的构造函数:

javascript 复制代码
console.log(Car.prototype.constructor === Car)  // true
console.log(car.__proto__.constructor === Car)   // true
console.log(car.constructor === Car)              // true

constructor 就像一个"名片",记录了这个实例是谁创建的。


三、new 操作到底做了什么?

理解原型,必须搞清楚 new 的执行过程:

javascript 复制代码
const car = new Car('蓝色')

V8 在后台做了三件事:

javascript 复制代码
// 第一步:创建一个空对象
const this = new Object()

// 第二步:执行构造函数,绑定 this
this.constructor = Car.prototype
this.color = '蓝色'          // 构造函数中 this.color = color 执行

// 第三步:将实例的隐式原型指向构造函数的显式原型
this.__proto__ = Car.prototype

用一句话概括:

new 创建一个新对象 → 执行构造函数赋值 → 把新对象的 __proto__ 指向构造函数的 prototype

这就是为什么实例能访问原型上的属性------因为在第三步,V8 已经帮你把"通道"打通了。


四、原型链

4.1 什么是原型链?

当 V8 访问对象的某个属性时,查找顺序是:

  1. 对象自身
  2. 对象的 __proto__(构造函数的 prototype
  3. __proto__.__proto__(上一层的 prototype
  4. 一路往上找,直到找到 null 为止

这条从实例出发,顺着 __proto__ 逐级向上查找的链路,就叫原型链。

4.2 原型链的终点

javascript 复制代码
function Car() {}
const car = new Car()

car.__proto__             // Car.prototype
car.__proto__.__proto__   // Object.prototype
car.__proto__.__proto__.__proto__  // null ------ 到头了!

原型链的最终指向是 Object.prototype,再往上就是 null

用一个例子来验证:

javascript 复制代码
function Car(color) {
  this.color = color
}
Car.prototype.name = 'SU7'

const car = new Car('蓝色')

// car 自身有 color
console.log(car.color)       // "蓝色" ------ 在自身找到

// car 自身没有 name,去 Car.prototype 上找
console.log(car.name)        // "SU7" ------ 在第一层原型找到

// car 自身没有 toString,Car.prototype 上也没有
// 继续去 Object.prototype 上找 → 找到了!
console.log(car.toString())  // "[object Object]" ------ 在第二层原型找到

// Object.prototype 上没有 foo,再往上 → null,到头了
console.log(car.foo)         // undefined ------ 整条原型链都没找到

4.3 原型链示意图

javascript 复制代码
car(实例对象)
 │
 │  __proto__
 ▼
Car.prototype(构造函数的显式原型)
 │  ├── name: "SU7"
 │  ├── run: function()
 │  └── constructor: Car
 │
 │  __proto__
 ▼
Object.prototype(所有原型链的顶层)
 │  ├── toString: function()
 │  ├── hasOwnProperty: function()
 │  ├── constructor: Object
 │  └── ...
 │
 │  __proto__
 ▼
null(原型链的终点)

五、一个容易踩的坑

5.1 两个对象一定不相等

javascript 复制代码
const a = { name: '小明' }
const b = { name: '小明' }

console.log(a === b)  // false

虽然 ab 的结构完全一样,但它们是两个不同的内存地址

  • 基本类型 判断相等:只比1 === 1true
  • 引用类型 判断相等:比值 + 内存地址 (两个对象永远 !==,因为地址不同)

5.2 修改原型的正确方式

javascript 复制代码
function Car() {}

// ❌ 错误:实例没有 prototype 属性
// car.prototype = ...  // 这是给实例加了一个普通属性,不是修改原型

// ✅ 正确:通过构造函数的显式原型来修改
Car.prototype.name = 'SU7'

记住:只有函数才有 prototype(显式原型),普通对象只有 __proto__(隐式原型)。要修改原型,必须通过构造函数的 prototype 来操作。


六、总结

用一个表格把核心知识点整理清楚:

概念 拥有者 说明
prototype(显式原型) 只有函数 存放公共属性和方法,供所有实例共享
proto(隐式原型) 所有对象 指向创建该对象的构造函数的 prototype
constructor 原型对象 指向创建该实例的构造函数
原型链 对象的查找路径 自身 → __proto____proto__.__proto__ → ... → null
new 操作 创建实例 创建对象 → 执行构造函数 → 绑定 __proto__

核心等式:

ini 复制代码
实例.__proto__ === 构造函数.prototype
原型链终点:Object.prototype.__proto__ === null

一句话总结原型:

原型就是"共享属性和方法"的机制。实例通过 __proto__ 指向构造函数的 prototype,找不到属性时顺着原型链往上找,直到 null


写在最后

原型是 JavaScript 面向对象编程的基石,也是面试中的高频考点。理解原型的关键在于搞清 prototype__proto__constructor 三者的关系 ,以及属性查找的原型链机制

如果这篇文章对你有帮助,欢迎点赞收藏,我们下期见!👋


🔗 系列文章:

相关推荐
MageGojo10 小时前
JavaScript 调用 QQ 信息接口:头像直链和 QQ 空间链接展示
开发语言·javascript·ecmascript·qq 接口
胡萝卜术10 小时前
JavaScript 继承的本质之辩:从 Crockford 到 Kyle Simpson,我们真的需要 Class 吗?
javascript·程序员
huangdong_10 小时前
淘宝商品数据采集:浏览器方案的完整技术实现
服务器·前端·javascript
蓝莓味的口香糖10 小时前
带图标的Loading组件封装
开发语言·前端·javascript
夜雪闻竹10 小时前
Electron 入门:Web 应用打包成桌面软件
前端·javascript·electron
Darling噜啦啦11 小时前
JavaScript 数组去重的 6 种实现方式:从 O(n²) 到 O(n) 的进化之路
javascript·面试
幸运小圣11 小时前
SheetJS(xlsx)导出 Excel 全流程(新手版)【SheetJS】
javascript·excel
怕浪猫11 小时前
# Electron 开发实战(三):基础UI开发与布局全解
前端·javascript·electron
大可-11 小时前
CSDN博客-星火知识库教程
前端·javascript·vue.js·elementui·html