深层继承链是否会减慢 V8 JavaScript 引擎中的方法查找速度?

azt*_*ack 3 javascript inheritance v8 mixins

我正在用 TypeScript 编写游戏的基类。它具有发送消息、资源管理等功能。受到Mixins的启发,我编写了以下代码(编译为 JavaScript):

function Messenger(Base) {
    return class Messenger extends Base {
        $dispatch(e) {
           // TODO
        }
    };
}
function ResourceManager(Base) {
    return class ResourceManager extends Base {
        $loadRes(key) {
            // TODO
            return Promise.resolve({});
        }
    };
}
class Component {
}
class GameBase extends Component {
    start() {
        console.log('start');
    }
    init() {
        console.log('init');
    }
}
const Klass = ResourceManager(Messenger(GameBase));
var gg = new Klass();
gg.start();
Run Code Online (Sandbox Code Playgroud)

据我所知,当我尝试调用 时gg.start,JavaScript 引擎会查找原型链,在这种情况下它会更长一点,并且当 mixins 增长时它会变得很长: 方法查找

这会减慢方法查找速度吗?V8 是否优化了这个查找过程?我可以忽略查找开销吗?

jmr*_*mrk 5

V8 开发者在这里。这是一个复杂的问题;简短的回答是“这取决于”。

毫无疑问,在进行查找时必须遍历较长的原型链会花费更多时间。然而,如果只进行一两次,那么时间通常太短而无关紧要。

所以下一个问题是:这样的查找多久执行一次?V8 尽可能地尝试缓存查找结果(如果您想了解更多信息,请搜索术语“内联缓存”);与所有缓存一样,此类缓存的有效性主要取决于所看到的不同案例的数量。

因此,如果您的代码大部分是“单态”(即在任何给定的foo.bar查找中,foo始终具有相同的类型/形状,包括相同的原型链),或低度多态(最多四种不同类型foo),则完整的原型链walk 只需要执行一次(或最多四次),之后将使用缓存的结果,因此,如果您执行此类代码数千次,您将不会看到原型链之间的性能差异一步或数百步长。

另一方面,如果您的属性加载或存储看到许多不同的类型(在某些框架中往往会发生这种情况,其中每个单独的查找都会经过某个中央getProperty(object, property) { /* do some framework stuff, and then: */ return object[property]; }函数),那么缓存就变得毫无用处,V8 必须执行完整的查找每次。对于长原型链来说,这特别慢,但这意味着它总是比可缓存的情况慢得多(即使是短原型链)。

总之,如果您对整体程序设计比较谨慎,并避免在同一代码位置有许多不同的类型,那么您可以轻松承受很长的原型链。事实上,保持尽可能多的代码单态类型往往比保持较短的原型链长度产生更大的影响。另一方面,较短的原型链长度确实使引擎的寿命更轻松,我个人认为它们可以(如果你不过分)也可以提高可读性,所以在其他条件相同的情况下,我建议保留你的对象模型尽可能简单。