5

I've got the following example.

<div class="parent">
  <div data-id="5"></div>
  <div data-id="2"></div>
  <div data-id="3"></div>
  <div data-id="1"></div>
  <div data-id="4"></div>
</div>

If I want to order these div's in an ascending order (1,2,3,4,5). I would normally do a loop and append the div's in order to the parent div. This will however mean I always make 5 changes to the dom (regardless of the order of the div's), one for each div.

You can however use the .insertBefore() method to order the div's correctly with only 2 changes!

5,2,3,1,4
Insert 1 before 2
5,1,2,3,4
Insert 5 behind 4 (using .insertBefore() and .nextSibling)
1,2,3,4,5 

Question 1 By making only 2 changes to the DOM I assume less reflow ocours, making the '2 change' sorting action faster than the '5 change'sorting action. Is this correct?

Question 2 What sort of method/algorithm would be able to figure out to only do the insert 1 before 2 and 5 behind 4?

Question 3 (bonus) Will this 'optimized' algorithm still be faster as the amount of items incease? Range 10 - 100 - 1.000 - 10.000 - 100.000

Maybe to clarify: I am not searching for a way to figure out the order (1,2,3,4,5) in the most optimal way. At a certain point I know the order but I want to compare the order agains the order of the div's and THEN figure out the least amount of operations.

Dex
  • 704
  • 2
  • 9
  • 22
  • i recommend reading further on the different sorting algorithms, the subject is very broad, there are many that are explained and tested for performance in a simple web search. Your question is kinda legit, but there might be lots of different answers – Kaddath Jul 26 '17 at 08:07
  • I tried to find some sorting algorithms but there are to many trees in the forrest :) I don't know what name this type of algorithm has. Do you? – Dex Jul 26 '17 at 08:13

3 Answers3

5

Be more confident in browsers’ capabilities.

  1. Browsers batch together DOM operations when they are performed in one single, synchronous execution of a JavaScript sequence. Exceptions occur when you explicitely (and sometimes unknowingly) request a reflow by accessing DOM properties/methods that require the DOM to be up-to-date, e.g. innerWidth, offsetTop, or getBoundingClientRect. See Rendering: Repaint, Reflow, Restyle for details.
    Deleting DOM nodes is not necessary. Add them to a newly created DocumentFragment, they’ll be automatically removed from their current position in the DOM.

  2. Browsers, more specifically JS engines, already know and use the smartest sorting algorithm. Save for very specific cases, you don’t need to know about the inner mechanisms of a sorting operation. Just use Array.prototype.sort, provide a custom function if necessary, and watch magic happen.

Watilin
  • 286
  • 3
  • 9
  • Thank you indeed the direction I was looking for! +1 – Dex Jul 26 '17 at 09:52
  • 2
    If you only intend to reorder elements you also have to mention the css `flex` layout. You can reorder elements without doing any DOM manipulations at all. https://developer.mozilla.org/en-US/docs/Web/CSS/order – Bellian Jul 28 '17 at 15:07
  • While I agree with the *attitude* of this answer ("Be more confident in browser capabilities") note that this does not work as a general solution to the problem of "least amount of operations", as highlighted in the problem statement. Merely reappending child nodes (whether directly or via `DocumentFragment`) can trigger reloading of iframes, cause CSS animations to restart, loss of text/input selections, and so on. – mindplay.dk Apr 04 '21 at 10:06
3

Remove all nodes from the DOM and replace them in the right order after you sorted them in your code.

HTML:

<div id="wrapper">
  <div id="par" class="parent">
    <div id="5">5</div>
    <div id="2">2</div>
    <div id="3">3</div>
    <div id="1">1</div>
    <div id="4">4</div>
  </div>
</div>

Javascript:

var clone = document.getElementById("par")
  .cloneNode(true)
var childs = [].slice.call(clone.children);
var sorted = childs.sort(function(a, b) {
  return parseInt(a.id) - parseInt(b.id)
})
var frag = document.createDocumentFragment();
sorted.forEach(function(el) {
  frag.appendChild(el)
})

var wrapper = document.getElementById("wrapper");
wrapper.removeChild(document.getElementById("par"));
wrapper.appendChild(frag);

Explanation: A single DOM manipulation is way heavier than an algorithmic step in your sorting algorithm.

If numbers get large, the smartest sorting algorithm takesO(n log n) times the amount of nodes. This means n*log(n) DOM manipulations. In human language, that is just a couple times more operations than the amount of nodes.

