46

The description of jQuery.unique() states:

Sorts an array of DOM elements, in place, with the duplicates removed. Note that this only works on arrays of DOM elements, not strings or numbers.

With the description in mind, can someone explain why the code below works?

<div></div>
<div></div>​

var arr = ['foo', 'bar', 'bar'];

$.each(arr, function(i, value){
    $('div').eq(0).append(value + ' ');
});

$.each($.unique(arr), function(i, value){
    $('div').eq(1).append(value  + ' ');
});
​

http://jsfiddle.net/essX2/

Thanks

Edit: Possible solution:

function unique(arr) {
var i,
    len = arr.length,
    out = [],
    obj = { };

for (i = 0; i < len; i++) {
    obj[arr[i]] = 0;
}
for (i in obj) {
    out.push(i);
}
return out;
};
Johan
  • 35,120
  • 54
  • 178
  • 293
  • 3
    I guess it just "happens to work" here, but it should not be trusted to work in other situations than described in the docs. – m90 Apr 17 '12 at 13:15
  • `var arr = ['bar', 'bar', 'foo', 'foo', 'bar', 'bar', 'foo', 'bar'];` this array will contain duplicates – Fabrizio Calderan Apr 17 '12 at 13:19
  • @Johan use an object and it will: http://jsfiddle.net/essX2/2/ – m90 Apr 17 '12 at 13:19
  • @F.Calderan And my example still works – Johan Apr 17 '12 at 13:20
  • @m90 That wont be a problem in this case though... – Johan Apr 17 '12 at 13:20
  • @m90 I don't think it can be said to "work" here at all, since any time I pass in an array of strings I get it back with the duplicates removed, but the array isn't sorted. – Blazemonger Apr 17 '12 at 13:21
  • 1
    @Johan yet it shows that it isn't designed for other uses and *might* fail. If you are looking for something more robust have you considered using underscore.js? – m90 Apr 17 '12 at 13:21
  • 1
    An array is not a string - or is it? See the section on unique in Paul Irish's article here: http://paulirish.com/2010/duck-punching-with-jquery/ There is a modification to unique. – Jay Blanchard Apr 17 '12 at 13:22
  • @mblase75 Yes, it's more like "working" – m90 Apr 17 '12 at 13:22
  • @Johan I'd go with your edited answer – vol7ron Mar 11 '13 at 19:26
  • @vol7ron Glad it helped you out, but the question is about why the `$.unique` worked for me even though it shouldn't, so I'll stick with gnarf's answer. – Johan Mar 11 '13 at 19:53
  • @Johan: Just saying, I'd go with a variation of populating an object, perhaps setting the value to an array and pushing an object/variable-value into that array. --- Gnarf does a good job at explaining the reason, but your edit says "Possible solution", which I don't understand how it could be a possible solution if your question was about why the `$.unique` worked even though it shouldn't --- my comment was in regards to that. – vol7ron Mar 11 '13 at 21:11
  • @vol7ron Ok. Yea well it's a solution for my problem in the thread, since $.unique wasn't meant to be used in that case. – Johan Mar 11 '13 at 21:59
  • Prevent duplicate and exist other topic, check http://stackoverflow.com/a/12551652/2777092 – KingRider Mar 29 '17 at 12:42

9 Answers9

105

Although it works, you should probably take into consideration the function description. If the creators say that it is not designed for filtering arrays of anything else than dom elements, you should probably listen to them.
Besides, this functionality is quite easy to be reproduced :

function unique(array){
    return array.filter(function(el, index, arr) {
        return index === arr.indexOf(el);
    });
}

(demo page)

Update:

In order for this code to work in all browsers (including ie7 that doesn't support some array features - such as indexOf or filter), here's a rewrite using jquery functionalities :

  • use $.grep instead of Array.filter
  • use $.inArray instead of Array.indexOf

Now here's how the translated code should look like:

function unique(array) {
    return $.grep(array, function(el, index) {
        return index === $.inArray(el, array);
    });
}

(demo page)

gion_13
  • 41,171
  • 10
  • 96
  • 108
  • Thanks for the code snippet. `indexOf` doesnt work on arrays in ie7 if im not misstaken. I need it to work there as well. Ive updated my question with another possible solution – Johan Apr 18 '12 at 07:53
  • 1
    You can use jquery's `inArray`S instead of `indexOf`. See the update please. – gion_13 Apr 18 '12 at 08:44
  • @Jonah You were right.. I updated my answer again, because I fell in the same trap. ie7 and lower doesn't support `Array.filter` too :)). See the update. – gion_13 Apr 18 '12 at 08:53
  • This uses loose equality `==`. lodash uses strict equality `===` and PHP uses a hybrid form by casting to strings first. Might I recommend including an optional `strict` argument? – zamnuts Nov 07 '14 at 08:52
  • well, you could use the strict equality operator (`===`), but in these cases, both of the operands are always numeric, so it's won't make any significant difference, but anyway, thanks for the feedback. I will update the answer to use strict equality, because.. that's the way I should've written the code 2 years ago anyway:) – gion_13 Nov 07 '14 at 19:20
