Могу ли я получить совет по поводу делегатов JavaScript? - PullRequest
6 голосов
/ 11 июля 2009

Я зациклен на делегатах и ​​замыканиях в JavaScript, и мне кажется, что я столкнулся с ситуацией, в которой я хотел бы попытаться использовать один или оба.

У меня есть веб-приложение, которое во многом похоже на приложение форм, с полями, обращающимися к серверу для изменения данных в каждом onBlur или onChange (в зависимости от элемента формы). Я использую веб-службы ASP.NET 3.5 и jQuery для выполнения большей части работы.

Что нужно знать для примера:

  • isBlocking() - это простой механизм для синхронизации некоторых функций (например, мьютекса)
  • isDirty(el) проверяет, что значение элемента действительно изменилось, прежде чем тратить вызов на сервер
  • Agent() возвращает одноэлементный экземпляр прокси-класса WebService
  • getApplicationState() передает строку в кодировке base-64 в веб-службу. Эта строка представляет состояние приложения - значение элемента и состояние передаются службе, которая выполняет некоторые вычисления. Функция onSuccess вызова веб-службы возвращает новое состояние, которое клиент обрабатывает и обновляет весь экран.
  • waitForCallback() устанавливает флаг, который isBlocking() проверяет мьютекс

Вот пример одной из 50 очень похожих функций:

function Field1_Changed(el) {
    if (isBlocking()) return false;
    if (isDirty(el)) {
        Agent().Field1_Changed($j(el).val(), getApplicationState());
        waitForCallback();
    }
}

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

function Field_Changed(el, updateFunction, checkForDirty) {
    if (isBlocking()) return false;
    var isDirty = true; // assume true
    if (checkForDirty === true) {
        isDirty = IsDirty(el);
    }
    if (isDirty) {
        updateFunction(el);
        waitForCallback();
    }
}

function Field1_Changed(el) {
    Field_Changed(el, function(el) { 
        Agent().Field1_Changed($j(el).val(), getTransactionState()); 
    }, true);
}

Это нормально, но иногда у меня может быть много параметров:

    ...
    Agent().Field2_Changed($j(el).val(), index, count, getApplicationState());
    ....

В конечном итоге я хотел бы сделать звонки одним бельем, что-то вроде этого (обратите внимание, getTransactionState() звонки - я бы хотел как-то автоматизировать это):

// Typical case: 1 value parameter
function Field1_Changed(el) {
    Field_Changed(el, delegate(Agent().Field1_Changed, $j(el).val()), true);
}

// Rare case: multiple value parameters
function Field2_Changed(el, index, count) {
    Field_Changed(el, delegate(Agent().Field1_Changed, $j(el).val(), index, count), true);
}

function Field_Changed(el, theDelegate, checkIsDirty) {
    ???
}

function delegate(method) {
    /* create the change delegate */
    ???
}

Хорошо, мой первый вопрос: стоит ли это того? Это труднее читать, но легче поддерживать или наоборот? Это довольно хорошее мероприятие, поэтому я могу в конечном итоге назначить награду за это, но я был бы признателен за любую помощь, которую вы могли бы предложить. Спасибо!

UPDATE

Итак, я принял ответ, основываясь на том факте, что он указал мне правильное направление. Я подумал, что вернусь и опубликую свое решение, чтобы у других, кто только начинает с делегатами, было что-то для моделирования. Я также публикую его, чтобы узнать, хочет ли кто-нибудь попробовать его оптимизировать или внести предложения. Вот общий метод Field_Changed(), который я придумал, с checkForDirty и omitState необязательными параметрами:

function Field_Changed(el, args, delegate, checkForDirty, omitState) {
    if (isBlocking()) return false;
    if (!$j.isArray(args) || args.length == 0) {
        alert('The "args" parameter in Field_Changed() must be an array.');
        return false;
    }
    checkForDirty = checkForDirty || true; // assume true if not passed
    var isDirty = true; // assume true for updates that don't require this check
    if (checkForDirty === true) {
        isDirty = fieldIsDirty(el);
    }
    if (isDirty) {
        omitState = omitState || false; // assume false if not passed
        if (!omitState) {
            var state = getTransactionState();
            args.push(state);
        }
        delegate.apply(this, args);
        waitForCallback();
    }
}

Он обрабатывает все, что мне нужно (проверка на грязность, применение состояния приложения, когда оно мне нужно, и принудительное выполнение синхронных вызовов веб-службы. Я использую его так:

function TransactionAmount_Changed(el) {
    Field_Changed(el, [cleanDigits($j(el).val())], Agent().TransactionAmount_Changed, true);
}

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

Ответы [ 3 ]

5 голосов
/ 11 июля 2009

ОК, несколько вещей:

  1. Делегаты чрезвычайно просты в javascript, так как функции являются членами первого класса.
  2. Function.apply позволяет вам вызывать функцию с массивом аргументов.

Так что вы можете написать это так

function Field_Changed(delegate, args)
{
    if (isBlocking()) return false;
    if (isDirty(args[0])) { //args[0] is el
        delegate.apply(this, args);
        waitForCallback();
    }
}

И назовите это как:

Field_Changed(Agent().Field2_Changed, [el, getApplicationState(), whatever...]);
0 голосов
/ 22 августа 2010
0 голосов
/ 11 июля 2009

Я использовал следующую служебную функцию, которую я написал давно:

/**
 * @classDescription This class contains different utility functions
 */
function Utils()
{}

/**
 * This method returns a delegate function closure that will call
 * targetMethod on targetObject with specified arguments and with
 * arguments specified by the caller of this delegate
 * 
 * @param {Object} targetObj - the object to call the method on
 * @param {Object} targetMethod - the method to call on the object
 * @param {Object} [arg1] - optional argument 1
 * @param {Object} [arg2] - optional argument 2
 * @param {Object} [arg3] - optional argument 3
 */
Utils.createDelegate = function( targetObj, targetMethod, arg1, arg2, arg3 )
{
    // Create an array containing the arguments
    var initArgs = new Array();

    // Skip the first two arguments as they are the target object and method
    for( var i = 2; i < arguments.length; ++i )
    {
        initArgs.push( arguments[i] );
    }

    // Return the closure
    return function()
    {
        // Add the initial arguments of the delegate
        var args = initArgs.slice(0);

        // Add the actual arguments specified by the call to this list
        for( var i = 0; i < arguments.length; ++i )
        {
            args.push( arguments[i] );
        }

        return targetMethod.apply( targetObj, args );
    };
}

Итак, в вашем примере я бы заменил

function Field1_Changed(el) {
    Field_Changed(el, delegate(Agent().Field1_Changed, $j(el).val()), true);
}

С чем-то вроде

function Field1_Changed(el) {
    Field_Changed(el, Utils.createDelegate(Agent(), Agent().Field1_Changed, $j(el).val()), true);
}

Затем внутри Agent().FieldX_Changed я бы вручную вызвал getApplicationState() (и инкапсулировал бы эту логику в общий метод для обработки изменений полей, которые все методы Agent().FieldX_Changed будут вызывать внутри).

...