夜下客

繁体版 简体版
夜下客 > JS修炼法则 > 第25章 V8的sort实现

第25章 V8的sort实现

一.背景

在JS中排序很难。本文致力于发现排序算法和JS交互之间的怪癖,并讲述将V8迁移到稳定算法和可预测性能的过程。

当比较不同的排序算法时,我们会用描述内存操作或比较次数渐进增长的“大O表示法”,去比较最差性能和平均性能。

你需要知道,在动态语言(如 JS)中,一次比较操作付出的代价远高于一次内存访问。

这是因为在排序时比较两个值,通常意味着调用开发者的代码。

让我们看一个简单的升序排序函数。当两个比较的值结果是小于、等于、大于时,比较函数应对应返回负数、0、正数。

不遵循上述模式的比较函数,其可能会产生副作用,比如修改要排序的数组。

const array = [4, 2, 5, 3, 1];

function compare(a, b) {

// Arbitrary code goes here, e.g. `array.push(1);`.

return a - b;

// A “typical” sort call.

array.sort(compare);

再来一个示例,也可能调用开发者的代码。因为默认的比较函数是调用toString进行字典比较。

const array = [4, 2, 5, 3, 1];

array.push({

toString() {

// Arbitrary code goes here, e.g. `array.push(1);`.

return '42';

});

// Sort without a comparison function.

array.sort();

感兴趣的同学,可以自行阅读 V8 sort test 测试用例

二.极端情况:访问器和原型链交互

在本节我们将离开规范,进入“实现定义”的具体部分。

排序规范有一个完整的条件列表,当满足这些条件时,引擎将采用它认为合适的方式,对对象/数组进行排序,或者根本不排序。

引擎一定会遵循一些基本的规范,除此之外的规则会发生什么,只有引擎自己知道。比如 访问器和原型链交互。

一方面,这给了引擎开发人员自由去尝试不同的实现;另一方面,对于规范未定义的行为,JS开发者也期望出现符合预期的“合理行为”。

实际上,“合理行为”很难去界定。所以,引擎不会针对这种代码进行优化。

在Array#sort的某些极端情况,引擎行为仍存在很大差异。

第一个示例,一个包含访问器(即getter和setter)的数组,以及该代码在不同JS引擎中的执行情况。

const array = [0, 1, 2];

Object.defineProperty(array, '0', {

get() { console.log('get 0'); return 0; },

set(v) { console.log('set 0'); }

});

Object.defineProperty(array, '1', {

get() { console.log('get 1'); return 1; },

set(v) { console.log('set 1'); }

});

array.sort();

该代码在各种JS引擎中的输出如下。

这里没有绝对正确和错误的答案,因为这是规范留白给引擎去实现的。

// Chakra: 微软从IE9开始推出的新JavaScript引擎

get 0

get 1

set 0

set 1

// JavaScriptCore: C++实现的开源项目, 是WebKit的一部分

『加入书签,方便阅读』