JavaScript/jQuery Image Slider I've just wrote this class for an image slider (it cross-fades rather than sliding, so a class rename may be beneficial). It's my first proper attempt at JavaScript OOP. Can anyone see any real issues with this?

// Wrapper is the container element, in this case $('#sliderWrap').
// images is an array of the images.

function ImageSlider(wrapper, images){

    // Elements.
    this.elem = wrapper;
    this.images = images;
    this.top = this.elem.find('#topImage');
    this.bottom = this.elem.find('#bottomImage');
    this.next = this.elem.find('#nextBtn');
    this.prev = this.elem.find('#prevBtn');


    this.lastImage = images.length - 1;
    this.currImage = 1; // This is 1 as the first iteration is rendered in the HTML.
    this.interval;
    this.fadeTime = 400;
    this.sliderDelay = 4000 + this.fadeTime;

    var self = this;

    this._init = function(){

        $(window).on('load', function(){
            self.elem.css('height', self.top.height());
            self.next.show();
            self.prev.show();
            self._animate();
            self._startInterval();
            self.next.click(function(){
                clearInterval(self.interval);
                self._animate(true);
                self._startInterval();
            });

            self.prev.click(function(){
                clearInterval(self.interval);
                self._animate(false);
                self._startInterval();
            });
        });
    }

    /**
     * @param next boolean. True next, false prev 
     */

    this._animate = function(next){
        if(typeof next == 'undefined') {
            next = true;
        }

        if (next) {
            self.nextImage();
        } else {
            self.prevImage();
        }
    }

    this.nextImage = function(){
        self.currImage++;


        this.top.fadeOut(self.fadeTime, function(){
            self.bottom.addClass('active');
            self.top.attr('src', self.images[self.currImage - 1]).show();

            if (self.currImage &gt; self.lastImage) {
                self.currImage = 0;
            }

            self.bottom.removeClass('active');

            self.bottom.attr('src', self.images[self.currImage]);
        });
    }

    this.prevImage = function(){

        var minusTwo = self.currImage - 2;

        switch (minusTwo){
            case -1:
                minusTwo = self.lastImage;
                break;
            case -2:
                minusTwo = self.lastImage - 1;
                break;  
        }

        self.bottom.attr('src', self.images[minusTwo]);
        self.top.fadeOut(self.fadeTime, function(){
            self.bottom.addClass('active');

            var plusOne = minusTwo + 1;
            if (plusOne &gt; self.lastImage){
                plusOne = 0;
            }


            self.top.attr('src', self.images[minusTwo]).show();
            self.bottom.attr('src', self.images[plusOne]);
            self.bottom.removeClass('active');
            self.currImage--;
            if(self.currImage &lt; 0){
                self.currImage = self.lastImage;
            }
        });
    }

    this._startInterval = function(){
        self.interval = setInterval(function(){
            self._animate();
        }, self.sliderDelay);
    }

    this._init();
}


The HTML markup to go with this is.

&lt;div id="sliderWrap"&gt;
    &lt;div id="nextBtn"&gt;&lt;i class="fa fa-chevron-right"&gt;&lt;/i&gt;&lt;/div&gt;
    &lt;div id="prevBtn"&gt;&lt;i class="fa fa-chevron-left"&gt;&lt;/i&gt;&lt;/div&gt;
    &lt;img id="topImage" src="{$featureImages[0]}" /&gt;
    &lt;img id="bottomImage" src="{$featureImages[1]}" /&gt;
&lt;/div&gt;


I think the only really relevant CSS is the active class, which basically puts the bottomImage on top of the topImage (z-index wise).
Firstly, I don't see any "real" issues with this - you mostly have done an awesome job! I have a few recommendations though that might help this be a little more performant, readable, and refined:


