DEV Community

Safia Abdalla
Safia Abdalla

Posted on

The fun of filter, according to the ECMAScript spec

In one of my recent blog posts, I took a look at the ECMAScript specification for the Array.prototype.map method in JavaScript. I found the ECMAScript specification rather interesting. For one, it's a single document that underpins the entirety of one of the most popular programming languages in the world. It sits there, mysterious and precise, just waiting to be combed through.

So this time around, I decided to look at the ECMAScript specification for Array.prototype.filter method.

If you've used both filter and map before, you know that they share a lot of similarities. Although they have different functionality, they accept the same argument interface. That's why the first portions of the spec for both methods are virtually identical. For example:

If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn. If it is not provided, undefined is used instead.

You can pass a second parameter to both methods that will set the context for this in the function callback.

filter does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.

Similar to map, filter is intended to be used to create a modified copy of the array, not modify the original array itself.

The range of elements processed by filter is set before the first call to callbackfn. Elements which are appended to the array after the call to filter begins will not be visited by callbackfn.

map and filter also share this same caveat. They operate on the state of the array when the methods are invoked. If the array is modified elsewhere, those changes won't be "visible" to the callback function invoked y the map and filter methods.

Now that we've covered the similarities, let's cover the differences: the actual functionality of each method. The first few bits of the algorithmic specification for the two methods are still the same.

Let O be ToObject(this value).
ReturnIfAbrupt(O).
Let len be ToLength(Get(O, "length")).
ReturnIfAbrupt(len).

In the segment above, we create a local copy of the array that the filter method has been invoked on and retrieve the length of the input array. For both values, we return from the filter method if they are undefined, null, empty, or 0. Next up:

If IsCallable(callbackfn) is false, throw a TypeError exception.

The callback function must be invokable, otherwise we are in trouble. The map method does the same check and you can bet that other array iteration methods do as well.

If thisArg was supplied, let T be thisArg; else let T be undefined.

The execution context, T, will be set to the value of thisArg and used as the value of this when this is used within the callback function. After this:

Let A be ArraySpeciesCreate(O, 0).
ReturnIfAbrupt(A).

ArraySpeciesCreate is a wonderfully whimsical name to use for the this function (technically, pseudocode). The spec outlines that this function will create new array from O with a size of 0. The interesting thing about this function is that it uses the constructor of the parameter array to construct the new array. The next two steps are stupendously succinct.

Let k be 0.
Let to be 0.

We'll need to initialize two values. k will be used to track our current position in the list. to will be used to track our position in the new list. Once we've initialized these values, we begin our iteration through the array:

Repeat, while k < len
Let Pk be ToString(k).
Let kPresent be HasProperty(O, Pk).
ReturnIfAbrupt(kPresent).
If kPresent is true, then

We validate our position in the list by checking if the index that we are currently on (k) is indeed in the list. You've probably written a similar check in your own iteration code, but the above is what it looks like in psuedocode. After we've validated the index...

Let kValue be Get(O, Pk).
ReturnIfAbrupt(kValue).
Let selected be ToBoolean(Call(callbackfn, T, «kValue, k, O»)).
ReturnIfAbrupt(selected).

We use the key (index) to get the value in the input object. We invoke the callback, giving it the object and the execution context then set the return value to selected.

If selected is true, then
Let status be CreateDataPropertyOrThrow (A, ToString(to), kValue).
ReturnIfAbrupt(status).
Increase to by 1.
Increase k by 1.
Return A.

If selected is true, we copy the value to the new array using CreateDataPropertyOrThrow. This function is expected to return a status indicator that we can use to return out of this method if there was a probably when copying the values over to the new array. After that, we increment the values as necessary so we can move on to the next item in the list. Once we've iterated through all items, we return the new array (Return A).

Reading through the specifications for each method, you get a real sense of the commonalities between map and filter (and I bet other array methods if we continued this exploration.)

  1. Input validation happens in the same way across both.
  2. The this context is set in the callback function in a similar fashion.
  3. Changes to the input array won't be visible to the callback function passed to each method once it has been invoked.

Latest comments (0)