JavaScript flood fill I tried to implement a very simple color flood fill to experiment a bit with Javascript. All comments about the code are welcome but I am especially interested in performance as it seems to be a bit slow. The browser tools only tell me that there is apparently a bunch of minor garbage collecting going on.



function setPixelColor(cvs, data, row, col, color) {
  const [red, green, blue] = color;
  data[4 * (row * cvs.width + col)] = red;
  data[4 * (row * cvs.width + col) + 1] = green;
  data[4 * (row * cvs.width + col) + 2] = blue;
  data[4 * (row * cvs.width + col) + 3] = 255;
}

function getPixelColor(cvs, data, row, col) {
  return [data[4 * (row * cvs.width + col)],
          data[4 * (row * cvs.width + col) + 1],
          data[4 * (row * cvs.width + col) + 2]];
}

function arr_eq(arr1, arr2) {
  if(arr1.length != arr2.length) {
    return false
  }

  for(let i=0; i&lt;arr1.length; i++) {
    if(arr1[i] !== arr2[i]) {
      return false
    }
  }

  return true;
}

(function () {
  const cvs = document.getElementById("paint");
  const ctx = cvs.getContext('2d');

  // black background
  ctx.fillStyle='black';
  ctx.fillRect(0, 0, cvs.width, cvs.height);

  const imageData = ctx.getImageData(0, 0, cvs.width, cvs.height);
  const data = imageData.data;

  const start = [[40, 40, [255, 0, 0]],
                 [10, 20, [0, 255, 0]],
                 [23, 42, [0, 0, 255]],
                 [300, 333, [255, 255, 0]],
                 [200, 333, [255, 0, 255]]];

  for(point of start) {
    let [r, c, v] = point;
    setPixelColor(cvs, data, r, c, v);
  }

  let queue = start.slice(0); // clone

  while(queue.length &gt; 0) {
    p = queue.shift();
    let [r,c] = p;
    const vcur = getPixelColor(cvs, data, r, c);
    for (const n of [[r+1,c], [r-1,c], [r,c+1], [r,c-1]]) {
      const [rn, cn] = n;
      if (rn &gt;= 0 &amp;&amp; rn &lt; cvs.height &amp;&amp; cn &gt;= 0 &amp;&amp; cn &lt; cvs.width) {
        const vn = getPixelColor(cvs, data, rn, cn);
        if (arr_eq(vn, [0, 0, 0])) {
          setPixelColor(cvs, data, rn, cn, vcur);
          queue.push(n);
        }
      }
    }
  }

  for(point of start) {
    let [r, c, v] = point;
    setPixelColor(cvs, data, r, c, [0, 0, 0]);
  }

  ctx.putImageData(imageData, 0, 0);
})();
&lt;!doctype html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="utf-8"&gt;
    &lt;title&gt;test&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
  &lt;canvas id="paint" width=500px height=500px /&gt;
  &lt;script src="test.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
Performant code requires a different style.
When you are working with a lot of data, or need it done fast, it pays to optimise everything. That means that you have to forgo many of the idiomatic coding styles that have come around since ES6.
Some points regarding your code

Don't repeat calculations. Eg you calculate the pixel chanel index 4 times in setPixelColor. Should be once.
Destructuring is SLOW, I mean forever slow, until browsers get round to making destructuring as performant as standard assignment dont use it in performance code.
Move frequent code and variables close to the scope you use the, You have setPixelColor and getPixelColor outside the function, thus it takes extra time to locate these functions.
Most devices are 64bit these days, and low end is 32 bits. Moving a byte takes just as long as moving a 4 bytes as a 32bit long or 8 bytes as 64bit long. Create a 32bit typed array so you can move a pixel in a quarter of the time.
Use 32Bit variables to store colors (eg [255, 0, 0] is 0x0000FF (NOTE 32bit pixels are backwards ABGR the opposite to CSS colors RGBA))
The source of your GC overload is the line for (const n of [[r+1,c], [r-1,c], [r,c+1], [r,c-1]]) { You create an array that contains 4 populated array. A million pixels in a image is small fry, but 5 million arrays is crazy.
Avoid getting values from getters, eg cvs.width is likely a getter. Get the value and store it in a variable scoped as close as possible to where you use it.
You work in rows, and columns (x,y) but the pixel data is a single array. Index directly when you can.
Use local scope, rather than pass variables if you must call functions inside performance loops..
If you can avoid calling functions, its quicker inline.
Use lookup tables to reduce math calculations.
Run in strict mode as it give a slight performance increase, and would also have spotted the undeclared variable p. Because you did not declare it, it becomes a global, and thus access would be much slower than a local. As it is in the heart of the function this will cost you many CPU cycles.

Example
The following does it all in about 1/4 of the time.
See comments for info relating to above comments.


(function () {
    "use strict"; // Always for performant code

    // Colors as 32bit unsigned ints. Order ABGR
    const black = 0xFF000000;
    const red = 0xFF0000FF;
    const green = 0xFF00FF00;
    const blue = 0xFFFF0000;
    const yellow = red | green;
    const magenta = red | blue;

    const cvs = document.getElementById("paint");
    const width = cvs.width;  // Get potencial slow accessors into vars.
    const w = cvs.width;  // alias
    const height = cvs.height;
    const size = width * height;
    const ctx = cvs.getContext('2d');

    // black background
    ctx.fillStyle = "black";
    ctx.fillRect(0, 0, width, height);

    const imageData = ctx.getImageData(0, 0, width, height);

    // Use 32bit buffer for single pixel read writes
    const d32 = new Uint32Array(imageData.data.buffer);  

    const start = [
        [40 * w + 40, red],  // work in precalculated pixel indexes
        [10 * w + 20, green],
        [23 * w + 42, blue],
        [300 * w +333, yellow],
        [200 * w + 333, magenta]
    ];
    const pixOff = [w, -w, 1, -1];  // lookup for pixels left right top bottom
    const pixOffX = [0, 0, 1, -1];  // lookup for pixel x left right

    const queue = [];  // keep queue content as simple as possible.
    for (const pixel of start) { 
        queue.push(pixel[0]);     // Populate the queue
        d32[pixel[0]] = pixel[1]; // Write pixel directly to buffer
    }
    
    while (queue.length) {
        const idx = queue.shift();
        const x = idx % w; // Need the x coord for bounds test
        for (let i = 0; i&lt; pixOff.length; i++) {
            const nIdx = idx + pixOff[i]; 
            if (d32[nIdx] === black) {   // Pixels off top and bottom 
                                         // will return undefined
                const xx = x + pixOffX[i];
                if (xx &gt; -1 &amp;&amp; xx &lt; w ) {
                    d32[nIdx] = d32[idx];
                    queue.push(nIdx);
                }
            }
        }
     }
     for (const pixel of start) { d32[pixel[0]] = pixel[1] }
     ctx.putImageData(imageData, 0, 0);
})();
&lt;canvas id="paint" width=500px height=500px /&gt;