23

It might work on an array strings, etc, but it has not been designed for that use...

Notice that the code for unique() is hiding in Sizzle as uniqueSort: github source

While some of that extra code might seem like it would work on any array, pay close attention to sortOrder as defined here. It does a lot of extra work to put things in "document order" - hence why the documentation states that it should only be used on arrays of DOM elements.

gnarf
  • 105,192
  • 25
  • 127
  • 161
  • NOTE: "As of jQuery 3.0, jQuery.unique() is deprecated and just an alias of jQuery.uniqueSort(). Please use that method instead." – thdoan Apr 01 '16 at 07:21
15

I know unique works with DOM but this WORKS on arrays of int:

$.unique(arr.sort());
evclid
  • 171
  • 1
  • 4
9

If not limited using jQuery, consider to use Set from ES6.

var arr = ['foo', 'bar', 'bar'];
Array.from(new Set(arr)); // #=> ["foo", "bar"]

Working for Firefox 45, Safari 9 and Chrome 49.

xjlin0
  • 341
  • 5
  • 10
4

$.unique will remove duplicate DOM elements, not identical DOM elements. When you try to use it on strings, you get unpredictable behavior and the sorting will (probably) fail.

It's a function intended for internal use by jQuery only, and won't be useful to mere mortals like you and I.

Blazemonger
  • 90,923
  • 26
  • 142
  • 180
  • I'm saying that `$.unique` is explicitly documented as accepting an array of **DOM elements**. Any attempts to use it on an array of **strings** is technically wrong, and will lead to undocumented and unpredictable behavior. – Blazemonger Apr 17 '12 at 13:19
3

There's a quick way to extend the jQuery.unique() function to work on arrays containing elements of any type.

(function($){

    var _old = $.unique;

    $.unique = function(arr){

        // do the default behavior only if we got an array of elements
        if (!!arr[0].nodeType){
            return _old.apply(this,arguments);
        } else {
            // reduce the array to contain no dupes via grep/inArray
            return $.grep(arr,function(v,k){
                return $.inArray(v,arr) === k;
            });
        }
    };
})(jQuery);

// in use..
var arr = ['first',7,true,2,7,true,'last','last'];
$.unique(arr); // ["first", 7, true, 2, "last"]

var arr = [1,2,3,4,5,4,3,2,1];
$.unique(arr); // [1, 2, 3, 4, 5]

http://www.paulirish.com/2010/duck-punching-with-jquery/ - example #2

davegaeddert
  • 3,139
  • 1
  • 21
  • 20
0

$.unique will only remove duplicate DOM element, if you need it for array :

var result=[] ;
$.each([12,1,2,4,3,1,4,3,3,2,111], 
        function(i,e){ if($.inArray(e,result)===-1) result.push(e) ;});
result;
brotherol
  • 361
  • 2
  • 6
0
var array=['a','b','c','a'];

    function unique(array)
    {
    var unique_arr=[];
    array.forEach(function(i,e)
    {
    if(unique_arr.indexOf(i)===-1) unique_arr.push(i);
    });
    return unique_arr;
    }
    console.log(unique(array));
Mainak Ray
  • 69
  • 1
  • 6
0

OK, so without Sizzle a shorthand for jQuery will be appreciate!

Based on the gion_13 answer, I ve just rewrite to make a native Array implementation.

Array.prototype.unique = function() { 
    let _array = this;
    return $.grep(_array, function(el, index) {
        return index === $.inArray(el, _array);
    });
};

Edited to be consistent ES shorter...

Array.prototype.unique=function(){let _array=this;return $.grep(_array,function(el, index){return index===$.inArray(el,_array);});};

In this case test done:

var A = ['1', 'green', true, 'blue', 1, (!false), 'toto', 'green', (!!0) ];

console.log( A );
// Array(9) [ "1", "green", true, "blue", 1, true, "toto", "green", false ]

console.log( A.unique() );
//Array(7) [ "1", "green", true, "blue", 1, "toto", false ]

Note about evclid that $.unique(arr.sort()); works fine but we lose order (:

EsseBé
  • 41
  • 3