JS实例成员函数可修改的原因及内存性能相关技术咨询
关于JavaScript/TypeScript面向对象的技术问题解答
代码示例
class T{ func(){return 0;} } let var1 = new T; var1.func = ()=>{return 1;} console.assert((new T).func() != var1.func())
问题与解答
1. JS中的成员函数是否仅为「键(函数名)-值(函数指针)」对?每个实例是否占用64位内存?
- 分两种场景:
- 原型成员函数:默认类定义的成员函数会挂载到类的原型(如
T.prototype)上,实例自身并不存储这个函数,仅通过原型链查找调用。这种情况下实例里不存在对应的键值对。 - 实例自有函数:像示例中给
var1直接赋值func时,才会在实例自身添加「函数名-函数引用」的键值对。
- 原型成员函数:默认类定义的成员函数会挂载到类的原型(如
- 内存方面:JS实例的内存大小不是固定64位。实例内存由引擎内部对象头(如隐藏类指针、GC标记位)、自身属性的数量和类型共同决定。如果实例无自有属性,仅继承原型,内存主要是对象头;若有自有函数属性,会额外存储一个函数引用(64位系统下为8字节,但这只是单个属性的大小,不是整个实例的总内存)。
2. JS是否存在类似C++的虚拟函数表(virtual Function Table)?
JS引擎(如V8)内部有类似虚表的优化机制,但和C++的虚表差异很大:
- V8使用**隐藏类(Hidden Class)和内联缓存(Inline Cache)**优化属性/方法访问。当调用对象方法时,引擎会通过隐藏类快速定位原型上的函数,作用类似C虚表,但JS的隐藏类是动态更新的(因为对象可随时增删属性),而C虚表是编译期固定的。
- JS中所有方法调用本质都是动态绑定(除非显式指定),但这是通过原型链实现的,没有暴露给开发者的显式虚表结构。
3. 在基类中添加仅部分派生类使用的虚函数是否会降低性能?是否应采用拆分基类的实现方式?
先看给出的拆分实现代码:
class Base{ //loadAsset(){}; //是否会导致所有派生类浪费内存? } class BaseWithAsset extends Base{ loadAsset(){}; //所有需要loadAsset的派生类应继承BaseWithAsset }
- 性能与内存影响:把
loadAsset放在Base原型上,所有派生类实例不会额外占用内存——函数存储在Base原型中,所有实例共享,不会每个实例复制一份。性能上的损耗可以忽略,引擎的内联缓存会优化方法调用,即使派生类未重写该方法,一次缓存命中后就能快速调用。 - 拆分的必要性:如果是为了代码架构清晰(明确区分需要资源加载能力的类),可以拆分;但如果仅为性能或内存,完全没必要。拆分过多反而会增加类层级复杂度,得不偿失。
4. 静态成员函数是否更值得优先使用以节省内存?
- 内存层面:静态函数挂载在类本身(如
Base对象)上,原型成员函数挂载在类的原型上,两者都是仅存储一份,所有实例共享,因此在内存节省上没有本质区别。 - 使用选择:优先考虑语义适配:静态函数用于与实例状态无关的操作(如工具方法、工厂方法);原型成员函数用于需要访问实例状态(
this)的逻辑。强行将实例相关逻辑改为静态函数,需要手动传递实例参数,会导致代码冗余且不符合面向对象语义,不要为了“节省内存”优先使用静态函数。
内容的提问来源于stack exchange,提问作者YukiYuna
相关产品推荐
相关产品推荐