But if you simply remove all nodes, and then add them again in the right order, you get n + 1 DOM operations, at worst. Probably you can add all nodes together, and you will end up with a figure closer to 2 / O(1), but I'm not that specialised in how quickly this is actually done by modern browsers, so let's stay with n + 1, conservatively.

Now on to the numbers:

Say you have 5 nodes. In this case, everything is fine, numbers are small, but for your own peace, and the future peace of you and your colleagues, you want to write a clear algorithm. So delete all nodes and replace them in the right order.

Now say you have 1000 nodes. In this case, sorting will take you about 10,000 operations (n * log n = 1000 * 10) **. If you put each node in there separately, you will have 10,000 DOM manipulations.

Instead, if you just remove the nodes from the DOM, sort them in your javascript code, and then put them back in, you will only have 1000 DOM manipulations. This is quite conservative, because I have the feeling it actually just takes 2 DOM manipulations: one time removing all nodes and one time adding all nodes in the right order***

** I rather give hard figures just to make some sense of it all. Based on base 2 of 1024, which is 10 *** This is probably where the real difference lies. If people know, please comment / edit!

Sventies
  • 2,314
  • 1
  • 28
  • 44
  • I don't think I follow. It seems to me deleting the node entirely and rebuilding it 'costs' more than only providing it a new position (order). Appending them in the correct order would be faster than right? – Dex Jul 26 '17 at 08:10
  • 1
    I felt the same way when I started coding. I'm gonna try to add to this answer, to make it more understandable. Hold tight. – Sventies Jul 26 '17 at 08:11
  • Ok so if I understand correctly I delete all 5 divs. Create them again and then place them in oder under the `parent`? This still seems more than only placing them in order under the `parent` (not using the optimized insertBefore() stuff but .append() only). – Dex Jul 26 '17 at 08:22
  • Crazy right? The trick is, you, as a human, secretly know too much about where exactly to put this number. In reality, if you're applying a sorting algorithm on big numbers, you need memory to keep track of a stack of numbers. It seems you want to use the DOM for this, which is not encouraged. – Sventies Jul 26 '17 at 08:25
  • 2
    You dont have to recreate them. just append the elements to the parent in the order you want. – Bellian Jul 26 '17 at 08:28
  • Thanks @Bellian and Kaiido, I adjusted your comments in my answer. – Sventies Jul 26 '17 at 08:32
  • Ok so 1.000 nodes singting takes 10.000 operations? The order of the elements is 1.000,1,2,3,4,5,6,etc... Now to order this correctly I would only need to place number 1.000 at the end of my div. This is one operation not 10.000. – Dex Jul 26 '17 at 08:44
  • You're right, but how do you know beforehand? If you are positive that this is always the case, then you don't need a sorting algorithm at all. You simply remove the first node and add it to the end. But your original question suggests otherwise... – Sventies Jul 26 '17 at 08:47
  • I don't know this beforehand:) But I also don't need a sorting algorithm but an algorithm that can figure out what minimal changes to make to get the divs in a specific order (which was created by Array.prototype.sort). – Dex Jul 26 '17 at 08:53
  • Ok, I see your intent, now I still think it is better to remove all nodes and replace them, but I'll adjust my answer to it. – Sventies Jul 26 '17 at 08:58
  • Btw. Another way to go will be the `layout: flex` css property. You have to setup the parent element with `layout: flex; flex-direction: column;` and assign the `order` property to the children. This way you will have no DOM manipulation at all. Flex layout has many awesome applications ;) – Bellian Jul 28 '17 at 14:05
-1

Please take a look at this code.

Basically it removes the container from the dom, applies sorting, reinserts it into the dom.

Usually this all can happen within one frame redraw so it won't affect the length of the page/scroll issues

+function() {
  /**
   * Finding thing wrapping class
   */
  var $con = $('#container');
  /**
   * getting the parent of the wrapping class so we can reinsert when we're done.
   */ 
  var $parent = $con.parent();
  /**
   * Removing container from dom so we can sort in speed
   */   
  $con.remove();
  
  /**
   * Getting the things we need from container that we need to sort
   */
  var $tosort = $con.find('.thing');
  
  /**
   * Apply sorting algorything, get a newly sorted list
   */
  var $neworder = $tosort.sort(function(a,b){
     return parseInt(a.innerText) > parseInt(b.innerText);
  });
  /**
   * Simply append the newly ordered list. They are the same objects, not clones so they'll fit into the right order.
   */
  $con.append($neworder);
  /**
   * Reinsert container into the dom.
   */
  $parent.append($con);
}()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="wrap">
  <div id="container">
    <div class="thing">
      4
    </div>
    <div class="thing">
      3
    </div>
    <div class="thing">
      1
    </div>
    <div class="thing">
      5
    </div>
    <div class="thing">
      2
    </div>
  </div>
