全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

通过V8源码看一个关于JS数组排序的诡异问题

前言

前几天一个朋友在微信里面问我一个关于 JS 数组排序的问题。通过该问题发现了一些之前没发现的内容,下面话不多少了,来一起看看详细的介绍吧。

原始数组如下:

var data = [
 {value: 4}, 
 {value: 2}, 
 {value: undefined}, 
 {value: undefined}, 
 {value: 1}, 
 {value: undefined}, 
 {value: undefined}, 
 {value: 7}, 
 {value: undefined}, 
 {value: 4}
];

data 是个数组,数组的每一项都是一个拥有 value 作为 key 的对象,值为数字或者 undefined。

data
 .sort((x, y) => x.value - y.value)
 .map(x => x.value);

对数组的 value 进行排序,然后把排完序的数组进行 flat 处理。得到的结果如下:

[2, 4, undefined, undefined, 1, undefined, undefined, 7, undefined, 4]

显然这没有达到我们的目的。

现在我们修改一下排序,挑战一下函数的调用顺序:先对数组进行扁平化(flat)处理,然后再排序。

data
 .map(x => x.value)
 .sort((x, y) => x - y)

这时我们得到的结果和之前截然不同:

[1, 2, 4, 4, 7, undefined, undefined, undefined, undefined, undefined]

遇到这种情况第一感觉肯定是要去看看 ECMA 规范,万一是 JS 引擎的 bug 呢。

在 ES6 规范 22.1.3.24 节写道:

Calling comparefn(a,b) always returns the same value v when given a specific pair of values a and b as its two arguments. Furthermore, Type(v) is Number, and v is not NaN. Note that this implies that exactly one of a < b, a = b, and a > b will be true for a given pair of a and b.

简单翻译一下就是:第二个参数 comparefn 返回一个数字,并且不是 NaN。一个注意事项是,对于参与比较的两个数 a 小于 b、a 等于 b、a 大于 b 这三种情况必须有一个为 true。

所以严格意义上来说,这段代码是有 bug 的,因为比较的结果出现了 NaN。

在 MDN 文档上还有一个细节:

如果 comparefn(a, b) 等于 0, a 和 b 的相对位置不变。备注:ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守。

翻译成编程术语就是:sort 排序算法是不稳定排序。

其实我们最疑惑的问题上,上面两行代码为什么会输出不同的结果。我们只能通过查看 V8 源码去找答案了。

V8 对数组排序是这样进行的:

如果没有定义 comparefn 参数,则生成一个(高能预警,有坑啊):

comparefn = function (x, y) {
 if (x === y) return 0;
 if (%_IsSmi(x) && %_IsSmi(y)) {
 return %SmiLexicographicCompare(x, y);
 }
 x = TO_STRING(x); // <----- 坑
 y = TO_STRING(y); // <----- 坑
 if (x == y) return 0;
 else return x < y ? -1 : 1;
};

然后定义了一个插入排序算法:

