2015-12-01 6 views
4

Я создал класс под названием SearchBox для обработки взаимодействия с поиском (отложенный триггер, поиск по нажатию клавиши ввода, предотвращение поиска, когда он активен, синхронизация результатов при завершении поиска и изменении текста и т. Д.).Правильный способ привязки «this» в обратных вызовах JavaScript-событий?

Все методы класса - это прототипы, предназначенные для доступа через this. В следующем коде предположим, что p является прототипом класса.

p.registerListeners = function() { 
    $(this.element).on('keypress', this.searchKeyPressed); 
}; 

p.unregisterListeners = function() { 
    $(this.element).off('keypress', this.searchKeyPressed); 
}; 

Это не работает, потому что, когда событие нажатие вызывает searchKeyPressed обработчик, он не делает это в контексте this. Единственное решение, которое я могу придумать, - это тот, который поддерживает только современные браузеры, который связывает обратный вызов с this, который фактически создает новую функцию. Поскольку он создает новую функцию, я должен кэшировать ее, чтобы удалить ее позже, так как я должен передать ту же ссылку на off, что я перешел на on.

Есть ли лучший способ сделать это, чем это, или это нормально?

var boundKeyPressed; 

p.registerListeners = function() { 
    boundKeyPressed = this.searchKeyPressed.bind(this); 
    $(this.element).on('keypress', boundKeyPressed); 
}; 

p.unregisterListeners = function() { 
    $(this.element).off('keypress', boundKeyPressed); 
}; 

Я подумал, что, может быть, jQuery.on бы обеспечить способ сделать это событие связывания автоматически, но вместо этого он кажется, что он связывает this разные вещи в зависимости от того, как это называется. Например, при использовании on('eventname',instance.func)является «currentTarget» (не обязательно «целевой» в условиях барботирования), тогда как при использовании on('eventname','selector',instance.func)this относится к элементу, соответствующему селектору. В любом случае, func работает так, как будто он не имеет отношения к instance.

+0

Отсутствует методы AS3, где это не было проблемой. Ничто не похоже на переход на 10 лет назад с JS, работающий с избыточным синтаксисом типа «this.method.bind (this)» (с ограниченной поддержкой, заметьте), где «this.method» должно быть достаточно. – Triynko

+1

Вы пробовали передать это обработчикам в качестве параметра – Binvention

+0

Так как boundkeypress запускает другую функцию, поэтому этот параметр не подключен, если вы не передадите его ему – Binvention

ответ

0

Вы можете использовать крышку.

p.registerListeners = function() { 
    var me = this; 

    $(me.element).on('keypress', function() { 
     me.searchKeyPressed.apply(me, arguments); 
    }); 
}; 

Использование применяется для передачи аргументов.

+0

Закрытие не отличается от 'bind'. Оно создает новую функцию и делает это для каждого экземпляра. Это побеждает память -сохраняющая цель использования прототипов, созданных только один раз и разделяемых всеми экземплярами класса. – Triynko

+0

Я думал, что главная проблема связана с поддержкой браузера. Использование закрытия, а не связывания по крайней мере поддерживается всеми браузерами. – Comptonburger

+0

Это часть проблемы, но другая часть - избыточность всегда необходимости привязывать функцию явно, когда она уже назначена прототипу. 'this.method.bind (this)' и 'me.method.apply (me)' оба выполняют одну и ту же основную вещь, хотя последняя имеет лучшую поддержку. – Triynko

2

Если вы добавите пространство имен в свои события, вы можете связать события и легко развязать их все сразу.

Обязать:

$(this.element).on('keypress.mynamespace', this.searchKeyPressed.bind(this)); 

Чтобы отвязать:

$(this.element).off('.mynamespace'); 
+0

Это не то, о чем спрашивал ОП. –

+0

Возможно, нет, но это обходит его проблему контекста обработчика событий. –

+0

Это действительно полезно. Поэтому я все еще выполняю привязку, но мне не нужно сохранять внешнюю переменную. Это должно быть особенностью jQuery, где он хранит обработчики событий в пространствах имен, поэтому вы можете удалить именованные подмножества из них (без необходимости поддерживать ссылки на них) или принудительно удалить все обработчики. Вероятно, это также менее подвержено утечкам памяти, поскольку jQuery управляет ссылками. В AS3 мне никогда не приходилось беспокоиться, потому что я бы назвал addEventListener со слабым опорным параметром, установленным в true. – Triynko

1

Первого, если вы не ожидаете, что ваша страница будет очень долго жила (или вы слушаете нажатия с сотнями вещей , что является очень большой проблемой архитектуры), это не будет большой проблемой, когда речь идет о памяти, даже если у телефонов были «ключи».

Второй, .bind имеет отличную поддержку для всех браузеров, не превышающих половины десятилетия, и грязь проста для полиза.

Третье: вы 100% право, , что это не круто, чтобы кэшировать функцию, чтобы быть в состоянии справиться с этим позже, поэтому давайте делать что-то об этом.

