news 2026/3/12 15:38:11

区分__proto__和prototype

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
区分__proto__和prototype

彻底区分__proto__与prototype:从JS底层到Vue实战

要彻底理解__proto__prototype,需穿透 JavaScript 原型链的底层逻辑,再结合 Vue 框架的实例体系落地应用。本文从「归属、作用、关联、误区、实战」五个核心维度拆解,补充大量案例、调试技巧和 Vue 实战场景,帮你吃透这两个高频易混概念。

一、核心定义与归属:先明确“谁拥有谁”

prototype__proto__是原型链的两大核心载体,但二者的归属、本质和目的截然不同,是理解原型链的第一道门槛。

概念归属对象本质/类型存在的目的典型示例
prototype只有函数(构造函数)普通对象(原型对象)存放“供所有实例继承的公共属性/方法”,是构造函数的“原型仓库”Function.prototypeArray.prototypeVue.prototype
__proto__所有对象(包括函数)原型指针(内置访问器属性)连接实例与原型对象,是实例的“原型导航”,用于原型链查找(ES6 标准化,等价于Object.getPrototypeOf(){}.__proto__[].__proto__vm.__proto__

关键结论(补充强化):

  1. 普通对象/数组/实例无prototype
    比如const obj = {}; console.log(obj.prototype)undefinedconst arr = []; console.log(arr.prototype)undefined;只有function F() {}这样的函数才有F.prototype
  2. 函数“身兼两职”
    函数既是“可执行的代码块”,也是“对象”——因此函数既有prototype(作为构造函数的仓库),也有__proto__(作为对象的导航指针,指向Function.prototype)。
    functionFoo(){}console.log(Foo.prototype);// { constructor: ƒ Foo(), __proto__: Object }(仓库)console.log(Foo.__proto__);// ƒ () { [native code] }(指向 Function.prototype)
  3. __proto__是访问器属性
    并非对象的直接属性,而是Object.prototype上的get/set方法,本质是调用Object.getPrototypeOf()/Object.setPrototypeOf();早期是浏览器私有属性(如__proto__曾写作__proto__),ES6 才标准化。

二、核心作用:“仓库” vs “导航”

1.prototype:构造函数的“共享原型仓库”

prototype是构造函数专属的“公共资源池”,所有通过该构造函数创建的实例,都能共享仓库中的属性/方法——这是 JavaScript 实现“继承”和“内存优化”的核心手段。

原生 JS 深度示例(补充内存优化说明):
// 构造函数:定义实例私有属性functionPerson(name,age){this.name=name;// 每个实例独有的私有属性this.age=age;}// 往 prototype 仓库添加公共方法(所有实例共享,仅占用一份内存)Person.prototype.sayHi=function(){console.log(`Hi, 我是${this.name}${this.age}`);};Person.prototype.gender='人类';// 公共属性// 创建多个实例constp1=newPerson('张三',20);constp2=newPerson('李四',22);// 所有实例共享 prototype 中的方法/属性console.log(p1.sayHi===p2.sayHi);// true(内存共享)console.log(p1.gender===p2.gender);// truep1.sayHi();// Hi, 我是张三,20岁p2.sayHi();// Hi, 我是李四,22岁
核心特点(补充细节):
  • 内存优化核心:如果每个实例都定义sayHi方法,1000 个实例会占用 1000 份内存;放在prototype中仅需 1 份,大幅节省内存。
  • constructor反向指向prototype自带constructor属性,默认指向所属构造函数,可用于判断实例的构造函数类型(但易被覆盖,需谨慎使用)。
    console.log(Person.prototype.constructor===Person);// trueconsole.log(p1.constructor===Person);// true(p1 无 constructor,通过 __proto__ 查找)// 注意:若手动覆盖 prototype,constructor 会丢失Person.prototype={sayHi:function(){}};console.log(p1.constructor===Person);// false(指向 Object)// 修复:手动重置 constructorPerson.prototype.constructor=Person;
  • 原型属性可动态修改:修改prototype后,所有未覆盖该属性的实例都会同步生效(原型链查找的动态性)。
    // 动态添加方法Person.prototype.run=function(){console.log(`${this.name}在跑步`);};p1.run();// 张三在跑步(无需重新创建实例,直接生效)

2.__proto__:实例的“原型导航指针”

__proto__是每个对象内置的“导航器”,指向创建该对象的构造函数的prototype。当实例访问属性/方法时,遵循“先自身、后原型”的规则:

  1. 先查找实例自身是否有该属性/方法(如p1.name);
  2. 若没有,通过__proto__向上查找构造函数的prototype(如p1.sayHi);
  3. 若仍没有,继续通过prototype.__proto__向上查找,直到Object.prototype
  4. 若最终未找到,返回undefined(方法则报错xxx is not a function)。
原生 JS 深度示例(补充遮蔽效应):
functionPerson(name){this.name=name;}Person.prototype.sayHi=function(){console.log('原型方法');};Person.prototype.age=18;constp1=newPerson('张三');// 1. 原型链查找:自身无 → __proto__ 找 prototypeconsole.log(p1.age);// 18(来自 Person.prototype)p1.sayHi();// 原型方法(来自 Person.prototype)// 2. 遮蔽效应:实例属性覆盖原型属性p1.age=20;// 给实例添加自身属性console.log(p1.age);// 20(自身属性优先)console.log(p1.__proto__.age);// 18(原型属性仍存在)// 3. 完整原型链查找路径// p1.__proto__ → Person.prototype → Person.prototype.__proto__ → Object.prototype → nullconsole.log(p1.toString());// [object Object](来自 Object.prototype)console.log(p1.__proto__.__proto__.toString===Object.prototype.toString);// true
核心特点(补充细节):
  • “只读”的本质:ES6 规范中__proto__是“只读”的(严格模式下修改会报错),虽然部分浏览器允许修改,但会破坏原型链的稳定性,且触发引擎优化失效(性能下降)。
  • 原型链的终点:所有原型链最终指向Object.prototype.__proto__,其值为null(无更高层原型)。
    console.log(Object.prototype.__proto__);// nullconsole.log(p1.__proto__.__proto__.__proto__);// null

三、关联关系:__proto__是实例与prototype的桥梁

原型链的本质,是__proto__把多个prototype串联起来的链式结构。我们用可视化文字链 + 完整验证代码拆解核心关联:

1. 通用原型链结构(补充函数/数组的特例)

// 普通实例的原型链 实例 p1 → __proto__ → Person.prototype → __proto__ → Object.prototype → __proto__ → null // 函数的原型链(函数既是构造函数也是对象) 函数 Person → __proto__ → Function.prototype → __proto__ → Object.prototype → __proto__ → null // 数组的原型链(数组是 Array 的实例) 数组 [1,2,3] → __proto__ → Array.prototype → __proto__ → Object.prototype → __proto__ → null

2. 完整验证代码(补充注释和解释)

functionPerson(){}constp1=newPerson();// 1. 实例的 __proto__ === 构造函数的 prototype(核心关联)console.log(p1.__proto__===Person.prototype);// true// 2. 构造函数(Person)作为对象,__proto__ 指向 Function.prototypeconsole.log(Person.__proto__===Function.prototype);// true// 补充:Function 自身的 __proto__ 指向自身(特例)console.log(Function.__proto__===Function.prototype);// true// 3. 构造函数的 prototype 是普通对象,__proto__ 指向 Object.prototypeconsole.log(Person.prototype.__proto__===Object.prototype);// true// 4. Object.prototype 是原型链顶端,__proto__ 为 nullconsole.log(Object.prototype.__proto__);// null// 5. 数组的原型链验证constarr=[1,2,3];console.log(arr.__proto__===Array.prototype);// trueconsole.log(Array.prototype.__proto__===Object.prototype);// true

3. 核心关联结论

  • __proto__是连接“实例”和“构造函数 prototype”的唯一桥梁;
  • 原型链查找的本质,是__proto__不断向上指向不同prototype的过程;
  • 所有对象最终都继承自Object.prototype(除了Object.create(null)创建的无原型对象)。

四、常见误区:彻底避坑(补充更多误区)

误区 1:“实例有 prototype 属性”

❌ 错误:const p1 = new Person(); console.log(p1.prototype);
✅ 正确:实例仅拥有__proto__prototype是构造函数的专属属性;p1.prototype始终返回undefined

误区 2:“proto和 prototype 是同一个东西”

❌ 错误:混淆“指针”和“仓库”,认为p1.__proto__ === Person.prototype就是“同一个”;
✅ 正确:p1.__proto__指向Person.prototype的指针,二者是“引用关系”而非“同一对象”;只有当手动修改p1.__proto__ = {}时,引用才会断开。

误区 3:“修改proto是常规操作”

❌ 错误:直接修改__proto__(如p1.__proto__ = { age: 20 });
✅ 正确:修改__proto__会:

  1. 破坏原型链,导致实例丢失原有原型方法(如p1.sayHi失效);
  2. 触发 JavaScript 引擎的“去优化”(JIT 优化失效,性能下降 10 倍以上);
  3. 若需修改原型,优先操作构造函数的prototype(如Person.prototype.age = 20)。

误区 4:“constructor 属性永远可靠”

❌ 错误:认为p1.constructor一定指向创建它的构造函数;
✅ 正确:constructorprototype上的普通属性,可被轻易覆盖:

Person.prototype={sayHi:function(){}};// 覆盖 prototype,丢失 constructorconstp1=newPerson();console.log(p1.constructor===Person);// false(指向 Object)

若需依赖constructor,需手动重置:Person.prototype.constructor = Person

误区 5:“原型链查找无性能损耗”

❌ 错误:认为原型链查找和直接访问实例属性效率一致;
✅ 正确:原型链层级越深,查找耗时越长(虽现代引擎优化后差距极小,但深层原型链仍需避免);建议:

  1. 高频访问的属性/方法,优先定义在实例自身;
  2. 原型链层级控制在 3 层以内。

误区 6:“Object.create(null) 的对象有proto

❌ 错误:认为所有对象都有__proto__
✅ 正确:Object.create(null)创建的“纯净对象”无原型,因此无__proto__

constpureObj=Object.create(null);console.log(pureObj.__proto__);// undefinedconsole.log(Object.getPrototypeOf(pureObj));// null

五、实战进阶:原型链的实用技巧(新增章节)

1. 手动实现继承(基于 prototype)

这是面试高频考点,也是理解prototype核心价值的实战场景:

// 父构造函数functionParent(name){this.name=name;}Parent.prototype.sayName=function(){console.log(this.name);};// 子构造函数functionChild(name,age){Parent.call(this,name);// 继承父类实例属性this.age=age;}// 核心:让子类 prototype 继承父类 prototypeChild.prototype=Object.create(Parent.prototype);Child.prototype.constructor=Child;// 重置 constructor// 子类专属方法Child.prototype.sayAge=function(){console.log(this.age);};// 测试constchild=newChild('小明',10);child.sayName();// 小明(继承父类方法)child.sayAge();// 10(子类专属方法)console.log(childinstanceofChild);// trueconsole.log(childinstanceofParent);// true

2. 正确判断实例类型(instanceof vsproto

  • instanceof:判断实例的原型链中是否包含构造函数的prototype(推荐);
  • __proto__:直接对比指针(易出错,不推荐)。
console.log(childinstanceofChild);// true(原型链包含 Child.prototype)console.log(child.__proto__===Child.prototype);// true(仅对比直接原型)// 注意:instanceof 能识别多层继承,__proto__ 仅识别直接原型console.log(childinstanceofParent);// trueconsole.log(child.__proto__===Parent.prototype);// false

六、在 Vue 中的实际体现(强化+补充场景)

Vue 的实例体系(vm/根实例、vc/组件实例)完全基于原型链设计,__proto__prototype是理解 Vue 实例能力继承的底层钥匙。

1. Vue 实例的核心方法继承(补充 Vue 2/3 对比)

Vue 的$emit$watch$mount等核心方法,本质是挂载在原型上的公共方法:

// Vue 2 源码简化逻辑functionVue(options){this._init(options);// 初始化实例}Vue.prototype.$emit=function(eventName,...args){// 触发自定义事件的核心逻辑constlisteners=this._events[eventName];if(listeners){listeners.forEach(listener=>listener.apply(this,args));}};// 创建 vm 根实例constvm=newVue({el:'#app'});// vm 通过 __proto__ 找到 Vue.prototype.$emitconsole.log(vm.$emit===Vue.prototype.$emit);// true// Vue 3 变化:弱化全局 Vue.prototype,改为应用实例级注入import{createApp}from'vue';constapp=createApp({});// 全局方法挂载到应用实例的核心原型app.config.globalProperties.$myMethod=function(){};constrootVc=app.mount('#app');// rootVc 通过 __proto__ 链式查找核心原型console.log(rootVc.$myMethod);// ƒ () {}

2. 组件实例(vc)的原型链(补充调试技巧)

Vue 组件的构造函数是VueComponent,其原型链设计保证了组件能继承 Vue 核心能力:

// Vue 内部简化逻辑(Vue 2)functionVueComponent(options){Vue.call(this,options);// 调用 Vue 构造函数,继承基础属性}// 核心:让组件原型继承 Vue.prototypeVueComponent.prototype.__proto__=Vue.prototype;// 渲染组件时,Vue 自动创建 vc 实例constvc=newVueComponent();// vc 的原型链:vc.__proto__ → VueComponent.prototype → __proto__ → Vue.prototypeconsole.log(vc.$emit===Vue.prototype.$emit);// true
调试技巧:在浏览器开发者工具查看原型链
  1. 打开 Vue Devtools → 选中任意组件 → 切换到“Console”;
  2. 输入$vm0(当前组件实例 vc)→ 展开__proto__可看到VueComponent.prototype
  3. 再展开__proto__可看到Vue.prototype,能直接查看$emit$watch等方法。

3. 组件 data 必须是函数的原型链原因(补充原理)

  • 若组件data是对象,会挂载到VueComponent.prototype上,导致所有 vc 实例共享同一data(数据污染);
  • 要求data是函数,每次创建 vc 时执行函数返回新对象,保证每个实例的data是独立私有属性:
    // 错误示例:data 为对象(所有 vc 共享)Vue.component('BadComp',{data:{count:0},// 挂载到 VueComponent.prototypetemplate:'<button @click="count++">{{ count }}</button>'});// 多次使用 <BadComp>,点击一个按钮,所有按钮的 count 都会增加(数据共享)// 正确示例:data 为函数(每个 vc 独立)Vue.component('GoodComp',{data(){return{count:0};},// 每次创建 vc 都返回新对象template:'<button @click="count++">{{ count }}</button>'});

4. Vue Mixin 与原型链的关系(新增场景)

Vue Mixin 的本质是修改组件的VueComponent.prototype,让 vc 实例通过__proto__优先查找 Mixin 中的方法:

// 定义 MixinconstmyMixin={methods:{mixinMethod(){console.log('Mixin 方法');}}};// 全局注册 MixinVue.mixin(myMixin);// 组件实例 vc 访问 Mixin 方法:vc.__proto__ → VueComponent.prototype → mixinMethodconstvc=vm.$refs.myComp;vc.mixinMethod();// Mixin 方法

5. Vue 3 setup 中访问原型属性(新增场景)

Vue 3<script setup>中无this,需通过getCurrentInstance获取实例,再访问原型属性:

<script setup> import { getCurrentInstance, onMounted } from 'vue'; onMounted(() => { const instance = getCurrentInstance(); // 访问原型上的全局属性 $api const $api = instance.proxy.$api; // 等价于:instance.vnode.componentInstance.__proto__.__proto__.$api $api.getList(); }); </script>

七、总结:核心区分表(补充对比维度)

维度prototype__proto__
归属仅函数(构造函数)拥有所有对象(实例、函数、普通对象)拥有
本质原型对象(仓库)原型指针(导航器,访问器属性)
作用存放供实例继承的公共属性/方法连接实例与原型对象,实现原型链查找
可修改性推荐修改(扩展原型、实现继承)不推荐修改(破坏原型链、性能下降)
关联实例.proto指向构造函数.prototype是实例访问 prototype 的唯一途径
内存特性共享内存(所有实例复用)仅占用指针内存(无额外开销)
Vue 中的体现Vue.prototype 存放核心方法;VueComponent.prototype 存放组件方法vm.proto指向 Vue.prototype;vc.proto指向 VueComponent.prototype
示例Person.prototype.sayHi = function(){}p1.__proto__ === Person.prototype

最终记忆口诀:

  • prototype是构造函数的“仓库”,存公共方法,省内存;
  • __proto__是实例的“导航”,找仓库内容,构链条;
  • 原型链 =__proto__串起的所有prototype
  • Vue 实例的核心能力,都是__proto__Vue.prototype仓库“取”来的。

学习价值:

掌握__proto__prototype的区别与关联,不仅能:

  1. 吃透 JavaScript 面向对象的底层原理(继承、原型链);
  2. 理解 Vue 实例的能力继承逻辑(如$emit为何能全局调用);
  3. 规避原型链相关的开发陷阱(如组件 data 类型错误、修改__proto__导致的 bug);
  4. 应对前端面试中的高频原型链问题(如手动实现继承、instanceof 原理)。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/13 3:24:29

java_base_(抽象类与接口区别篇)

我相信大家面对什么时候用抽象类&#xff0c;什么时候用接口会犯糊涂甚至手足无措。那么下面我将结合原神场景介绍一下它们各自的区别和特点&#xff0c;让你更了解何时用抽象类和接口。一、先明确核心&#xff1a;抽象类与接口到底是什么&#xff1f;在讲区别前&#xff0c;我…

作者头像 李华
网站建设 2026/3/9 0:47:41

开源游戏宝库终极指南:awesome-open-source-games

开源游戏宝库终极指南&#xff1a;awesome-open-source-games 【免费下载链接】awesome-open-source-games Collection of Games that have the source code available on GitHub 项目地址: https://gitcode.com/gh_mirrors/aw/awesome-open-source-games awesome-open-…

作者头像 李华
网站建设 2026/3/13 3:20:39

【C++】--红黑树的概念和实现

前言&#xff1a;在计算机科学的浩瀚领域中&#xff0c;数据结构是构建高效算法的基石&#xff0c;而树结构因其出色的层次性和查找效率&#xff0c;成为处理动态数据集合的核心选择。二叉搜索树作为基础的树结构&#xff0c;虽能实现快速的插入、删除与查找操作&#xff0c;但…

作者头像 李华
网站建设 2026/3/9 5:04:49

如何用WebRL技术实现浏览器自动化:5个快速提升效率的终极技巧

在数字化办公时代&#xff0c;浏览器操作占据了日常工作的大量时间。现在&#xff0c;借助智谱AI开源的WebRL-Llama-3.1-8B模型&#xff0c;任何人都可以轻松实现网页操作的智能化自动化。本文将为你揭示5个简单易学的技巧&#xff0c;让你的浏览器操作效率提升数倍。 【免费下…

作者头像 李华
网站建设 2026/3/13 3:52:36

ISO 26262功能安全标准:汽车电子系统安全开发完整指南

ISO 26262功能安全标准&#xff1a;汽车电子系统安全开发完整指南 【免费下载链接】ISO26262中文版本PDF下载分享 ISO 26262 中文版本 PDF 下载 项目地址: https://gitcode.com/Open-source-documentation-tutorial/442c6 ISO 26262标准是汽车电子系统功能安全领域的权威…

作者头像 李华