简体   繁体   中英

How can I access a class as “this” when adding event listeners, using bind?

I have an eventlistener that looks like this:

window.addEventListener('scroll', scroll.throttle(
    triggered, 
    {state: state, wrapper: wrapper, children: children, scroll: scroll},
    50
));

And I have a class that looks like this:

Scroll = class{
    constructor(){
        this.on = true; 
    }
    throttle(fn, v, wait){
        var time = Date.now();
        return () => {
            if ((time + wait - Date.now()) < 0 && this.on) {
                fn(v);
                time = Date.now();
            }
        }
    }
    triggered(o){
        if(o.state.check  !== 0){
            o.scroll.on = false;
            o.wrapper.classList.toggle('flic-down', o.state.check === 1)
            o.wrapper.classList.toggle('flic-up', o.state.check === -1)
            o.state.update();

            o.wrapper.classList.add('flic-transition')
            setTimeout(()=>{this.changeDone(o)}, 1200);
        }
    }
    changeDone(o) {
        o.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
        o.children.setClasses(o.state.state);
        o.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
        o.scroll.on = true;
    }
},

I don't like passing state, wrapper, children and scroll as variables. I would prefer to store them in the class when instantiating them. I understand the problem is that "this" won't be passed correctly and that it can be bound. But because the throttle function I don't understand how to pass this.

I would recommend to separate the throttling from the scrolling.

class Throttle {
    constructor() {
        this.on = true; 
    }
    get(fn, wait) {
        var time = Date.now();
        return (...args) => {
            if ((time + wait - Date.now()) < 0 && this.on) {
                fn(...args);
                time = Date.now();
            }
        }
    }
}

class Scroll {
    constructor(state, wrapper, children) {
        this.state = state;
        this.wrapper = wrapper;
        this.children = children;
        this.throttle = new Throttle();
    }
    triggered() {
        if (this.state.check !== 0) {
            this.throttle.on = false;
            this.wrapper.classList.toggle('flic-down', this.state.check === 1)
            this.wrapper.classList.toggle('flic-up', this.state.check === -1)
            this.state.update();

            this.wrapper.classList.add('flic-transition')
            setTimeout(()=>{this.changeDone()}, 1200);
        }
    }
    changeDone() {
        this.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
        this.children.setClasses(this.state.state);
        this.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
        this.throttle.on = true;
    }
}

You then would do

const scroll = new Scroll(state, wrapper, children);
window.addEventListener('scroll', scroll.throttle.get(() => scroll.triggered(), 50));

Notice the arrow function being passed to throttle.get that calls triggered on the scroll instance, instead of passing the method without a context.


If you want to mix them both in the same class, I don't see why throttle would take a fn and v as parameters. You're only using it to call triggered anyway:

class ThrottledScroll {
    constructor(state, wrapper, children) {
        this.state = state;
        this.wrapper = wrapper;
        this.children = children;
        this.throttle = new Throttle();
        this.on = true; 
    }
    get(wait) {
        var time = Date.now();
        return () => {
            if ((time + wait - Date.now()) < 0 && this.on) {
                this.triggered();
                time = Date.now();
            }
        }
    }
    triggered() {
        if (this.state.check !== 0) {
            this.on = false;
            this.wrapper.classList.toggle('flic-down', this.state.check === 1)
            this.wrapper.classList.toggle('flic-up', this.state.check === -1)
            this.state.update();

            this.wrapper.classList.add('flic-transition')
            setTimeout(()=>{this.changeDone()}, 1200);
        }
    }
    changeDone() {
        this.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
        this.children.setClasses(this.state.state);
        this.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
        this.on = true;
    }
}

with

const scroll = new ThrottledScroll(state, wrapper, children);
window.addEventListener('scroll', scroll.get(50));

In your class constructor, you can bind your class methods' context to the class with this.methodName = this.methodName.bind(this) . ReyHaynes is right, this can also be accomplished by defining class methods as arrow functions, but I believe that's still an uncomfirmed ES7 feature, so you might not want to use that.

If that's unclear or unhelpful, you might find some helpful context here . It's a React resource, but, if I understand correctly, your problem is one dealt with in React all the time.

Adding the bind to the constructor worked for the class, that handles the binding issue when called out of context:

Scroll = class{
    constructor(){
        this.on = true; 
        this.triggered = this.triggered.bind(this)
        this.changeDone = this.changeDone.bind(this)
    }
    throttle(fn, v, wait){
        var time = Date.now();
        return () => {
            if ((time + wait - Date.now()) < 0 && this.on) {
                fn(v);
                time = Date.now();
            }
        }
    }
    triggered(o){
        if(o.state.check  !== 0){
            o.scroll.on = false;
            o.wrapper.classList.toggle('flic-down', o.state.check === 1)
            o.wrapper.classList.toggle('flic-up', o.state.check === -1)
            o.state.update();

            o.wrapper.classList.add('flic-transition')
            setTimeout(()=>{this.changeDone(o)}, 1200);
        }
    }
    changeDone(o) {
        o.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
        o.children.setClasses(o.state.state);
        o.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
        o.scroll.on = true;
    }
}

The event listener needed to have scroll.triggered vs triggered unless you deconstructed ( const { triggered } = scroll ) before it and we didn't see that:

window.addEventListener('scroll', scroll.throttle(
    scroll.triggered, 
    {state: state, wrapper: wrapper, children: children, scroll: scroll},
    50
))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM