Giter Club home page Giter Club logo

proposal-array.prototype.includes's Introduction

Array.prototype.includes Proposal

Spec

Status

This proposal is officially in stage 4 of the TC39 process, and is in the process of being integrated into the spec.

This proposal was formerly for Array.prototype.contains, but that name is not web-compatible. Per the November 2014 TC39 meeting, the name of both String.prototype.contains and Array.prototype.contains was changed to includes to dodge that bullet.

Motivation

When using ECMAScript arrays, it is commonly desired to determine if the array includes an element. The prevailing pattern for this is

if (arr.indexOf(el) !== -1) {
    ...
}

with various other possibilities, e.g. arr.indexOf(el) >= 0, or even ~arr.indexOf(el).

These patterns exhibit two problems:

  • They fail to "say what you mean": instead of asking about whether the array includes an element, you ask what the index of the first occurrence of that element in the array is, and then compare it or bit-twiddle it, to determine the answer to your actual question.
  • They fail for NaN, as indexOf uses Strict Equality Comparison and thus [NaN].indexOf(NaN) === -1.

Proposed Solution

We propose the addition of an Array.prototype.includes method, such that the above patterns can be rewritten as

if (arr.includes(el)) {
    ...
}

This has almost the same semantics as the above, except that it uses the SameValueZero comparison algorithm instead of Strict Equality Comparison, thus making [NaN].includes(NaN) true.

Thus, this proposal solves both problems seen in existing code.

We additionally add a fromIndex parameter, similar to Array.prototype.indexOf and String.prototype.includes, for consistency.

FAQs

Why includes instead of has?

If you survey existing APIs, has is used for conceptual "keys," whereas includes is used for conceptual "values." That is:

  • Keys inside a key-value map: Map.prototype.has(key), WeakMap.prototype.has(key), Reflect.has(target, propertyKey)
  • Sets, whose elements are conceptually both keys and values: Set.prototype.has(value), WeakSet.prototype.has(value), Reflect.Loader.prototype.has(name)
  • Strings, which are conceptually maps from indices to code points: String.prototype.includes(searchString, position)

The best consistency here is with String, not with Map or Set.

The web has classes like DOMStringList and DOMTokenList which are array-like, and have methods named contains with the same semantics as our includes. Unfortunately, meshing with those is not web-compatible, as explained above; we will have to accept this inconsistency.

But String.prototype.includes works on strings, not characters!?

Yes, that's true. The best way to think about this is that String.prototype.indexOf and String.prototype.includes behave like their Array.prototype counterparts in the special case of a single character. But the string versions can also be used in the more general case of a larger string.

So in this way, the relationship between String.prototype.includes and Array.prototype.includes is the same as the relationship between String.prototype.indexOf and Array.prototype.indexOf.

Why SameValueZero?

There are four equality algorithms in the current ES6 draft:

  • Abstract Equality Comparison (==)
  • Strict Equality Comparison (===): used by Array.prototype.indexOf, Array.prototype.lastIndexOf, and case-matching
  • SameValueZero: used by %TypedArray% and ArrayBuffer constructors, as well as Map and Set operations
  • SameValue: used in all other places

(Note however that most places SameValue is used could be replaced by SameValueZero since those places often never compare primitives, or at least never compare numbers.)

Using Abstract Equality Comparison would be bonkers, of course. Using SameValue is not a good idea for the same reasons it is not used by Map and Set. (Briefly: -0s can sneak into your code fairly easily via arithmetic operations, but you almost always desire -0 to be treated the same as +0, so distinguishing them will just cause spurious failures.) This leaves Strict Equality Comparison and SameValueZero as the two possibilities.

SameValueZero is generally the better choice, as it allows you to detect if an array includes a NaN. The argument for Strict Equality Comparison boils down to "bug compatibility" with Array.prototype.indexOf. But one of the purposes of Array.prototype.includes is to steer users away from creating these sort of bugs.

This introduces a slight refactoring hazard from Array.prototype.indexOf to Array.prototype.includes: they will indeed behave differently for arrays containing NaNs. However, it seems much more likely that code will become less buggy via this refactoring, instead of causing problems. Introducing a new method, and accompanying it with the appropriate messaging around this case, should help.

Typed Arrays

As with all non-mutating array methods, we also install this method on %TypedArray%.prototype.

Illustrative Examples

assert([1, 2, 3].includes(2) === true);
assert([1, 2, 3].includes(4) === false);

assert([1, 2, NaN].includes(NaN) === true);

assert([1, 2, -0].includes(+0) === true);
assert([1, 2, +0].includes(-0) === true);

assert(["a", "b", "c"].includes("a") === true);
assert(["a", "b", "c"].includes("a", 1) === false);

proposal-array.prototype.includes's People

Contributors

domenic avatar kentaromiura avatar robertkowalski avatar thomasr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

proposal-array.prototype.includes's Issues

Publish to npm

Please publish this module to npm, even if it's in the 0.x range :-) I'd like to add it as a dependency.

Put tests in separate module

What would you think about putting the tests in a separate module?

I'm about to release an ES3-compatible Array#includes polyfill, and while I've got my own tests, I'd love to run yours against my module. Currently, I tried installing this module as a devDep, and running npm explore array.prototype.includes -- 'npm install && npm test' which only works if I modify your package.json to reference my prelude.

If the tests are a separate module, then both of us could require it and run it against our own shimmed environments. Thoughts?

Documentation

Mostly FAQs, I think, to cover design decisions and things like "why not has?"

[NaN].includes(NaN) === true?

Surely this is going to just confuse people. How about to do this check,

assert([NaN].some(Number.isNaN) === true)

which already works.

Get into V8

  • Write and submit patch
  • Get accepted as experimental (behind a flag)
  • Get shipping

Should we deal with holes?

The operative question is:

var arrayWithHoles = [,,,];
arrayWithHoles.contains(undefined) // true or false?

Additionally there is a consequence for proxies: because holes are checked via HasProperty before doing the GetProperty, a weird proxy could respond inconsistently between the two (e.g. proxy[5] === "foo" but Array.prototype.contains.call(proxy, "foo") === false).

I am leaning toward consistency with other array functions in that we should respect holes.

Tests

Must be in test262 format. Ideally use the Node test262 runner @bterlson is developing (which I will probably submit patches to in the process).

  • Smoke tests
  • Array-like
  • Holes
  • ToObject
  • zero length (array and array-like)
  • negative length
  • throwing getters should cause error to be rethrown at the right algorithm step
  • ToLength works: missing .length property; negative; above max-length; Infinities; NaN; valueOf
  • fromIndex: above .length
  • fromIndex: between -.length and 0
  • fromIndex: -.length
  • fromIndex: below -.length
  • fromIndex: rounding behavior
  • fromIndex: infinities; NaN; valueOf
  • SameValueZero (kind of duplicated by smoke tests)
  • non-primitives get found correctly
  • Mutate global objects (e.g. global.Object) to ensure they are not called. Will probably require fixing especially to make the reference implementation pass.
  • Check the .length property
  • Check the property descriptor (non-enumerable, configurable, writable; test both the propdesc directly and the behaviors. test262 has helpers for this already).
  • Check the name

Advance to stage 2

Criteria:

  • Those from stage 1

This is #6

  • Initial spec text

This is done

Implementation types expected during this stage:

  • Experimental

Tracked by #7, #8, #9.

Finally:

  • Get TC39 to agree that we have advanced to stage 2, after meeting all the above requirements.

why is includes not implemented like the other related methods (find, findIndex etc.)

the way this is implemented right now just works for arrays containing primitives. when you have objects and you want the includes method to target just one of the properties (or maybe some of them), you have to fall back to .findIndex()!==-1 (or other solutions) again.

example:
(code is TypeScript)

interface Data{
  propA: string;
  propB: number;
}
let myDataArray: Data[] = [];

myDataArray.push({ propA: "one", propB: 1 });
myDataArray.push({ propA: "two", propB: 2 });

