OVERRIDING QUIRKS IN JS
Generated using LinkedIn's editor

OVERRIDING QUIRKS IN JS

JavaScript provides an arguments object inside any non-arrow function and it provides the list of arguments that were used to call the function.

While this allows us to handle a variable number of arguments in our function, we can use rest parameters to the same effect in a less convoluted way.

Where the arguments object comes in uniquely useful is when we want to handle optional parameters in a way that default parameters cannot.

The Case of Array Splice

The array splice method can do a number of things, one of which is deleting items.

The way the deleteCount parameter works cannot be implemented using default parameters because if we do not pass a value then all items from the start index onwards are removed. If we do pass undefined for deleteCount then that is interpreted as 0 (zero) and no items are deleted.

If we were to implement an array-like object we would not be able to implement our own splice method using default parameters, but instead we would need to handle the different cases by inspecting the arguments object.

class MyArrayLike {
    // ...
    splice(start, deleteCount, ...newItems) {
        const resolvedDeleteCount = (
            arguments.length === 1
                ? this.length
                : (Number(deleteCount) || 0)
        );
        // ...
    }
}        

The snippet above will check whether the collection length is the resolved value that should be used, when deleteCount is not provided, or fallback to 0 (zero) for invalid numbers.

Inheritance and Overriding

So far, this is great, however in some cases we may need to extend our array-like implementation and change a little bit what splice does. Following the usual pattern, we would just override the method and call that base one if we need it.

class MySpecificArrayLike extends MyArrayLike {
    // ...
    splice(start, deleteCount, ...newItems) {
        // ...
        super.splice(start, deleteCount, ...newItems);
    }
}        

No problem here, right? Well, it turns out there is one. The problem of deleteCount manifests itself in a different way this time.

We provide all arguments when calling the base method, we provide a value for deleteCount as well even when we call the override without it. In other words, our call to the base method is not consistent.

To resolve this, we need to call the base method and provide the exact same arguments. Thus, the way our overridden method was called is preserved in its entirety making our code truly polymorphic.

class MySpecificArrayLike extends MyArrayLike {
    // ...
    splice(start, deleteCount, ...newItems) {
        // ...
        super.splice.apply(this, arguments);
    }
}        

Closing Thoughts

I've encountered this issue while working on the observable collections in React MVVM. Read-only observable collections implement all methods, but the mutating ones like splice are marked as protected . An observable collection is defined as well, but all it does is expose the mutating methods as public and thus can be used similarly to how an array is used.

This approach allows you to have your own custom observable collections, controlling how items are added or removed, while reusing the entire implementation.

Having found this solution, it is neat and it may actually be the proper way to override methods in JavaScript as it maintains the way the override was called when having to call the base method.

The cases presented are not very common to be fair, it's the first time I run into this and it is only because I wanted to provide an observable collection that has a very similar interface with an array so its usage will not feel as strange and be seamless.

I do think that we should generally use default parameters when this is the case rather than check the arguments object, however when we extend types it might be best to apply the arguments when wanting to call a base method just to be safe as it works in all cases.

References

要查看或添加评论,请登录

Andrei Fangli的更多文章

  • Dotnet Single Project Approach

    Dotnet Single Project Approach

    This is a little experiment I want to try, over the years what I noticed is that the general approach in dotnet is to…

  • Iterators, Visitors and Selectors

    Iterators, Visitors and Selectors

    The Iterator and Visitor design patterns are generally used to traverse collections or handle object tree navigation…

  • Model-View-ViewModel in ReactJS

    Model-View-ViewModel in ReactJS

    In my previous article, MVVM to Flux and Back Again, I wrote about my journey with Model-View-ViewModel (MVVM) in .NET…

  • MVVM to Flux and Back again

    MVVM to Flux and Back again

    If you are only curious about my conclusions about how MVVM can be better applied for JavaScript applications (mainly…

社区洞察