delete(value) {

        // special case: the tree is empty, just exit
        if (this[root] === null) {
            return;
        }
        
        /*
         * The `found` variable keeps track of whether or not the value has
         * been found in the tree.
         */
        let found = false;

        /*
         * These two variables keep track of the location during traversal.
         * The `current` variable is the node we have traversed to while the
         * `parent` variable is the parent node of `current`. Unlike with other
         * traversals, we need to keep track of the parent so we can remove
         * the child node.
         */
        let current = this[root],
            parent = null; 

        /*
         * The first step is to do a search for the value to remove. This is
         * the same algorithm as the `has()` method, with the difference being
         * that the parent is also tracked.
         */
        while (!found && current !== null) {

            // if the value is less than the current node's, go left
            if (value < current.value) {
                parent = current;
                current = current.left;

            // if the value is greater than the current node's, go right
            } else if (value > current.value) {
                parent = current;
                current = current.right;

            // values are equal, found it!
            } else {
                found = true;
            }
        }

        // if the value wasn't found, just exit
        if (!found) {
            return;
        }

        /*
         * If we make it to here, the `nodeToRemove` variable continues the node
         * to remove. This assignment isn't necessary but makes it easier to
         * figure out what's going on in the code below.
         */
        const nodeToRemove = current;

        /*
         * The `replacement` variable is filled with what should replace
         * `nodeToRemove`. It starts out set to `null` but can change based
         * on what we find later.
         */
        let replacement = null;

        /*
         * The most complicated case is when the `nodeToRemove` node has both a left
         * and a right child. In that case, we need to move things around to 
         * ensure the tree remains properly structured.
         */
        if ((nodeToRemove.left !== null) && (nodeToRemove.right !== null)) {

            /*
             * We need to find the best replacement for the removed node by
             * traversing the subtrees. To start, we assume that the best
             * replacement is `nodeToRemove.left`.
             */
            replacement = nodeToRemove.left;

            /*
             * We need to keep track of the replacement's parent to modify
             * the subtree as we go.
             */
            let replacementParent = nodeToRemove;

            /*
             * The best replacement is found by traversing the right subtree
             * of `replacement`. The rightmost node in this subtree is the
             * largest value in `nodeToRemove`'s left subtree and so is the
             * easiest one to use as a replacement to minimize the number of
             * modifications we need to do.
             */
            while (replacement.right !== null) {
                replacementParent = replacement;
                replacement = replacement.right;
            }


            /*
             * Because `replacement` has no right subtree, we can copy over the
             * `nodeToRemove`'s right subtree directly.
             */
            replacement.right = nodeToRemove.right;

            /*
             * Special case: if `nodeToRemove.left` doesn't have a right subtree,
             * then `replacementParent` will be equal to `nodeToRemove`. In that
             * case, we should not make any further changes. Otherwise, we need
             * to rearrange some more nodes.
             */
            if (replacementParent !== nodeToRemove) {
                
                /*
                 * Remove `replacement` from its current location and replace it
                 * with `replacement.left` (we know `replacement.right` is `null`).
                 * It's possible that `replacement.left` is `null`, but that doesn't
                 * matter. Both `null` and non-`null` values keep the tree intact.
                 */
                replacementParent.right = replacement.left;
    
                /*
                 * Assign the complete left subtree of `nodeToRemove` to
                 * `replacement` to maintain the proper structure.
                 */
                replacement.left = nodeToRemove.left;
            }

        } else if (nodeToRemove.left !== null) {
            replacement = nodeToRemove.left;
        } else if (nodeToRemove.right !== null) {
            replacement = nodeToRemove.right;
        }

        /*
         * If the `nodeToRemove` node has no children, then the default value of
         * `null` is used for `replacement`.
         */

        // special case: the `nodeToRemove` node is the root
        if (nodeToRemove === this[root]) {
            this[root] = replacement;
        } else {
            
            if (nodeToRemove.value < parent.value) {
                parent.left = replacement;
            } else {
                parent.right = replacement;
            }

        }



    }
Deletes the value from the tree.
@param {int} index The zero-based index of the item to remove.
@returns {*} The value in the given position in the tree.
@throws {RangeError} If index is out of range.