ECMAScript 7 中新增了用于检测数组中是否包含某个元素 Array.prototype.includes() API,想到了 Array 其实有很多相关 API 可以检测到是否包含某个元素,比如 Array.prototype.indexOf,于是好奇为什么要实现这样一个 "看起来功能有点重复的 API"。

前端开发 QQ 群:377786580

原文发表于 http://tasaid.com,转载请参阅 转载授权

前言

最近又看了下 ECMAScript 7 规范,看到新的规范中包含 Array.prototype.includes(),方法签名如下:

Array.prototype.includes(value : any): boolean

Array.prototype.includes() 是用于检测数组中是否包含某个元素。

[0, 1].includes(1) // true
['foo', 'bar'].includes('baz') // false

想到了 Array 其实有很多相关 API 可以检测到是否包含某个元素:

[0, 1].findIndex(i => i == 1) // 1
['foo', 'baz'].find(i => i == 'foo') // foo
['foo', 'baz'].indexOf('foo') // 0
  • Array.prototype.findIndex():返回数组中满足提供的测试函数的第一个元素的索引。否则返回 -1
  • Array.prototype.find():返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined
  • Array.prototype.indexOf():返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回 -1

我们可以简单的通过判断实现类似 Array.prototype.includes() 的效果:

export const includes = (sources : any[] searchElement: any): boolean => {
return !!~any.indexOf(searchElement)
}

于是好奇为什么要实现这样一个 "看起来功能有点重复的 API"。

查询了 StackOverflow 和 TC39 (Technical Committee 39,JavaScript 委员会) 的 ECMAScript 提案,找到一些细节。

Array.prototype.includes 前身

早前的 Array.prototype.includes 的提案名为 Array.prototype.contains,但由于有很多网站自行 hack 了 Array.prototype.contains(其实主要是因为 MooTools 导致的),看起来就跟上面的代码类似。

JavaScript 中所有原生提供的方法属性都是 不可枚举的( enumerable ) 的,我们可以通过 Object.getOwnPropertyDescriptor(object: any, prototypeName : String) 来获取这个属性的属性描述符 (Property Descriptor)。

Object.getOwnPropertyDescriptor(Array.prototype, 'indexOf')
// output { writable: true, enumerable: false, configurable: true, value: ƒ() }

给对象赋值,是不会改变原属性的属性描述符,我们可以给 Array.prototype.indexOf 重新赋值,之后获取它的属性描述符,会发现 indexOf 仍是不可枚举的:

Array.prototype.indexOf = () => { return -1 }
Object.getOwnPropertyDescriptor(Array.prototype, 'indexOf')
// output { writable: true, enumerable: false, configurable: true, value: ƒ() }

而这些网站自行 hackcontains() 是可以被枚举的,也就是可以通过 for..in 读出来。

发现问题了么?

如果规范实现 contains(),会导致 contains() 无法被 for..in 读出来,而之前自行 hackcontains() 是可以被读出来的,所以会出现代码没变动,但是在新规范推出后会产生 bug 的情况。

Array.prototype.contains 初稿阶段,考虑到新的规范不能让世界上许多现有的网站出问题,所以改名成了 Array.prototype.includes

细节

起源

虽然我们可以使用 indexOf() 来模拟 includes() 的行为,但是 indexOf() 在语义上无法清晰的描述这个场景。

includes() 是明确的判断 "是否包含该项",而 indexOf() 是 "查找数组中第一次出现对应元素的索引是什么,再针对返回的索引进一步处理逻辑",例如下面的代码:

// indexOf
if (~arr.indexOf(1)) {
// do something
} // includes
if (arr.includes(1)) {
// do something
}

为什么叫做 includes 而不是 has

has 是用于 key 的,而 includes 是检测 value 的:

let foo = new Map()
foo.set('name', 'linkFly')
foo.has('name') // true

SameValueZero

Array.prototype.includes 底层使用了 SameValueZero() 进行元素比较。

目前 ES2015 草案中有四种相等算法:

  • 抽象标准相等比较:实现接口是 == 运算符

  • 严格相等比较:实现接口是 === 运算符,Array.prototype.indexOf 就是使用这种比较

  • SameValueZero():没有直接暴露的接口,内部实现接口是 MapSet

    const foo = new Map()
    foo.set(0, '0') // Map(1) {0 => "0"}
    foo.set('0', 'zero') // Map(2) {0 => "0", "0" => "zero"}
    foo.get(0) // 0
    foo.get('0') // zero
  • SameValue():实现接口是 Object.is()

    NaN === NaN // false
    Object.is(NaN, NaN) // true -0 === +0 // true
    Object.is(-0, +0) // false

SameValue() 不同的是,SameValueZero() 不区分 +0-0。而 includes 为了和 JavaScript 其他特性保持一致 所以内部也采用了 SameValueZero 实现。

所以 Array.prototype.includes 也不区分 +0-0 ,当然也可以检测 NaN

[-0].includes(+0) // true
[NaN].includes(NaN) // true
[NaN].indexOf(NaN) // -1