Do you really need all of these to be instance/public variables? Later on when you instantiate an ImageSlider, are you going to need an external module to know about these properties? Most of them could probably be private with var
lastImage and currImage are improperly/confusingly named - really they should be lastIndex and currIndex, because they are storing the index of an image, not a reference to the image itself.
It is a common convention to store a reference to the current scope at the top of a constrctor function.
The prefix "_" (underscore) is usually used to mark a function as private (using var _animate, for example). The airbnb style guide shows a usage similar to yours here, but, it is a very bad idea and I can't imagine why they would spread it. If a variable is attached to this, i.e. this.anything/this['anything'], it is, in JavaScript, not private. Putting this _ prefix is incredibly unintuitive and many people have told them so. At first glance, I thought maybe you were using it to indicate that the property was a method (i.e., this.elem and this.images are properties, but this._animate and this._init are functions), but I see you have other methods that do not use this prefix. If you are going to use this prefix to denote that it is a function, definitely be consistent with it.
You can remove the typeof check by reversing your logic a bit.
Did you mean to use "this" here, or "self"? Be sure to be consistent.
Use jQuery chaining to reduce scope resolution - you were calling this.bottom twice but you only needed to resolve that variable once, since jQuery will return the element for chainability. You can break it onto two lines if you prefer as well.
Pass animate by reference to setInterval instead of creating an anonymous scope to wrap the item.
minusTwo and plusOne are, at least for me, difficult to understand. I have read the function a few times and still do not quite understand why these variables have this name.
Cache this.bottom and this.top in order to reduce scope resolution in this function, since you are using them both quite a bit.


Here is the code, slightly modified and rewritten with specific numeric callouts:

function ImageSlider(wrapper, images){

    // 3
    var self = this;

    // 1
    this.elem = wrapper;
    this.images = images;
    this.top = this.elem.find('#topImage');
    this.bottom = this.elem.find('#bottomImage');
    this.next = this.elem.find('#nextBtn');
    this.prev = this.elem.find('#prevBtn');


    // 2
    this.lastIndex = images.length - 1;
    this.currIndex = 1; // This is 1 as the first iteration is rendered in the HTML.
    this.interval;
    this.fadeTime = 400;
    this.sliderDelay = 4000 + this.fadeTime;

    // 4
    this._init = function(){

        $(window).on('load', function(){
            self.elem.css('height', self.top.height());
            self.next.show();
            self.prev.show();
            self._animate();
            self._startInterval();
            self.next.click(function(){
                clearInterval(self.interval);
                self._animate(true);
                self._startInterval();
            });

            self.prev.click(function(){
                clearInterval(self.interval);
                self._animate(false);
                self._startInterval();
            });
        });
    }

    /**
     * @param next boolean. True next, false prev 
     */
    // 4, 5
    this._animate = function(next){
       if(!next){
         self.prevImage();
        } else {
          self.nextImage();
        }
    }

    this.nextImage = function(){
        self.currIndex++;

        // 6
        this.top.fadeOut(self.fadeTime, function(){
            self.bottom.addClass('active');
            self.top.attr('src', self.images[self.currIndex - 1]).show();

            if (self.currIndex &gt; self.lastIndex) {
                self.currIndex = 0;
            }

            // 7
            self.bottom.removeClass('active').attr('src', self.images[self.currIndex]);
        });
    }

    this.prevImage = function(){

        // 9
        var minusTwo = self.currIndex - 2;

        switch (minusTwo){
            case -1:
                minusTwo = self.lastIndex;
                break;
            case -2:
                minusTwo = self.lastIndex - 1;
                break;
        }

        // 10
        var bottom = self.bottom,
            top    = self.top;

        bottom.attr('src', self.images[minusTwo]);
        top.fadeOut(self.fadeTime, function(){
            bottom.addClass('active');

            // 9
            var plusOne = minusTwo + 1;
            if (plusOne &gt; self.lastIndex){
                plusOne = 0;
            }


            top.attr('src', self.images[minusTwo]).show();
            bottom.attr('src', self.images[plusOne]);
            bottom.removeClass('active');
            self.currIndex--;
            if(self.currIndex &lt; 0){
                self.currIndex = self.lastIndex;
            }
        });
    }

    // 4
    this._startInterval = function(){
        // 8
        self.interval = setInterval(self._animate, self.sliderDelay);
    }

    // 4
    this._init();
}