</div>
Tschallacka
  • 27,901
  • 14
  • 88
  • 133
  • Cool this seems to be an approche I was looking for in getting a better performance. Would I still get a significant performance gain using the insertBefore() method 2 times instead of the .sort running 5 times or O(5) times? – Dex Jul 26 '17 at 09:01
  • You're modifing stuff in one reflow call of the dom. The dom won't reflew untill the $parent.append($con) is called. How you optimise the sorting depends totally upon your data. But all that happens **outside** the dom, as if you were ordering raw data, because until you reinsert, it's all raw data. – Tschallacka Jul 26 '17 at 09:03
  • Just to be sure Im getting it right, "$tosort.sort(function(a,b){ return parseInt(a.innerText) > parseInt(b.innerText); });" basically takes one element, compare it to another, then repeat the process until every elements are sorted? – DevMoutarde Jul 26 '17 at 09:13
  • 1
    you can do whatever you want in that sort function. a and b are the raw html elements as if you had done `document.getElementById('something')` You can wrap them in jquery and access data attributes, you can compare their contents, whatever. it's up to your own actual implementation. You can read more about sort at the mdm docs. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort – Tschallacka Jul 26 '17 at 09:15
  • 1
    Ok, but I wish to know for that specific function that you have written, to understand the process I mean, cuz to my newbie eyes I wasn't sure what was going on. But ok I get the general purpose of it ! PS: ooh I actually hadnt read about the 'compareFunction' thing in MDN ! ahah I get it now – DevMoutarde Jul 26 '17 at 09:18
  • Best thing as a newbie to do is experiment. I usually either open a stack snippet here, or head to jsfiddle, punch out some code, just to see what it does so I can uderstand it. Don't be afraid to experiment. Also, breakpoints, `console.log()` and `console.table()` are your friends ;-) – Tschallacka Jul 26 '17 at 09:30
  • 1
    @revo I was looking for the method. Though I'll write the solution in vanilla Javascript the usage of jquery here is not really an issue... – Dex Jul 26 '17 at 10:55
  • This effectively moves every single element, whether it needed to be moved or not - so not the "least amount of operations", as asked (and illustrated by example) in the post. I also don't understand what removing and reinserting the parent is supposed to accomplish. All the DOM operations will performed in a single, blocking operation - so reflow and layout won't happen before your function returns either way. – mindplay.dk Apr 04 '21 at 09:51
  • @mindplay.dk By detaching it from the dom, you're essentially moving it into the ram, where you can perform all kinds of updates, without triggering dom updates, re-evaluations, etc... So sorting the data becomes a lot faster because there isn't a large event chain of the DOM document that needs to get triggered with updates, reflows, etc... By just inserting the modified parent element, you trigger only one document reflow. Granted, most browsers do this internally nowadays, but it can't be a given that you can depend on. – Tschallacka Apr 04 '21 at 10:12
  • @Tschallacka this is all theoretical. I recently benchmarked (and checked for correctness) all the well known algorithms from virtual DOM libraries - you can see the results and notes [here](https://github.com/luwes/sinuous/issues/160#issuecomment-821844355). If you're going to claim this can be faster, prove it by forking the benchmark - if you're right, there's a whole community of developers who would value that contribution. Personally, I think you'd be wasting your time. – mindplay.dk Aug 05 '21 at 06:40
  • 1
    @mindplay.dk You might be right. I hail from the times of Netscape and ie4, and best practises change over the course of 20 years as cpu's und graphic cards and code changes. I might need to update myself. I'll play around with that interesting test to "update" myself. – Tschallacka Aug 05 '21 at 06:46
  • 1
    @Tschallacka (I should also note here that detaching from the DOM means losing some state, such as focus and iframe content - which is why this doesn't work as a general optimization... as long as your table has no inputs, that might be fine - it just isn't a fully general approach.) – mindplay.dk Aug 05 '21 at 06:46