Передача области в функцию обратного вызова / связывание - PullRequest
16 голосов
/ 18 февраля 2011

Я пытаюсь передать область действия функции в метод обратного вызова.У меня проблема в том, что я получаю объектную область, которая не предоставляет мне доступ к параметрам и локальным переменным в исходной функции.Мое понимание «этого» означает текущий контекст (будь то окно или какой-то объект) в дополнение к локально объявленным переменным и параметрам.[цитируйте отличную работу Ричарда Корнфорда в http://jibbering.com/faq/notes/closures/ в разделе «Контексты исполнения»].Я также понимаю, что переменные в JavaScript имеют область действия функции (что, если они объявлены внутри функции, они доступны только внутри этой функции).

С этим пониманием в новой среде я пытаюсь кодировать шаблон, который много сделал для моего предыдущего работодателя, вызывая асинхронный метод, определяя обработчик обратного вызова и передавая мою текущую область, ожидая, что онабыть доступным в методе обратного вызова.Я не считаю, что это имеет место в моей нынешней обстановке.(раскрытие: я использовал ExtJS в своей предыдущей среде ... теперь я чувствую себя слишком уютно с фреймворком, делаю предположения о том, что происходит).

Мой простой тестовый код демонстрируетчто я пытаюсь сделать, а что не работает.

function myHandler(data, ctx) {
    console.log('myHandler():  bar: ' + bar);  // <- prob: bar undefined 
    console.log(JSON.stringify(data));
}
MySvcWrap = {
    doWork: function(p1, callback, scope) {
        var result = {colors: ['red', 'green'], name:'Jones', what: p1};
        if (callback) {
            callback.call(scope||this,result, scope);
        } 
    }
}
function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler, this); // scope object is this
}

lookup();

Проблема здесь в том, что this передается в MySvcWrap.doWork в этом случае глобальный объект Window.Мое намерение состоит в том, чтобы передать контекст выполнения функции myHandler.

То, что я пробовал.Если вместо «этого» я ​​передаю объект, который работает, например:

function myHandler(data, ctx) {
    console.log('myHandler():  this.bar: ' + this.bar);  // <- no prob: this.bar 
    console.log(JSON.stringify(data));
}

function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler, {bar: bar}); // scope object is just object
}

Мне нужно, чтобы кто-то ударил меня по голове здесь ... при прохождении «этого» в моем первом случае,конечно, это глобальная область (я нахожусь в глобально определенной функции) ... Моя проблема в том, что я думал, передавая область, что у меня был доступ к локально определенным переменным и параметрам в этом контексте ... Я качаю своюпредыдущее понимание JS ??Как выполнить эту задачу?

Ответы [ 3 ]

16 голосов
/ 18 февраля 2011

Кстати, несколько слов об областях в вашем коде:

function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler, this); // this here points to window object and not to the function scope
}

так же, как писать:

function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler, window); 
}

это так, потому что вы определяете функцию поиска глобально. внутри функции doWork, когда вы пишете this, она указывает на MySvcWrap объект (потому что эта функция определена внутри этого объекта).

Если ваша функция обратного вызова должна видеть переменную bar, она должна быть определена в той же области видимости, что и

MySvcWrap = {
    doWork: function(p1, callback, scope) {
        var result = {colors: ['red', 'green'], name:'Jones', what: p1};
        if (callback) {
            callback.call(scope||this,result, scope);
        } 
    }
}
function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', 
        function (data, ctx) {
            console.log('myHandler():  bar: ' + bar); 
            console.log(JSON.stringify(data));
        }, 
        this); // scope object is this
}

lookup();

в этом случае вы отправляете анонимную функцию в качестве обратного вызова, она определена внутри функции поиска, поэтому она имеет доступ к своим локальным переменным; моя консоль показывает меня в этом cae:

myHandler(): bar: food
{"colors":["red","green"],"name":"Jones","what":"thang"}

Чтобы упростить поддержку, вы можете определить myHandler внутри функции поиска:

function lookup() {
    var bar = 'food'; // local var
    var myHandler = function(data, ctx) {
        console.log('myHandler():  bar: ' + bar);
        console.log(JSON.stringify(data));
    };
    MySvcWrap.doWork('thang', myHandler, this); // scope object is this
}

С другой стороны, почему одна функция должна иметь доступ к локальным переменным другой функции? Может быть, это может быть переработано ...

Еще один способ заставить ваш код работать, это использовать анонимную функцию вместо поиска (будет работать, если вы просто объявите и выполните эту функцию один раз):

