基础知识
在 JavaScript 中,显式原型属性(prototype)和隐式原型属性(__proto__ 或 [[Prototype]])是原型链机制的两个核心概念,它们共同构成了 JavaScript 的继承体系。
以下是详细对比和解释:
1. 显式原型属性(prototype)
定义
- 是
函数对象独有的属性(普通对象没有) - 用于实现基于原型的继承
特点
| 特性 | 说明 |
|---|---|
| 归属对象 | 仅函数对象拥有(构造函数/类) |
| 作用时机 | 在函数作为构造函数使用时(通过 new 调用) |
| 实际用途 | 定义该构造函数创建的实例共享的属性和方法 |
| 修改影响 | 修改 prototype 会影响所有已创建和未来的实例 |
示例
function Person(name) {
this.name = name;
}
// 显式原型属性:添加共享方法 sayHello()
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
const alice = new Person('Alice');
alice.sayHello(); // ✅ 正常调用
2. 隐式原型属性(__proto__ 或 [[Prototype]])
定义
- 是所有对象都有的内部属性(包括函数对象)
- 指向该对象的原型(即其构造函数的
prototype)
特点
| 特性 | 说明 |
|---|---|
| 归属对象 | 所有对象(包括函数对象) |
| 访问方式 | 通过 obj.__proto__(非标准)或 Object.getPrototypeOf(obj)(推荐) |
| 形成原型链 | 对象通过 __proto__ 链式查找属性和方法 |
| 修改影响 | 修改 __proto__ 会改变对象的原型链 |
示例
const animal = { eats: true };
const rabbit = { jumps: true };
// 设置隐式原型(不推荐直接操作 __proto__)
rabbit.__proto__ = animal;
console.log(rabbit.eats); // ✅ true(通过原型链访问)
console.log(Object.getPrototypeOf(rabbit) === animal); // ✅ true
3. 关键区别对比
| 特性 | prototype |
__proto__ |
|---|---|---|
| 归属对象 | 仅函数对象 | 所有对象 |
| 作用阶段 | 构造函数定义阶段 | 对象实例化及访问阶段 |
| 标准性 | 标准属性 | __proto__ 是非标准实现 |
| 推荐访问方式 | Func.prototype |
Object.getPrototypeOf(obj) |
| 修改影响范围 | 影响所有实例 | 仅影响当前对象 |
总结
prototype:是函数的属性,用于定义通过该构造函数创建的实例共享的成员__proto__:是对象的内部属性,指向其原型对象(构成原型链)- 关系:
实例.__proto__ === 构造函数.prototype - 现代实践:优先使用
class和Object.create()/Object.getPrototypeOf()
Vue 实例对象 不是来源于 Vue构造函数 ? 为什么 Vue实例对象 没有 prototype ?
您的问题触及了 JavaScript 原型链的核心机制。Vue 实例确实来源于 Vue 构造函数,但实例对象没有 prototype 属性是 JavaScript 原型继承体系的刻意设计。以下是逐层解析:
1. 原型链的基本规则 ★★★
JavaScript 中所有对象都遵循以下铁律:
- 只有函数对象(构造函数)才拥有
prototype属性 - 普通对象(包括实例)只有
__proto__,用于指向其原型
function Vue() {} // 构造函数
const vm = new Vue(); // 实例对象
console.log(Vue.prototype); // ✅ 存在(函数对象特有)
console.log(vm.prototype); // ❌ undefined(普通对象无prototype)
console.log(vm.__proto__ === Vue.prototype); // ✅ true(实例通过__proto__连接构造函数原型)
2. 设计原理图解
- 构造函数:通过
prototype定义蓝图 - 实例对象:通过
__proto__引用这个蓝图,自身不需要重复存储
-
3. 为什么实例不需要 prototype?
(1) 内存效率
如果每个实例都自带 prototype:
// 假设实例也有prototype(实际不会发生)
const vm1 = new Vue();
const vm2 = new Vue();
vm1.prototype.methodA = function() {};
vm2.prototype.methodB = function() {};
// 导致相同方法被重复创建,浪费内存
实际原型链共享:
Vue.prototype.methodA = function() {}; // 所有实例共享同一方法
(2) 职责分离
| 对象类型 | 核心职责 | 所需属性 |
|---|---|---|
| 构造函数 | 定义实例的共性 | prototype |
| 实例对象 | 存储个性化数据 | __proto__ |
4. 从底层实现看本质
Vue 构造函数初始化时的关键步骤:
function Vue(options) {
this._init(options); // 初始化实例数据
}
// 所有实例共享的方法挂在原型上
Vue.prototype._init = function() { /*...*/ };
Vue.prototype.$mount = function() { /*...*/ };
const vm = new Vue();
此时内存结构:
vm实例 {
_data: {...}, // 实例自有数据
__proto__: Vue.prototype // 继承共享方法
}5. 类比现实世界
- 构造函数 ≈ 模具
- 拥有
prototype(模具的图纸)
- 拥有
- 实例 ≈ 产品
- 通过
__proto__引用模具图纸 - 自身不需要携带图纸副本
- 通过
6. 特殊场景验证
(1) 函数实例的例外
极少数情况下,当实例本身也是函数时会有 prototype,但这非常规用法:
const funcInstance = new Function();
console.log(funcInstance.prototype); // ✅ 存在(因为funcInstance是函数对象)
(2) class 语法糖
ES6 class 本质仍是原型继承:
class MyVue extends Vue {}
const myVm = new MyVue();
console.log(MyVue.prototype); // ✅ 构造函数有prototype
console.log(myVm.prototype); // ❌ 实例依然没有
7. 总结回答
Vue 实例确实来源于 Vue 构造函数,但:
- 构造函数需要
prototype来定义所有实例的共享能力 - 实例对象只需通过
__proto__引用这些共享能力,无需自己存储 - 这是 JavaScript 节省内存+明确职责的经典设计,与 Vue 无关,是语言本身的特性
这种设计确保了:
- 方法只存储一次(在构造函数原型上)
- 实例保持轻量(仅存储数据)
- 继承关系清晰可追溯
