Search code examples
javascriptnode.jssortingv8

Injecting object instead of function for Javascript array sort


I'm trying to sort an array of objects in javascipt. Sort order itself depends on two factors: object's properties and a value of a single external variable. I know that I can use array.sort(sortFunction) to inject custom function, but I need to use a different function depending on the external variable (which is fixed during sorting).

I reduced the sorting to minimal test case which shows the problem and trying to inject "descending/ascending" parameter, although the real prolem is much more complex. I know I can reverse() the result, but that's not an issue here.

Here's the code:

var ar = [ 10, 3, 5, 17 ];
var ns = new nsort();
ar.sort(ns.sort);
console.log(ar);

function nsort()
{
    this.desc = true;
    this.sort = function(a,b) {
        console.log(this.desc);
        if (this.desc)
            return b-a;
        else
            return a-b;
    }
}

and here's the output (using Node.js)

undefined
undefined
undefined
undefined
[ 3, 5, 10, 17 ]

It looks like array sorting code extracts the function outside of parent object while sorting.

Any ideas why is this happening and what would be the proper way to do it?

Update: I did find a way to make it work:

var ar = [ 10, 3, 5, 17 ];
var ns = new nsort(true);
ar.sort(ns.sort);
console.log(ar);

function nsort(desc)
{
    this.sort = function(a,b) {
        if (desc)
            return b-a;
        else
            return a-b;
    }
}

Is this a good idea? Or is there something better?


Solution

  • Your ns.sort is losing its calling context. In other words, once you pass it to .sort(), it no longer has any connection to the ns object.

    You can have the function reference the object directly by making a variable in the constructor point to it, and having the sort function reference the variable.

    function nsort()
    {
        this.desc = true;
        var self = this;
        this.sort = function(a,b) {
            console.log(self.desc);
            if (self.desc)
                return b-a;
            else
                return a-b;
        }
    }
    

    Or if you're in a modern environment like NodeJS, you can use Function.prototype.bind() to permanently affix the calling context value to the function.

    function nsort()
    {
        this.desc = true;
        this.sort = function(a,b) {
            console.log(this.desc);
            if (this.desc)
                return b-a;
            else
                return a-b;
        }.bind(this);
    }