Существует малоизвестный трюк addEventListenerattachEvent), в котором он с радостью поддерживает объекты, с handleEvent методами на них.

Я не использую это для всего, так как иногда это на самом деле просто не стоит, но к игре решений, я использовал его для входа, вроде так:

class Input { 
    constructor (events) { 
    this.events = events || []; 
    } 

    handleEvent (e) { 
    var input = this; 
    var method = e.type; 
    if (typeof input[method] === "function") { 
     input.dispatchEvent(method, e); 
    } 
    } 

    dispatchEvent (method, content) { 
    var input = this; 
    input[method](content); 
    } 

    listen (el, events) { 
    var input = this; 
    events = events || input.events; 
    events.forEach(event => el.addEventListener(event, input)); 
    return this; 
    } 

    ignore (el, events) { 
    var input = this; 
    events = events || input.events; 
    events.forEach(event => el.removeEventListener(event, input)); 
    return this; 
    } 
} 


class Keyboard extends Input { 
    constructor() { 
    super(["keydown", "keyup"]); 
    var keyboard = this; 
    keyboard.keys = new Set(); 
    } 

    press (key) { this.keys.add(key); } 
    release (key) { this.keys.delete(key); } 
    isPressed (key) { return this.keys.has(key); } 

    keydown (e) { 
    var key = e.keyCode; 
    this.press(key); 
    } 

    keyup (e) { 
    var key = e.keyCode; 
    this.release(key); 
    } 
} 

я мог затем:

var gameplayEvents = ["keyup", "keydown"]; 
var keyboard = new Keyboard(); 
keyboard.listen(canvas, gameplayEvents); 

// ongameover 
keyboard.ignore(canvas, gameplayEvents); 

И если вы заметите, это все 100% чистый JS. Нет jQuery, extJS и т. Д. И действительно, это серьезно не намного больше кода. Я мог бы сделать это одним объектно-литеральным, если бы мне просто нужен один экземпляр для обработки мышки и mousedown; действительно все, что мне нужно - это объект с handleEvent, чтобы стать this внутри обратного вызова handleEvent.

Существует только один случай, о котором можно беспокоиться. Я не кешу ничего лишнего, если мне нужно отменить регистрацию.

jQuery (и другие) фактически используют это внутренне, чтобы оптимизировать ужасный код, который они обычно злоупотребляют производством.

Да, возможно, я обманываю, используя ES6 ... ... но это совсем не обязательно.

Это просто бодрее, чем то, что я делал:

function Parent (args) { } 
extend(Parent.prototype, { /*public-methods*/ }); 

function Child (args) { 
    Parent.call(this); 
    // construct 
} 
extend(
    Child.prototype, 
    Parent.prototype, 
    { /*public-override-methods*/ }, 
    { constructor: Child } 
); 

И опять же, есть много раз, когда связывают это 100% в силе.
Есть предложение прямо сейчас, для версии связывания ES7, которая потенциально может вызывать одно и то же значение при каждом вызове (если он проходит через этот путь).

С дополнительным преимуществом, что синтаксис позволяет объединять всевозможные удивительные вещи вместе.

2

Вместо использования bind, вы можете использовать jQuery.proxy function сохранить контекст, который создает функцию обертки вы могу отвязать:

jQuery.proxy имеет несколько вариантов, но сейчас jQuery.proxy(function, context) является то, что вы должны будете использование:

p.registerListeners = function() { 
    $(this.element).on('keypress', $.proxy(this.searchKeyPressed, this)); 
}; 

p.unregisterListeners = function() { 
    $(this.element).off('keypress', $.proxy(this.searchKeyPressed, this)); 
}; 
0

Добавить bind(this) в конструкторе - так вы не создавать новую функцию каждый раз, когда вы называете .bind. bind создает новую функцию каждый раз, когда вы ее вызываете, поэтому, если вы присоедините ее к ней: $el.on("click", this.handler.bind(this)), вы не можете отсоединить ее с помощью $el.off("click", this.handler.bind(this)), потому что обработчик не является тем же. (this.handler.bind(this) !== this.handler.bind(this)) Если вы сохраните эту ссылку на эту связанную функцию (например, в конструкторе this.handler = this.handler.bind(this)), вы можете $el.on("click", this.handler) и $el.off("click", this.handler), потому что обработчик такой же.

С помощью этого метода вы по существу переписываете эту функцию для этого экземпляра. Он больше не будет вызывать функцию, как на прототипе, но работает как на этом компьютере.

function MyObject($el) { 
    this.testValue = "I am really this!"; 
    this.$el = $el; 
    this.onClick = this.onClick.bind(this); 
    this.render(); 
} 
MyObject.prototype.onClick = function(evt) { 
    console.log(this.testValue); // logs "I am really this!" 
} 
MyObject.prototype.render = function() { 
    var $a = $("<a>", {"text": "Click on me!"}).appendTo($el.empty()); 
    $a.on("click", this.onClick); 
}