基础知识

在 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
  • 现代实践:优先使用 classObject.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. 设计原理图解

拥有包含通过__proto__指向没有Vue构造函数prototype$mount等共享方法vm实例
  • 构造函数:通过 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 构造函数,但:

  1. 构造函数需要 prototype 来定义所有实例的共享能力
  2. 实例对象只需通过 __proto__ 引用这些共享能力,无需自己存储
  3. 这是 JavaScript 节省内存+明确职责的经典设计,与 Vue 无关,是语言本身的特性

这种设计确保了:

  • 方法只存储一次(在构造函数原型上)
  • 实例保持轻量(仅存储数据)
  • 继承关系清晰可追溯