addEventListener를 사용하는 핸들러 내의 "this"값
프로토 타이핑을 통해 Javascript 객체를 만들었습니다. 테이블을 동적으로 렌더링하려고합니다. 렌더링 부분은 간단하고 잘 작동하지만 동적으로 렌더링 된 테이블에 대한 특정 클라이언트 측 이벤트도 처리해야합니다. 그것도 쉽습니다. 문제가있는 곳은 이벤트를 처리하는 함수 내부의 "this"참조입니다. "this"가 개체를 참조하는 대신 이벤트를 발생시킨 요소를 참조합니다.
코드를 참조하십시오. 문제 영역은 다음과 ticketTable.prototype.handleCellClick = function()
같습니다.
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
}
ticketTable.prototype.render = function(element)
{
var tbl = document.createElement("table");
for ( var i = 0; i < this.tickets.length; i++ )
{
// create row and cells
var row = document.createElement("tr");
var cell1 = document.createElement("td");
var cell2 = document.createElement("td");
// add text to the cells
cell1.appendChild(document.createTextNode(i));
cell2.appendChild(document.createTextNode(this.tickets[i]));
// handle clicks to the first cell.
// FYI, this only works in FF, need a little more code for IE
cell1.addEventListener("click", this.handleCellClick, false);
// add cells to row
row.appendChild(cell1);
row.appendChild(cell2);
// add row to table
tbl.appendChild(row);
}
// Add table to the page
element.appendChild(tbl);
}
ticketTable.prototype.handleCellClick = function()
{
// PROBLEM!!! in the context of this function,
// when used to handle an event,
// "this" is the element that triggered the event.
// this works fine
alert(this.innerHTML);
// this does not. I can't seem to figure out the syntax to access the array in the object.
alert(this.tickets.length);
}
핸들러를 인스턴스에 "바인딩"해야합니다.
var _this = this;
function onClickBound(e) {
_this.handleCellClick.call(cell1, e || window.event);
}
if (cell1.addEventListener) {
cell1.addEventListener("click", onClickBound, false);
}
else if (cell1.attachEvent) {
cell1.attachEvent("onclick", onClickBound);
}
여기서 이벤트 핸들러는 event
객체 (첫 번째 인수로 전달됨)를 정규화 handleCellClick
하고 적절한 컨텍스트 (즉, 이벤트 리스너가 연결된 요소 참조 )에서 호출합니다 .
또한 여기서 컨텍스트 정규화 (즉 this
, 이벤트 핸들러에서 적절하게 설정 )는 이벤트 핸들러로 사용되는 함수 ( onClickBound
)와 요소 객체 ( cell1
) 사이에 순환 참조를 생성합니다 . 일부 버전의 IE (6 및 7)에서 이로 인해 메모리 누수가 발생할 수 있습니다. 본질적으로이 누수는 브라우저가 네이티브 개체와 호스트 개체간에 존재하는 순환 참조로 인해 페이지 새로 고침시 메모리를 해제하지 못하는 것입니다.
이를 우회하려면 a) this
정규화를 삭제해야합니다 . b) 대안 적 (그리고 더 복잡한) 정규화 전략을 사용합니다. c) 페이지 언로드시 기존 이벤트 리스너를 "정리" 합니다 ( 예 : removeEventListener
, detachEvent
및 요소 사용 ) null
(불행히도 브라우저의 빠른 히스토리 탐색을 쓸모 없게 만듭니다).
이를 처리하는 JS 라이브러리를 찾을 수도 있습니다. 대부분 (예 : jQuery, Prototype.js, YUI 등)은 일반적으로 (c)에 설명 된대로 정리를 처리합니다.
주어진 함수에 대한 모든 호출에 대해이 값으로 사용해야하는 값을 지정할 수있는 bind 를 사용할 수 있습니다 .
var Something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as this is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as this is the binded Something object
};
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}
위의 예에서 문제는 bind로 리스너를 제거 할 수 없다는 것입니다. 또 다른 해결 방법은 handleEvent 라는 특수 함수를 사용하여 이벤트를 포착하는 것입니다.
var Something = function(element) {
this.name = 'Something Good';
this.handleEvent = function(event) {
console.log(this.name); // 'Something Good', as this is the Something object
switch(event.type) {
case 'click':
// some code here...
break;
case 'dblclick':
// some code here...
break;
}
};
// Note that the listeners in this case are this, not this.handleEvent
element.addEventListener('click', this, false);
element.addEventListener('dblclick', this, false);
// You can properly remove the listners
element.removeEventListener('click', this, false);
element.removeEventListener('dblclick', this, false);
}
언제나처럼 mdn 이 최고입니다 :). 이 질문에 대답하는 것보다 부분을 복사하여 붙여 넣었습니다.
또한 한 가지 더 방법은 EventListener 인터페이스 를 사용하는 것입니다 (DOM2에서 !! 왜 아무도 언급하지 않았는지 궁금합니다. 이것이 가장 적절한 방법이며 그러한 상황을위한 것입니다.)
즉, 콜백 함수를 전달하는 대신 EventListener 인터페이스를 구현하는 객체를 전달합니다. 간단히 말해, 이벤트 핸들러 함수를 가리키는 "handleEvent"라는 객체에 속성이 있어야 함을 의미합니다. 여기서 주요 차이점은 함수 내 this
에서 addEventListener
. 즉 this.theTicketTable
, belowCode에서 객체 인스턴스가됩니다. 내가 의미하는 바를 이해하려면 수정 된 코드를주의 깊게 살펴보십시오.
ticketTable.prototype.render = function(element) {
...
var self = this;
/*
* Notice that Instead of a function, we pass an object.
* It has "handleEvent" property/key. You can add other
* objects inside the object. The whole object will become
* "this" when the function gets called.
*/
cell1.addEventListener('click', {
handleEvent:this.handleCellClick,
theTicketTable:this
}, false);
...
};
// note the "event" parameter added.
ticketTable.prototype.handleCellClick = function(event)
{
/*
* "this" does not always refer to the event target element.
* It is a bad practice to use 'this' to refer to event targets
* inside event handlers. Always use event.target or some property
* from 'event' object passed as parameter by the DOM engine.
*/
alert(event.target.innerHTML);
// "this" now points to the object we passed to addEventListener. So:
alert(this.theTicketTable.tickets.length);
}
나는 이것이 오래된 게시물이라는 것을 알고 있지만 단순히 컨텍스트를 변수에 할당하고 self
함수를 호출 .call(self)
하고 컨텍스트에서 전달 하는 익명 함수에 함수를 던질 수도 있습니다 .
ticketTable.prototype.render = function(element) {
...
var self = this;
cell1.addEventListener('click', function(evt) { self.handleCellClick.call(self, evt) }, false);
...
};
This works better than the "accepted answer" because the context doesn't need to be assigned a variable for the entire class or global, rather it's neatly tucked away within the same method that listens for the event.
This arrow syntax works for me:
document.addEventListener('click', (event) => {
// do stuff with event
// do stuff with this
});
this will be the parent context and not the document context
Heavily influenced by kamathln and gagarine's answer I thought I might tackle this.
I was thinking you could probably gain a bit more freedom if you put handeCellClick in a callback list and use an object using the EventListener interface on the event to trigger the callback list methods with the correct this.
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
// the callback array of methods to be run when
// event is triggered
this._callbacks = {handleCellClick:[this._handleCellClick]};
// assigned eventListenerInterface to one of this
// objects properties
this.handleCellClick = new eventListenerInterface(this,'handleCellClick');
}
//set when eventListenerInterface is instantiated
function eventListenerInterface(parent, callback_type)
{
this.parent = parent;
this.callback_type = callback_type;
}
//run when event is triggered
eventListenerInterface.prototype.handleEvent(evt)
{
for ( var i = 0; i < this.parent._callbacks[this.callback_type].length; i++ ) {
//run the callback method here, with this.parent as
//this and evt as the first argument to the method
this.parent._callbacks[this.callback_type][i].call(this.parent, evt);
}
}
ticketTable.prototype.render = function(element)
{
/* your code*/
{
/* your code*/
//the way the event is attached looks the same
cell1.addEventListener("click", this.handleCellClick, false);
/* your code*/
}
/* your code*/
}
//handleCellClick renamed to _handleCellClick
//and added evt attribute
ticketTable.prototype._handleCellClick = function(evt)
{
// this shouldn't work
alert(this.innerHTML);
// this however might work
alert(evt.target.innerHTML);
// this should work
alert(this.tickets.length);
}
With ES6, you can use an arrow function as that will use lexical scoping[0] which allows you to avoid having to use bind
or self = this
:
var something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // 'Something Good'
};
element.addEventListener('click', () => this.onclick1());
}
What about
...
cell1.addEventListener("click", this.handleCellClick.bind(this));
...
ticketTable.prototype.handleCellClick = function(e)
{
alert(e.currentTarget.innerHTML);
alert(this.tickets.length);
}
e.currentTarget points to the target which is bound to the "click event" (to the element that raised the event) while
bind(this) preserves the outerscope value of this
inside the click event function.
If you want to get an exact target clicked, use e.target instead.
'Program Tip' 카테고리의 다른 글
스타-스키마 디자인 (0) | 2020.11.20 |
---|---|
힘내 풀 : 오류 : 항목 foo가 업데이트되지 않았습니다. (0) | 2020.11.20 |
파이썬의 객체 목록에서 속성 목록 추출 (0) | 2020.11.20 |
"Inversion of Control", "Dependency inversion"및 "Decoupling"의 차이점 (0) | 2020.11.20 |
선택 상자의 각도, 부울 값 (0) | 2020.11.19 |