(function() {
   var bar = 'food';

   function myHandler(data, ctx) {
       console.log('myHandler():  bar: ' + bar);  
       console.log(JSON.stringify(data));
    } 
    MySvcWrap = {
       doWork: function(p1, callback, scope) {
           var result = {colors: ['red', 'green'], name:'Jones', what: p1};
           if (callback) {
               callback.call(scope||this,result, scope);
           } 
       }
    }
    MySvcWrap.doWork('thang', myHandler, this);
  }
)();

результат тот же, но функции поиска больше нет ...

И еще одна идея, чтобы заставить его работать ... На самом деле вам нужно определить обработчик обратного вызова в той же области, где определена переменная bar, так что это можно сделать немного хитрее, но в качестве альтернативы:

function myHandler(bar) { // actually in this case better to call it createHandler 
    return function(data, ctx) {
        console.log('myHandler():  bar: ' + bar); 
        console.log(JSON.stringify(data));
    } 
}
MySvcWrap = {
    doWork: function(p1, callback, scope) {
        var result = {colors: ['red', 'green'], name:'Jones', what: p1};
        if (callback) {
            callback.call(scope||this,result, scope);
        } 
    }
}
function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler(bar), this); // scope object is this
}

И несколько ресурсов для чтения по поводу определения и закрытия JavaScript:

  1. Объяснение объема и закрытий JavaScript
  2. Поднятие Javascript - закрытие и лексическая область видимости
  3. JavaScript: расширенные задачи и другие головоломки - очень хорошая презентация по теме
2 голосов
/ 18 февраля 2011

Код работал бы, если бы он был структурирован следующим образом.

MySvcWrap = {
    doWork: function(p1, callback, scope) {
        var result = {colors: ['red', 'green'], name:'Jones', what: p1};
        if (callback) {
            callback.call(scope||this,result, scope);
        } 
    }
}
function lookup() {
    var bar = 'food'; // local var

    function myHandler(data, ctx) {
        console.log('myHandler():  bar: ' + bar);  // <- prob: bar undefined 
        console.log(JSON.stringify(data));
    }

    MySvcWrap.doWork('thang', myHandler, this); // scope object is this
}

lookup();

Функция myHandler может обращаться к локальным переменным поиска, как она заключена, таким образом, закрытие.

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

function myHandler(data, bar, ctx) {
    console.log('myHandler():  bar: ' + bar);  // <- prob: bar undefined 
    console.log(JSON.stringify(data));
}
MySvcWrap = {
    doWork: function(p1, callback) {
        var result = {colors: ['red', 'green'], name:'Jones', what: p1};
        if (callback) {
            callback(result);
        } 
    }
}
function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler.bind(this, bar)); // callback function is bound to the scope
}

lookup();

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

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

0 голосов
/ 18 февраля 2011

Я был немного быстр в своем первом ответе и оставил несколько пробелов, как сказал Maxym в комментариях ниже, спасибо, что сообщили мне :) Вот что я получаю за попытку быстро опубликовать, прежде чем вернуться к работе: P

В моем первом ответе я пытался понять, что если вы хотите использовать привязку для доступа к переменным в функции lookup из функции myHandler (что выходит за рамки lookup), вам не следует этого делать. используйте вместо этого var, но this.

Использование var сделает его доступным только для lookup's частного контекста и любых вложенных в него функций, как это продемонстрировали другие ответы (и, вероятно, являются лучшим путем)

Ниже приведен обновленный пример того, как передать область поиска myHandler, полностью исключив myHandler из области поиска. Надеюсь, это поможет пролить немного света на:

«Проблема, с которой я столкнулся, заключается в том, что я получаю объектную область, которая не предоставляет мне доступ к параметрам и локальным переменным в исходной функции.»

Как указано в комментариях к моему предыдущему ответу, this может оказаться сложным, и вы можете в конечном итоге добавить вещи в область действия global, если не будете осторожны, как я делал в своем первом примере. :( Так что я добавил небольшую проверку на хакерство, чтобы увидеть, какая область действия this удостоверяется, что она не window для демонстрационных целей ...

Живой пример

function myHandler(data, ctx) {
    console.log('myHandler():  bar: ' + this.bar); // <- must use `this`
    console.log('myHandler():  privateBar: ' + this.privateBar); // <- undefined because it's privately scoped
    console.log(JSON.stringify(data));
}
MySvcWrap = {
    doWork: function(p1, callback, scope) {
        var result = {
            colors: ['red', 'green'],
            name: 'Jones',
            what: p1
        };
        if (callback) {
            callback.call(scope || this, result, scope);
        }
    }
}

function lookup() {
    if(this == window) return lookup.call(lookup); // <- my hack'ish check

    var privateBar = 'private food'; // private local var
    this.bar = 'food'; // public local var
    MySvcWrap.doWork('thang', myHandler, this); // scope object is this
}

lookup();

console.log('global space - bar: ' + this.bar);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...