While researching binary tree implementations in JavaScript, I ran across Marijn Haverbeke's implementation from the first edition of Eloquent JavaScript. What I especially like about this implementation is the use of sequential representation of a binary tree.

I re-factored (using newer syntax, as the first edition is pre-ES6) and annotated:

1
function BinaryHeap(compare) {
2
  this.content = [];
3
  this.compare = compare;
4
}
5
6
BinaryHeap.prototype = {
7
  push: function(element) {
8
    this.content.push(element);
9
    this.bubbleUp(this.content.length - 1);
10
  },
11
12
  pop: function() {
13
    // Remove the first element from the tree,
14
    // and store it to remove it.
15
    let result = this.content.shift();
16
17
    // When removing an element from a minHeap, 
18
    // we remove the last element, place it at 
19
    // the top, then allow it to sink down the
20
    // tree.
21
    if (this.content.length) {
22
      this.content.unshift(this.content.pop());
23
      this.sinkDown(0);
24
    }
25
26
    return result;
27
  },
28
29
  peek: function() {
30
    return this.content[0];
31
  },
32
33
  size: function() {
34
    return this.content.length;
35
  },
36
37
  bubbleUp: function(index) {
38
    // We now iterate through the tree, which is represented here as an array.
39
    // (See: https://www.geeksforgeeks.org/binary-tree-array-implementation/)
40
    //
41
    // To do so with a zero-indexed tree, which looks like this:
42
    //
43
    //.        A(0)    
44
    //.      /   \
45
    //.     B(1)  C(2)  
46
    //.   /   \      \
47
    //.  D(3)  E(4)   F(5) 
48
    //
49
    // Then, with parent P, then the left child is (2*P)+1, and the right 
50
    // child is (2*P)+2;
51
    //
52
    // Thus, the left child of B is:
53
    //
54
    // (2 * 1) + 1 = 3.
55
    //
56
    // While the right child is:
57
    // 
58
    // (2 * 1)+ 2 = 4.
59
60
    while (index > 0) {
61
      let valueToInsert = this.content[index];
62
63
      // To calculate the parent of a node, say the parent of node E
64
      // in the example above, we can do the following calculation:
65
      //
66
      // Math.floor((E + 1) / 2) - 1 = 
67
      // Math.floor(5 / 2) - 1 =
68
      // 2 - 1 = 1
69
      // 
70
      // Similarly, to calculate the parent of F, we do the following:
71
      // 
72
      // Math.floor((F + 1) / 2) - 1 =
73
      // 3 - 1 = 2
74
75
      let parentNodeIndex = Math.floor((index + 1) / 2) - 1;
76
77
      if (this.compare(
78
        this.content[parentNodeIndex], 
79
        this.content[index])) {
80
        
81
        // If the child node's value is (for a minHeap) greater than the parent node,
82
        // we're done, e.g. the parent node value must be less than
83
        break;
84
        
85
      } else {
86
        // Otherwise, we're going to need to swap the two values.
87
        // this.swap(this.content[index], this.content[parentNodeIndex])
88
        let tmp = this.content[parentNodeIndex];
89
        this.content[parentNodeIndex] = valueToInsert;
90
        this.content[index] = tmp;
91
92
        // We're simply climbing up the binary tree
93
        // here, comparing each node with its parent.
94
        index = parentNodeIndex;
95
      }
96
    }
97
  },
98
99
  sinkDown: function(index) {
100
    let length = this.content.length;
101
102
    // we'll need to sink down the value at the index
103
    // the function is given.
104
    let valueToInsert = this.content[index];
105
106
    // We now traverse the tree downwards.
107
    while (true) {  
108
      let rightChildIndex = (index + 1) * 2;
109
      let leftChildIndex = rightChildIndex - 1;
110
      let swapIndex = null;
111
112
      // see if the left child exists.
113
      if (leftChildIndex < length) {
114
        // If so, if the left child's value is (for a minHeap) 
115
        // less than the index's value, prepare to swap the two.
116
        if (this.compare(
117
          this.content[leftChildIndex], 
118
          this.content[index])) 
119
            swapIndex = leftChildIndex;
120
      }
121
122
      // see if the right child exists.
123
      if (rightChildIndex < length) {
124
        // If the right element is (for a minHeap) lower than the left element (and 
125
        // as a function of the logic, lower than the parent index) then swap with the parent
126
        // index.
127
        if (this.compare(this.content[rightChildIndex], 
128
          (swapIndex === null ? this.content[index] : this.content[leftChildIndex]))) {
129
          swapIndex = rightChildIndex;
130
        }
131
      }
132
133
      // if there's nothing to swap, break.
134
      if (swapIndex === null) break;
135
136
      // The value with either the parent, or other child.
137
      this.content[index] = this.content[swapIndex];
138
            this.content[swapIndex] = valueToInsert;
139
140
      // Now, do the same process on the same value, but in the position of the
141
      // node with which we swapped.
142
      index = swapIndex;
143
      }
144
    }
145
  }
146
147
var minHeap = new BinaryHeap((a, b) => a <= b);
148
([10, 3, 4, 8, 2, 9, 7, 1, 2, 6, 5].forEach((x) => minHeap.push(x)));
149
while (minHeap.content.length) console.log(minHeap.pop());
150
151
var maxHeap = new BinaryHeap((a, b) => a >= b);
152
([10, 3, 4, 8, 2, 9, 7, 1, 2, 6, 5].forEach((x) => maxHeap.push(x)));
153
while (maxHeap.content.length) console.log(maxHeap.pop());