JavaScript 显示原型与隐式原型完全指南
前言:从构造函数说起
让我们先看一段揭示原型本质的代码:
function Person(name) {
this.name = name
}
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`)
}
const john = new Person('John')
john.sayHello() // 输出:Hello, I'm John
这段代码展示了 JavaScript 中一个核心机制:原型继承。理解显示原型(prototype)和隐式原型(__proto__
)是掌握 JavaScript 面向对象编程的关键。
正文
1. 显示原型(prototype)详解
1.1 基本定义
- 显示原型是函数对象特有的属性(
prototype
) - 它是一个对象,包含应该被继承的属性和方法
- 只有函数才有
prototype
属性(箭头函数除外)
function Foo() {}
console.log(Foo.prototype) // 输出:{ constructor: Foo }
const arrowFn = () => {}
console.log(arrowFn.prototype) // 输出:undefined
1.2 核心作用
- 作为新实例对象的原型模板
- 实现属性和方法的共享
- 构成原型链的基础
1.3 原型操作(增删改查)
function Car(brand) {
this.brand = brand
}
// 增加原型属性
Car.prototype.color = 'red'
// 修改原型属性
Car.prototype.color = 'blue'
// 查询原型属性
console.log(Car.prototype.color) // 'blue'
// 删除原型属性
delete Car.prototype.color
重要特性:
- 实例对象不能直接修改或删除原型上的属性
- 实例对象的属性访问会触发原型链查找
const myCar = new Car('BMW')
myCar.color = 'black' // 这是在实例上添加属性,不是修改原型
console.log(myCar.color) // 'black'
console.log(Car.prototype.color) // undefined(之前已删除)
2. 隐式原型(__proto__
)详解
2.1 基本定义
- 每个对象都有
__proto__
属性(现推荐使用Object.getPrototypeOf()
) - 指向创建该对象的构造函数的
prototype
- 是 JavaScript 内部原型链查找的实际通道
const arr = []
console.log(arr.__proto__ === Array.prototype) // true
console.log(Object.getPrototypeOf(arr) === Array.prototype) // true
2.2 原型链形成原理
function Animal() {}
Animal.prototype.eat = function() { console.log('Eating') }
function Dog() {}
Dog.prototype = new Animal() // 原型继承
const myDog = new Dog()
myDog.eat() // 查找路径:myDog → Dog.prototype → Animal.prototype
2.3 原型链图示
myDog → Dog.prototype → Animal.prototype → Object.prototype → null
3. 显示原型 vs 隐式原型
特性 | 显示原型 (prototype) | 隐式原型 (__proto__ ) |
---|---|---|
所有者 | 函数对象 | 所有对象 |
访问方式 | Func.prototype | obj.__proto__ 或 Object.getPrototypeOf(obj) |
作用 | 作为构造函数创建实例时的原型 | 用于原型链查找 |
关系 | instance.__proto__ === Constructor.prototype | 指向创建该对象的构造函数的 prototype |
4. 特殊原型情况分析
4.1 Object.prototype 的原型
console.log(Object.prototype.__proto__) // null
4.2 函数对象的双重原型
function Foo() {}
// 函数作为对象时的原型
console.log(Foo.__proto__ === Function.prototype) // true
// 函数作为构造函数时的原型
console.log(Foo.prototype.__proto__ === Object.prototype) // true
4.3 基本类型的包装对象
const str = 'hello'
console.log(str.__proto__ === String.prototype) // true
console.log(String.prototype.__proto__ === Object.prototype) // true
5. 面试常见问题与回答技巧
问题1:什么是原型链?
回答要点:
- 解释原型链是 JavaScript 实现继承的机制
- 描述对象属性查找的向上追溯过程
- 举例说明原型链的实际应用
示例回答:
"原型链是 JavaScript 中对象之间通过 __proto__
连接形成的链式结构。当访问对象属性时,如果对象本身没有该属性,就会沿着原型链向上查找,直到找到属性或到达 null。比如数组实例可以调用 Array.prototype
上的方法,就是因为原型链的存在。"
问题2:new 操作符做了什么?
回答要点:
- 创建新对象
- 设置原型链接
- 绑定 this 并执行构造函数
- 返回对象
标准答案:
function myNew(Constructor, ...args) {
// 1. 创建新对象,并链接到构造函数的原型
const obj = Object.create(Constructor.prototype)
// 2. 执行构造函数,绑定this
const result = Constructor.apply(obj, args)
// 3. 如果构造函数返回对象则使用该对象,否则返回新对象
return result instanceof Object ? result : obj
}
问题3:如何实现继承?
回答要点:
- 原型链继承的优缺点
- 组合继承的实现
- ES6 class 语法糖
示例代码:
// 组合继承
function Parent(name) {
this.name = name
}
Parent.prototype.sayName = function() {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name) // 继承实例属性
this.age = age
}
Child.prototype = Object.create(Parent.prototype) // 继承原型方法
Child.prototype.constructor = Child
问题4:Object.create(null)
和 {}
的区别?
回答要点:
- 前者创建没有原型的纯净对象
- 后者继承自 Object.prototype
- 使用场景分析
6. 高级原型应用
6.1 原型污染与防御
// 不安全的扩展
Object.prototype.includes = function() { /* 恶意代码 */ }
// 防御方案
const safeObj = Object.create(null) // 无原型的对象
6.2 原型方法借用
const arrayLike = { 0: 'a', 1: 'b', length: 2 }
Array.prototype.forEach.call(arrayLike, item => {
console.log(item)
})
6.3 性能优化技巧
// 将常用方法放在原型上
function HeavyObject(data) {
this.data = data
}
HeavyObject.prototype.process = function() {
// 处理逻辑
}
// 避免在构造函数中定义方法(每个实例都会创建新函数)
结语
理解 JavaScript 的原型系统需要把握几个关键点:
- 函数才有 prototype:普通对象只有
__proto__
- 原型链终点是 null:
Object.prototype.__proto__ === null
- 实例与构造函数的关系:
obj.__proto__ === Constructor.prototype
- 属性查找机制:先自身后原型链
在实际开发中:
- 使用
Object.getPrototypeOf()
替代__proto__
- 谨慎扩展原生原型(可能引发冲突)
- 理解 ES6 class 只是原型继承的语法糖
- 合理使用原型实现方法共享
掌握这些知识后,你将能够:
- 深入理解 JavaScript 的继承机制
- 在面试中从容应对原型相关问题
- 编写更高效的面向对象代码
- 诊断原型链相关的 bug
记住这个简单的原型关系图:
实例对象 --__proto__--> 构造函数.prototype --__proto__--> Object.prototype --__proto__--> null
祝你在 JavaScript 的原型世界中探索愉快!🚀