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. c/c++ SQLite3的常用使用方法;

    下面测试用的sqlite例子;大家可以参考使用; #include "CppSQLite3.h" Class TestSqlite{ //定义db指针 private: CppSQ ...

  2. Reverse Integer - 反转一个int,溢出时返回0

    Reverse Integer Reverse digits of an integer. Example1: x = 123, return 321Example2: x = -123, retur ...

  3. linux下查看jdk路径

    jdk安装后 centos中: 执行 rpm -ql java-1.7.0-openjdk-devel | grep '/bin/javac' 命令确定, 执行后会输出一个路径,除去路径末尾的 &qu ...

  4. (转)log4j(七)——log4j.xml简单配置样例说明

    背景:在公司中警察需要做技术支持,查看日志,而查看日志首先要自己清楚日志是如何生成的,所以有必要知道日志的前世今生! 转载出处:http://www.cnblogs.com/godtrue/p/644 ...

  5. spring-boot整合mybatis(1)

    sprig-boot是一个微服务架构,加快了spring工程快速开发,以及简便了配置.接下来开始spring-boot与mybatis的整合. 1.创建一个maven工程命名为spring-boot- ...

  6. 统一代码风格工具——editorConfig

    前面的话 在团队开发中,统一的代码格式是必要的.但是不同开发人员的代码风格不同,代码编辑工具的默认格式也不相同,这样就造成代码的differ.而editorConfig可以帮助开发人员在不同的编辑器和 ...

  7. python教程6-3:排序

    (a).输入一串数字.并从大到小排列. (b).跟a一样,不过要用字典序从大到小排列. python35 PaiXu_6_3.py PaiXu_6_3.py #coding=utf-8 import ...

  8. 写移动端必备的meta标签

    <meta name="renderer" content="webkit" /> <meta http-equiv="X-UA-C ...

  9. js验证15位或18位身份证

    本篇文章是本人在网上搜集了一些验证,然后又个人进行一定修改的关于身份证的验证,欢迎修改指正..... function IdCardValidateRule(idCard) { var tip;    ...

  10. Hello BlogsPark

    2017年8月4日, 今天是使用博客园的第一天, 签个到. NSLog(@"Hello BlogsPark");