具体的相等比较运算符差异请参阅 [MDN - Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#A model for understanding equality comparisons)。

具体 Array.prototype.includes 实现的细节可以参考 ecma-262/ECMAScript 7 实现规范

参考和引用

有了 indexOf,为什么 ECMAScript 7 还添加了 Array.prototype.include的更多相关文章

  1. ES6中新添加的Array.prototype.fill

    用法 array.fill(start=0, end=this.length) 示例 [1, 2, 3].fill(4) // [4, 4, 4] [1, 2, 3].fill(4, 1) // [1 ...

  2. [基础] Array.prototype.indexOf()查询方式

    背景 最近在看Redux源码,createStore用于注册一个全局store,其内部维护一个Listeren数组,存放state变化时所有的响应函数. 其中store.subscribe(liste ...

  3. 使用Array.prototype.indexOf()的几点注意

    对应indexOf这个方法,在日常开发中比较常见的应该是String.prototype.indexOf()方法,Array.prototype.indexOf()方法和其有很大的相似性,本文不想去描 ...

  4. Array.prototype.indexOf

    arr.indexOf(searchElement[, fromIndex = 0]) Array.prototype.indexOf()

  5. 利用Array Prototype的方法来实现对dom集合的筛选、indexOf、map等功能

    <!DOCTYPE html><html> <head> <title>TODO supply a title</title> <me ...

  6. 数组Array和字符串String的indexOf方法,以及ES7(ES2016)中新增的Array.prototype.includes方法

    前言 我们在判断某一个字符是否存在于一个字符串中或者某一个值是否存在于一个数组中时,ES7之前我们需要使用indexOf,ES7引入了新的方法includes 语法 数组:Array.inexOf(s ...

  7. JS高级---为内置对象添加原型方法

    为内置对象添加原型方法 我们能否为系统的对象的原型中添加方法, 相当于在改变源码   我希望字符串中有一个倒序字符串的方法 //我希望字符串中有一个倒序字符串的方法 String.prototype. ...

  8. 第七章:Javascript数组

    数组是值的有序结合.每个值叫做一个元素,而每个元素在数组中都有一个位置,用数字表示,称为索引. javascript数组是无类型的:数组的元素可以是任意类型,并且同一个数组中的不同元素也可能有不同的类 ...

  9. 《JavaScript 闯关记》之数组

    数组是值的有序集合.每个值叫做一个元素,而每个元素在数组中有一个位置,以数字表示,称为索引. JavaScript 数组是无类型的,数组元素可以是任意类型,并且同一个数组中的不同元素也可能有不同的类型 ...

随机推荐

  1. STL—vector

    前面介绍了STL对象的构造与析构以及内存的配置与释放,那具体的容器是怎么应用STL的空间配置器的呢?这篇先介绍STL的容器vector. vector的数据成员 vector只有4个数据成员:3个迭代 ...

  2. Android - TabHost 与 Fragment 制作页面切换效果

    Android - TabHost 与 Fragment 制作页面切换效果 Android API 19 , API 23 三个标签页置于顶端 效果图: 在文件BoardTabHost.java中定义 ...

  3. nopCommerce 3.9 大波浪系列 之 汉化-Roxy Fileman

    官网:http://www.roxyfileman.com/ 中文包:zh.json 1.将zh.json包拷贝到Nop.Admin项目中"Content\Roxy_Fileman\lang ...

  4. 关于阻止PROE联网的一些想法!

    前言:商业上使用盗版proe会被官方警告,官方是通过proe软件联网来获取你的ip地址,那他是怎么知道你是商业用途而不是个人(一般不会对个人的盗版行为进行警告)?这不难,应该是通过检测同一ip下有多台 ...

  5. Spring初学

    一.spring体系结构spring核心组件 1.Beans(包装应用程序自定义对象Object,Object中存有数据) 2.Core (资源加载,资源抽象,建立维护与bean之间的一些关系所需的一 ...

  6. Spring+SpringMVC+MyBatis集成学习笔记【一】

    一,首先要清楚,SpringMVC其实就是Spring的一个组件       例如我们知道Spring中有类似于,AOP TX等等类似的组件,所以SpringMVC其实就是Spring的一个组件,是S ...

  7. Spring Data JPA在Spring Boot中的应用

    1.JPA JPA(Java Persistence API)是Sun官方提出的Java持久化规范.它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据.他的出现主要是为了简 ...

  8. (转)@ContextConfiguration注解说明

    场景:学习spring实战中相关的单元测试 1 正常使用 @ContextConfiguration Spring整合JUnit4测试时,使用注解引入多个配置文件 1.1 单个文件 @ContextC ...

  9. 【javascript】数组的操作

    一.常用操作 toString():把数组转换成一个字符串  toLocaleString():把数组转换成一个字符串  join():把数组转换成一个用符号连接的字符串  shift():将数组头部 ...

  10. python cookbook第三版学习笔记十三:类和对象(三)描述器

    __get__以及__set__:假设T是一个类,t是他的实例,d是它的一个描述器属性.读取属性的时候T.d返回的是d.__get__(None,T),t.d返回的是d.__get__(t,T).说法 ...