1

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?

2
  • 2
    Regarding your update, that's a valid solution, but is a little different from the original. It depends on the actual use. If all you want is to create an ascending or descending function, then I wouldn't bother with a constructor. If you intend to store other properties on an object, then you may want one. Commented Oct 27, 2012 at 20:34
  • 1
    Regarding you "Is there a better way?" question, the answer is it depends. Your update (and my answer) rely on the being explicit about the context of this at the point of sorting at the expense of duplicating references to ns. user1689607 has probably a nicer approach which says that "The sort method inside my nsort object will always interpret this on the nsort object containing the called function." Don't you love these JavaScript context problems? :) Commented Oct 27, 2012 at 20:41

3 Answers 3

3

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);
}
7
  • 1
    +1 for being first and giving a good explanation. I was going to delete my answer since you were first, but left it in because we are using bind in different places. Commented Oct 27, 2012 at 20:33
  • I guess instead of var self=this; I could simply use var desc = true; Is there any drawback to that? Commented Oct 27, 2012 at 20:51
  • @MilanBabuškov: Not at all. It all depends on your actual use. But if you do that, there doesn't seem to be much use for a constructor function. In fact I would think you'd just create two separate functions and use them when you want to ascend or descend. Commented Oct 27, 2012 at 20:56
  • As I wrote, it is not only asc/desc, that's just simplified. In reality the parameter is an integer which is used for some complex if/else rules. So, there are not only 2 functions, but rather 20+ different functions. Commented Oct 27, 2012 at 22:45
  • 1
    @MilanBabuškov: Here's an example of using a function factory to create custom sorters where the only difference is the integer you provide. Commented Oct 27, 2012 at 23:18
1

You need to use bind. See http://jsfiddle.net/fub49/.

var ar = [ 10, 3, 5, 17 ];
var ns = new nsort();
ar.sort(ns.sort.bind(ns));
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;
    }
}

In a nutshell this says "use the sorting function ns.sort but call it so that internally that function uses ns for the value of the this expression.

1

In stead of bind you could also use this pattern (using a closure):

function nsort(desc)
{
   desc = desc || false;
   this.setDesc = function(val){desc = val; return this;}
   this.sort = function(a,b) {
        return desc ? b-a : a-b;
   }
}
var ar = [ 10, 3, 5, 17 ]
   ,ns = new nsort();
ar.sort(ns.sort);
console.log(ar); //=> [ 3, 5, 10, 17 ]
ar.sort(ns.setDesc(true).sort);
console.log(ar); //=> [ 17, 10, 5, 3 ]

Just for fun, here's a simplification, more 'functional' and avoiding the calling context problem:

function nsort(desc)
{
  return function(a,b){
       return desc ? b-a : a-b;
  };
}
var ar = [ 10, 3, 5, 17 ];
console.log( ar.sort(nsort()) );     //=> [ 3, 5, 10, 17 ]
console.log( ar.sort(nsort(true)) ); //=> [ 17, 10, 5, 3 ]
1
  • Thanks, this is exactly what I discovered while answering comments for accepted answer. "var" is valid approach, but would require a setter function. I wish I could accept more that one answer sometimes ;) Commented Oct 27, 2012 at 22:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.