let result = myDataArray.findIndex(element:Data=>(element.propA==="one"))!==-1;`

it would be much nicer and more flexible to be able to just write:

let result = myDataArray.includes(element: Data => (element.propA === "one"));

the only "downsides" I can see would be

  1. the selection of the appropriate way of comparison would be left to the user (now its pre-defined in a particular way for strings, numbers etc.)
  2. the usage for the most simple case (array of primitives) would be slightly more verbose
    let result = simpleArray.includes(element => (element === "one"));
    instead of
    let result = simpleArray.includes("one");

in my opinion the benefits outweigh these two though. also the same rationale applies to Array.prototype.find or Array.prototype.findIndex.

Reference implementation

Must be TDDed. Should follow the spec exactly. By TDDing it we ensure every line of the spec has coverage.

Which equality comparator?

There are three choices in the ES spec: Strict Equality Comparison, SameValueZero, and SameValue.

Spec currently copies indexOf to use Strict Equality Comparison.

Advance to stage 3

Criteria:

  • Those from stage 2

This is #10

  • Complete spec text

Pending design problems being resolved.

  • Designated reviewers have signed off on the current spec text.

Not sure who these will be?

  • The ECMAScript editor has signed off on the current spec text.

Tracked by #11

Implementation types expected during this stage:

  • Spec compliant

Tracked as #7, #8, #9, #27, #28. Ideally this should be done at the same time as the experimental ones (i.e. there should be no experimental ones, just ones that follow the finalized spec text).

Finally:

  • Get TC39 to agree that we have advanced to stage 3, after meeting all the above requirements.

Holes?

What happens with a sparse array? Should Array(1).includes() be true (indicating that holes are traversed) or false (indicating that holes are skipped)?

All of the Array.prototype methods in ES6/ES2015 skip holes, so I'd expect includes to do so as well for consistency.

add "excludes" for symmetry

If it would be still called "contains" this would have never crossed my mind, but "includes"/"excludes" feels very natural.

array.excludes(thing)

instead of:

!array.includes(thing)

Having some '!' somewhere at the very left always feels kinda clunky - even more so if there are a few other method calls before you get to this final includes call.

Interestingly, the ES3/5/6 flavor with indexOf doesn't have this kind of issue because the "=== -1" or "!== -1" bit comes at the very end.

Includes:

array.indexOf(thing) !== -1

Excludes:

 array.indexOf(thing) === -1

Should Array.prototype.contains be enumerable?

I believe typically Array.prototype methods are not enumerable - ie, instead of direct Put assignment, they're set with Object.defineProperty where available. Should this shim do the same?

Add "includes" to unscopables?

I didn't find any info why it's okay to omit "includes" from unscopables. ES2015 added all new Array.prototype methods to Array.prototype[@@unscopables] even though only Array.prototype.values was problematic for web-compatibility.

Element.classList.contains but Array.prototype.includes?

Don't you think this will be confusing that Element.classList.contains, but Array.prototype.includes?
From the link that you provided you don't want to name this method contains because of bug MooTools had 3 years ago? If i create a new library "NewLib.js" that has bug with Array.prototype.includes would you change the name again? It doesn't sound reasonable to me.

Is fromIndex good?

Compare against String.prototype.contains and other array methods. Or just leave it out entirely??

Advance to stage 1

From the TC39 Process

Criteria:

  • Identified “champion” who will advance the addition

Done. (Me and @rwaldron.)

  • Prose outlining the problem or need and the general shape of a solution.

Part of #3

  • Illustrative examples of usage

Part of #3

  • High-level API

Done

  • Discussion of key algorithms, abstractions and semantics

Done. Includes design problems issues.

  • Identification of potential “cross-cutting” concerns and implementation challenges/complexity

There should be none.

Implementation types expected

  • Polyfills / demos

This is #2

Finally:

  • Get TC39 to agree that we have advanced to stage 1, after meeting all the above requirements.

Advance to stage 4

Criteria:

  • Those from stage 3

This is #12.

  • Test 262 acceptance tests have been written for mainline usage scenarios.

This is #1.

  • Two compatible implementations which pass the acceptance tests.

This requires completion of two out of #7, #8, #9 plus also #27 and #28.

  • The ECMAScript editor has signed off on the current spec text.

This seems like a dupe of a requirement from stage 3. In any case, tracked as #11.

Implementation types expected during this stage:

  • Shipping

This requires completion of two out of #7, #8, #9 plus also #27 and #28.

Finally:

  • Get TC39 to agree that we have advanced to stage 4, after meeting all the above requirements.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.