Javascript Closures и «это» - PullRequest
       15

Javascript Closures и «это»

34 голосов
/ 06 декабря 2008

У меня проблема с созданным мной объектом, который выглядит примерно так:

var myObject = {

    AddChildRowEvents: function(row, p2) {
        if(document.attachEvent) {
            row.attachEvent('onclick', function(){this.DoSomething();});
        } else {
            row.addEventListener('click', function(){this.DoSomething();}, false);
        }
    },

    DoSomething: function() {
        this.SomethingElse(); //<-- Error here, object 'this' does not support this method.
    }
}

Проблема в том, что когда я нахожусь внутри функции «DoSomething», «this» не ссылается на «myObject», что я делаю неправильно?

Ответы [ 5 ]

35 голосов
/ 06 декабря 2008

Когда вызывается функция, «this» относится к строке. Если вы хотите получить объект, вы можете сделать это примерно так: ]

AddChildRowEvents: function(row, p2) {
    var theObj = this;
    if(document.attachEvent) {
         row.attachEvent('onclick', function(){theObj.DoSomething();});
    } else {
         row.addEventListener('click', function(){theObj.DoSomething();}, false);
    }
},

Когда функция вызывается, она имеет доступ к переменной theOBj, которая находилась в области видимости, когда функция была определена.

10 голосов
/ 13 февраля 2009

this всегда относится к внутренней функции, если у вас есть вложенные функции, вы должны создать другую переменную и указать на this.

var myObject = {
    AddChildRowEvents: function(row, p2) {
        var that = this;
        if(document.attachEvent) {
            row.attachEvent('onclick', function(){that.DoSomething();});
        } else {
            row.addEventListener('click', function(){that.DoSomething();}, false);
        }
    }
}
3 голосов
/ 13 февраля 2009

Это распространенная проблема с замыканиями. Чтобы решить это, попробуйте что-то вроде этого:

var myObject = {    
    AddChildRowEvents: function(row, p2) { 
        var self = this;

        if(document.attachEvent) {            
             row.attachEvent('onclick', function(){this.DoSomething(self);});        
        } else {            
             row.addEventListener('click', function(){this.DoSomething(self);}, false);        
        }    
    },    

    DoSomething: function(self) {       
        self.SomethingElse(); 
    }
}
2 голосов
/ 12 февраля 2018

Проблема возникает из-за того, что this управляется в JS, что может быть быстрым, особенно если у вас есть обратный вызов, вызываемый асинхронно (и когда создается замыкание, связанное с этим).

Когда вызывается метод AddChildRowEvents, this хорошо ссылается на переменную myObject. Но цель этого метода - установить обработчик для клика. Так что событие будет инициировано когда-нибудь в будущем. В то время, какой объект действительно вызовет событие? Фактически, это DOM element, по которому пользователь будет нажимать. Так что this будет ссылаться на эту DOM element, а не на myObject переменную.

Для более современного решения, чем представленные, можно использовать bind method или arrow function.

1. Решение с функцией стрелки

let handler = {
	addEventHandler: function(row) {
	  console.log("this", this)
  	row.addEventListener("click", () => {
    	console.log("this", this)
    	this.doSomethingElse()
    })
  },
  
  doSomethingElse: function() {
  	console.log("something else")
  }
}
var div = document.querySelector("div")
handler.addEventHandler(div)
<div>one</div>

С arrow function (которые доступны с ES2015) контекст this не в контексте выполнения, а в лексическом контексте. Разница между ними заключается в том, что лексический контекст находится во время сборки, а не во время выполнения, поэтому лексический контекст легче отслеживать. Здесь, внутри arrow function, this ссылается на тот же this внешней функции (т.е. addEventHandler), поэтому ссылка myObject.

Для более подробного объяснения свойств функции жирной стрелки по сравнению с обычными функциями: https://dmitripavlutin.com/6-ways-to-declare-javascript-functions/

2. Раствор со связкой

let handler = {
	addEventHandler: function(row) {
	  console.log("this", this)
  	row.addEventListener("click", function() {
    	console.log("this", this)
    	this.doSomethingElse()
    }.bind(this))
  },
  
  doSomethingElse: function() {
  	console.log("something else")
  }
}
var div = document.querySelector("div")
handler.addEventHandler(div)
<div>one</div>

На этот раз, когда функция обратного вызова передается в addEventListener, один связывает контекст this с элементом this во внешней функции addEventHandler.

2 голосов
/ 17 августа 2013

Проблема с вашим кодом здесь, в следующей строке кода вы определили анонимную функцию и передали ее в качестве обработчика события для события onClick в следующих строках:

    row.attachEvent('onclick', function(){this.DoSomething();});

и

    row.addEventListener('click', function(){this.DoSomething();}, false);

когда возникает событие onclick и он вызывает анонимную функцию, которую вы передали, этот контекст ссылается на объект, который вызвал событие, но не myObject . потому что в этот момент исполняемый контекст находится в контексте обработчика событий.

Решение: вы можете выполнить хитрость, о которой говорил Майк Кантор, или с помощью jQuery использовать прокси-метод, чтобы определить этот контекст в анонимной функции.

Итак, ваш код будет выглядеть так:

var myObject = {

    AddChildRowEvents: function(row, p2) {
        if(document.attachEvent) {
            row.attachEvent('onclick', $.proxy(function () {
                this.DoSomething();
            }, this));
        } else {
            row.addEventListener('click', $.proxy(function () {
                this.DoSomething();
            },this), false);
        }
    },

    DoSomething: function() {
        this.SomethingElse(); //<-- Error here, object 'this' does not support this method.
    }
}

вы упомянули, что ошибка в this.SomthingElse () , но ваш код не показывает ее. Если вы действительно получаете ошибку в этой строке кода, возможно, вы используете метод DoSomthing где-то еще в качестве обработчика события.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...