JavaScript/jQuery variable scope in class (this)

2019-03-02 02:29发布

问题:

I'm writing simple slider for my website. This slider contains list items. I want to use OOP approach.

My actual code:

var miniSlider = function(objId)
    {
        this.obj = $("#" + objId);
        this.obj.settings = [];
        this.obj.settings['items'] = $('ul li', this.obj).length;

        this.pagerNext = this.obj.find("i.next");
        this.pagerPrev = this.obj.find("i.prev");
        this.pagerNext.on("click", function() {
        alert(this.obj.settings['items']);
        });
    };

I can invoke a few other sliders (yes, that's why I introduced a class):

miniSlider("mini-slider");

The problem is that when I'm in jQuery this.pagerNext.on("click", function() { }); this is no longer my object but - it's become a clicked element. How can I access this.obj.settings after click in a well done way (and with multi sliders support)?

EDIT:

Here is a full code created with a cooperation with SOF community :)

var MiniSlider = function(objId)
{
    this.obj = $("#" + objId);

    this.obj.settings = {
        items: $("ul li", this.obj).length,
        autoChangeTime: 8000
    };
    this.obj.activeElement = null;

    this.pagerNext = this.obj.find("i.next");
    this.pagerPrev = this.obj.find("i.prev");

    var self = this;

    this.pagerNext.on("click", function() {

        self.obj.activeElement = $('li.active', self.obj);

        if(self.obj.settings.items > 0)
        {
            if(self.obj.activeElement.is(':last-child')) 
            { 
                $('li.active', self.obj).removeClass('active');
                $('li', self.obj).first().addClass('active');
            } 
            else 
            {
                self.obj.activeElement.next().addClass('active').prev().removeClass('active');
        }
    }
    });
    this.pagerPrev.on("click", function() 
    {
        self.obj.activeElement = $('li.active', self.obj);

        if(self.obj.settings.items > 0)
        {
            if(self.obj.activeElement.is(':first-child')) 
            { 
                self.obj.activeElement.removeClass('active');
                $('li', self.obj).last().addClass('active');
            } 
            else 
            {
                self.obj.activeElement.prev().addClass('active').next().removeClass('active');
            }
        }
    });
    this.obj.parent().on('mouseenter mouseleave', function(e) {
        if (e.type == 'mouseenter') 
        {
            $(this).addClass('stop');
        }
        else 
        {
            $(this).removeClass('stop'); 
        }
    });
    setInterval(function() {
        if(self.obj.settings.items > 0 && !self.obj.parent().hasClass("stop"))
        {
            self.pagerNext.click();
        }
    }, this.obj.settings.autoChangeTime);

    };

and invoke:

new MiniSlider("mini-slider");

回答1:

Alex gave you the solution to the this problem in your callback, but there is another problem in your code.

You are calling the miniSlider() function without a new operator:

miniSlider("mini-slider");

That means that inside the function, this is not a unique object, but is actually the window object!

You need to use the new operator to create an individual object for each call:

new miniSlider("mini-slider");

But you should also change the name of this function to follow the JavaScript convention that constructors begin with a capital letter. Call it MiniSlider and use it like so:

new MiniSlider("mini-slider");

If you follow this convention (which most experienced JavaScript programmers do), it will help you remember when to use new. If the function begins with a capital letter, it's a constructor and you need to use new with it. Otherwise, you don't.

If you'd like to be able to use your constructor without new, that is also possible with a bit more code, e.g.:

function MiniSlider( objId ) {
    if( this == window ) return new MiniSlider( objId );
    // the rest of your constructor code goes here
}

But generally people don't bother with that and just use the initial capital letter on the constructor as a reminder to use new.

Also, as a suggestion, I like to use a meaningful name when I save this in a variable, and then I use that name consistently instead of using this at all. Doing it this way it might look like:

var miniSlider = function(objId) {
    var slider = this;

    slider.obj = $("#" + objId);
    slider.obj.settings = [];
    slider.obj.settings['items'] = $('ul li', slider.obj).length;

    slider.pagerNext = slider.obj.find("i.next");
    slider.pagerPrev = slider.obj.find("i.prev");
    slider.pagerNext.on("click", function() {
        alert(slider.obj.settings['items']);
    });
};

Why do I prefer that approach over using this in most places and another variable like self where you need it? This way I don't have to remember which to use: I can always use slider in the code instead of this. (Of course you could use self or any other name; I just like to have a more meaningful name if I'm going to the trouble of making up a name at all.)

Another minor problem in the code is in these two statements:

    slider.obj.settings = [];
    slider.obj.settings['items'] = $('ul li', slider.obj).length;

You shouldn't use an Array when you are going to be giving it named properties like this. Use an Object instead. Arrays should only be used when you have numeric indexes like 0, 1, 2, etc. And with an object literal you can set the property at the same time:

    slider.obj.settings = {
        items: $('ul li', slider.obj).length
    };

Also, when you use that property:

        alert(slider.obj.settings['items']);

you can write it more simply as:

        alert(slider.obj.settings.items);

Either way it does the same thing.



回答2:

Save a reference to this in a local variable, and use that variable instead of this in the nested function.

var self = this;
this.pagerNext.on("click", function() {
  alert(self.obj.settings['items']);
});