function InsertionSort(a, from, to) {
 for (var i = from + 1; i < to; i++) {
 var element = a[i];
 for (var j = i - 1; j >= from; j--) {
  var tmp = a[j];
  var order = comparefn(tmp, element);
  if (order > 0) { // <---- 注意这里
  a[j + 1] = tmp;
  } else {
  break;
  }
 }
 a[j + 1] = element;
}

为什么是插入排序?V8 为了性能考虑,当数组元素个数少于 10 个时,使用插入排序;大于 10 个时使用快速排序。

后面还定义了快速排序函数和其它几个函数,我就不一一列出了。

函数都定义完成后,开始正式的排序操作:

// %RemoveArrayHoles returns -1 if fast removal is not supported.
var num_non_undefined = %RemoveArrayHoles(array, length);

if (num_non_undefined == -1) {
 // There were indexed accessors in the array.
 // Move array holes and undefineds to the end using a Javascript function
 // that is safe in the presence of accessors.
 num_non_undefined = SafeRemoveArrayHoles(array);
}

中间的注释:Move array holes and undefineds to the end using a Javascript function。排序之前会把数组里面的 undefined 移动到最后。因此第二个排序算法会把 undefined 移动到最后,然后对剩余的数据 [4,2,1,7,4] 进行排序。

而在第一种写法时,数组的每一项都是一个 Object,然后最 Object 调用 x.value - y.value 进行计算,当 undefined 参与运算时比较的结果是 NaN。

当返回 NaN 时 V8 怎么处理的呢?我前面标注过,再贴一次:

var order = comparefn(tmp, element);
if (order > 0) { // <---- 这里
 a[j + 1] = tmp;
} else {
 break;
}

NaN > 0 为 false,执行了 else 分支代码。

思考题,以下代码的结果:

[1, 23, 2, 3].sort()

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。


# js  # 数组排序  # 按大小  # 二维数组排序  # 详解JavaScript引擎V8执行流程  # Node.js API详解之 V8模块用法实例分析  # JavaScript深入V8引擎以及编写优化代码的5个技巧  # 详解V8是如何执行一段JavaScript代码原理  # 都是  # 第二个  # 会把  # 有一个  # 每一项  # 几个  # 我就  # 这一  # 是个  # 出了  # 是有  # 是这样  # 一是  # 不多  # 而在  # 要去  # 这段  # 问我  # 去找  # 少了 


相关文章: 如何设计高效校园网站?  网站制作公司哪里好做,成都网站制作公司哪家做得比较好,更正规?  如何快速辨别茅台真假?关键步骤解析  网页设计与网站制作内容,怎样注册网站?  b2c电商网站制作流程,b2c水平综合的电商平台?  视频网站app制作软件,有什么好的视频聊天网站或者软件?  如何选择CMS系统实现快速建站与SEO优化?  外贸公司网站制作哪家好,maersk船公司官网?  魔毅自助建站系统:模板定制与SEO优化一键生成指南  如何确认建站备案号应放置的具体位置?  小说建站VPS选用指南:性能对比、配置优化与建站方案解析  python的本地网站制作,如何创建本地站点?  上海网站制作网页,上海本地的生活网站有哪些?最好包括生活的各个方面的?  厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?  c# 在高并发场景下,委托和接口调用的性能对比  如何选择网络建站服务器?高效建站必看指南  如何在Golang中指定模块版本_使用go.mod控制版本号  郑州企业网站制作公司,郑州招聘网站有哪些?  如何在Golang中处理模块冲突_解决依赖版本不兼容问题  ,网页ppt怎么弄成自己的ppt?  Android使用GridView实现日历的简单功能  ppt在线制作免费网站推荐,有什么下载免费的ppt模板网站?  测试制作网站有哪些,测试性取向的权威测试或者网站?  详解免费开源的.NET多类型文件解压缩组件SharpZipLib(.NET组件介绍之七)  如何通过宝塔面板实现本地网站访问?  免费网站制作appp,免费制作app哪个平台好?  桂林网站制作公司有哪些,桂林马拉松怎么报名?  利用JavaScript实现拖拽改变元素大小  Python lxml的etree和ElementTree有什么区别  如何获取免费开源的自助建站系统源码?  ,巨量百应是干嘛的?  湖南网站制作公司,湖南上善若水科技有限公司做什么的?  高端建站如何打造兼具美学与转化的品牌官网?  家族网站制作贴纸教程视频,用豆子做粘帖画怎么制作?  独立制作一个网站多少钱,建立网站需要花多少钱?  TestNG的testng.xml配置文件怎么写  深圳网站制作平台,深圳市做网站好的公司有哪些?  专业网站设计制作公司,如何制作一个企业网站,建设网站的基本步骤有哪些?  网站制作服务平台,有什么网站可以发布本地服务信息?  如何在建站之星网店版论坛获取技术支持?  网站制作知乎推荐,想做自己的网站用什么工具比较好?  如何选择高性价比服务器搭建个人网站?  如何选购建站域名与空间?自助平台全解析  如何通过山东自助建站平台快速注册域名?  建站与域名管理如何高效结合?  平台云上自助建站如何快速打造专业网站?  建站之星导航菜单设置与功能模块配置全攻略  企业网站制作费用多少,企业网站空间一般需要多大,费用是多少?  学校为何禁止电信移动建设网站?  如何在IIS7上新建站点并设置安全权限? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。