纯JavaScript中的表单处理和验证

时间:2013-05-10 14:52:30

标签: javascript performance

我的目的是在算法的设计,性能和跨浏览器兼容性方面对下面的脚本进行思考和批评。

我刚刚开始进入JavaScript,错过了很长一段时间的精彩。我的背景和经验是开发基于C / C ++ / PHP的RESTful后端。

为了理解语言和使用它的正确方法,我决定做一些我确信已经做过很多次的事情。但是学习使用新语言和范例往往会带来痛苦。

这是我尝试创建普通表单处理和验证脚本/函数。 为了降低复杂性并保持代码简单/干净,我决定使用HTML5自定义数据属性(data- *)为表单中的每个元素分配元数据:

  
      
  1. 数据要求:正确还是错误。如果设置为true,则此参数使表单字段成为必需,因此不能为空。设置为false的值表示该字段是可选的。默认值为false。>

  2.   
  3. 数据类型:要执行的验证类型。示例包括“电子邮件”,“密码”,“数字”或任何其他“正则表达式”。

  4.   

这种形式的一个神话般的简单例子是:

<form action="postlistings" id="postlistings" enctype='multipart/form-data' method="post" class="postlistings">
    <ul class="login-li">
        <li>
            <input class="title" name="title" type="title" id="title" data-required="true" data-type="title"></a>
        </li>
        <li>
            <textarea name="body" id="elm1" class="elm1" name="elm1" data-type="body" data-required="true" >
            </textarea>
        </li>
        <li>
        <span class="nav-btn-question">Add Listing</span>
        </li>
    </ul>
</form>

提醒:这是我的第一段JavaScript代码。 我们的想法是在传递表单名称时调用Form,以便在一个循环中检索并验证所有字段值以获得性能。验证涉及两个步骤,可以从上面描述的Data- *属性中猜到:

  

我。检查所需的表单字段。

如果值无法满足步骤1要求,则会针对特定表单值提取配置中的错误消息。因此,对于无法满足此要求的所有值,将收集一组错误消息并将其传递给View。

  

II。执行相应的验证。

仅当所有值都通过步骤1时才会执行验证。否则,它们将按照上面1中所示的相同步骤执行。

function Form(){

    var args = Array.prototype.slice.call(arguments),
        formName = args[0],
        callback = args.pop(),
        userError = [{type: {}, param: {}}],
        requiredDataParam = 'required',
        typeDataParam = 'type',
        form = document.forms[formName],
        formLength = form.length || null,
        formElement = {id: {}, name: {}, value: {}, required: {}, type: {}};

    function getFormElements(){
        var num = 0;
        var emptyContent = false;

        for (var i = 0; i < formLength; i += 1) {

            var formField = form[i];
            formElement.id[i] = inArray('id', formField) ? formField.id : null;
            formElement.name[i] = inArray('name', formField) ? formField.name : null;
            formElement.value[i] = inArray('value', formField) ? formField.value : null;
            formElement.required[i] = getDataAttribute(formField, requiredDataParam);
            formElement.type[i] = getDataAttribute(formField, typeDataParam);


            if (formElement.required[i] === true){
                if(!formElement.type[i]) {
                    error('Validation rule not defined!');
                }
                else if (!formElement.value[i]) {
                    userError[num++] = {'type': 'required', 'param': form[i]};
                    emptyContent = true;
                }
            }

            if (emptyContent === false) {
                // Perform validations only if no empty but required form values were found.
                // This is so that we can collect all the empty
                // inputs and their corresponding error messages.
            }

        }

        if (userError) {
            // Return empty form errors and their corresponding error messages.
        }

        return formElement;
    };

    // Removed the getFormParam function that was not used at all.

    return {
        getFormElements: getFormElements
    }
};

上面的JS脚本中使用的两个外部函数(来自JQuery源代码):

var inArray = function(elem, array){
    if (array.indexOf){
        return array.indexOf(elem);
    }

    for (var i = 0, length = array.length; i < length; i++){
        if (array[i] === elem){
            return i;
        }
    }

    return -1;
} 


// This is a cross-platform way to retrieve HTML5 custom attributes.
// Source: JQuery

var getDataAttribute = function(elem, key, data) {
    if (data === undefined && elem.nodeType === 1) {
        data = elem.getAttribute("data-" + key);

        if (typeof data === "string") {
            data = data === "true" ? true :
            data === "false" ? false :
            data === "null" ? null :
                !CheckType.isNaN ? parseFloat(data) :
                CheckType.rbrace.test(data) ? parseJSON(data) :
                data;
        }
        else {
            data = undefined;
        }
    }
    return data;
}

配置错误消息的示例可以设置如下:

var errorMsgs = {
    ERROR_email: "Please enter a valid email address.",
    ERROR_password: "Your password must be at least 6 characters long. Please try another",
    ERROR_user_exists: "The requested email address already exists. Please try again."
};

当我发布此内容供您审核时,请忽略我可能没有遵循的任何样式约定。我的目的是让我的专家评论我应该做的不同,或者可以做更好的代码本身和算法。

除了造型惯例外,欢迎所有批评和问题。

2 个答案:

答案 0 :(得分:8)

首先,我想澄清一个常见的误解。如果你已经清楚地理解了这一点,请原谅我;也许对其他人有帮助。

学习和使用jQuery或类似的库并不排除或与学习JavaScript语言冲突。 jQuery只是一个DOM操作库,它消除了使用DOM的许多痛点。即使您使用库来抽象出一些DOM细节,也有足够的空间来学习和使用JavaScript语言。

事实上,我认为直接使用DOM可能会教授糟糕的 JavaScript编码习惯,因为DOM非常 a&#34; JavaScript -ish&#34; API。它被设计为在JavaScript和Java以及其他可能的语言中完全相同,因此它完全无法充分利用JavaScript语言的功能。

当然,正如您所说,您将此作为学习练习;我只是不想让你陷入我已经看到许多人陷入困境的陷阱,而且我不想学习jQuery,因为我想学习JavaScript而不是!&#34;这是一个错误的二分法:在任何一种情况下你都必须学习JavaScript,而使用jQuery来解决这个问题根本就没有。

现在有些细节......

虽然可以引用对象文字中的属性名称,但是当您引用属性时,它是习惯性的 - 并且更具可读性 - 当它们是有效的JavaScript名称时不引用它们。例如在您的formElement对象

formElement = { id: {}, name: {}, value: {}, required: {}, type: {} };

(那里也有一个丢失的分号)

以及您可以使用的名称:

formElement.id[i] = ...
formElement.name[i] = ...

除非程序逻辑需要,否则不要向后运行循环。除非可能出现非常严格的循环,否则它不会使代码更快,并且不清楚您是否过早地优化或实际需要向后循环。

说到优化,该循环有几个inArray()次调用。由于每个循环都通过一个数组,这可能比外部循环更具有性能影响。我想这些阵列可能很短?因此,无论如何,性能根本不重要,但是如果你有更长的数组和对象,这是需要考虑的事情。在某些情况下,您可以使用具有属性名称和值的对象来加快查找速度 - 但我并没有仔细研究您正在做什么来提出任何建议。

无论如何,您使用inArray()错了!但不是你的错,这是jQuery中一个荒谬的命名函数。该名称清楚地表明了一个布尔返回值,但是如果找不到该值,该函数将返回从零开始的数组索引或-1。我强烈建议将此功能重命名为indexOf()以匹配原始Array方法,或arrayIndex()或其他类似方法。

同一个循环form[i]重复多次。你可以在循环的顶部做到这一点:

var field = form[i];

然后在整个过程中使用field,例如field.id代替form[i].id。这通常更快,如果它很重要(它可能不会在这里),但更重要的是它更容易阅读。

除非你真的需要,否则不要使用if( foo === true )if( bar === false)等严格的布尔比较 - 这些情况很少见。代码向读者发送一个信号,表明发生的事情与通常的布尔测试有所不同。应该使用这些特定测试的唯一时间是当您有一个可能包含布尔值的变量或者可能包含某个其他类型的值时,您需要区分哪个是哪个。

你应该使用这些测试的一个很好的例子是一个默认为true的可选参数:

// Do stuff unless 'really' is explicitly set to false, e.g.
// stuff(1) will do stuff with 1, but stuff(1,false) won't.
function stuff( value, really ) {
    if( really === false ) {
        // don't do stuff
    }
    else {
        // do stuff
    }
}

这个具体的例子并没有多大意义,但它应该给你一个想法。

类似地,=== true测试可用于需要区分实际布尔true值与其他其他&#34;真理&#34;值。实际上,看起来这一行是一个有效的案例:

if (formElement['required'][i] === true){

假设if (formElement['required'][i]来自getDataAttribute()函数,它可能返回布尔值或其他类型。

如果你只是在测试真实性 - 而且这应该是大部分时间 - 只需使用if( foo )if( ! foo )。或者在条件表达式中类似:foo ? x : y!foo ? x : y

以上是一种冗长的说法,你应该改变这一点:

if (empty_content === false) {

为:

if (!empty_content) {

您的getFormParam()功能可用于将undefined结果转换为null。通常没有理由这样做。我没有看到任何调用该函数的地方,因此我无法专门提供建议,但总的来说,您正在测试此类事件的真实性,因此null和{ {1}}将被视为undefined。或者,如果您确实需要将false / null与其他值(例如,明确的undefined)区分开来,您可以使用false或{{1 }}。这是一个&#34;松散&#34;由!= null== null执行的比较非常有用:==!=对这些运算符的评估相同。

你要求忽略编码风格,但这里有一点建议:你混合了nullundefined。在JavaScript中,camelCaseNames更适用于函数和变量名称,names_with_underscores用于构造函数。当然可以自由地使用下划线,因为它们更有意义,例如,如果您正在编写与该格式的数据库列一起使用的代码,您可能希望您的变量名称与列名匹配。

希望有所帮助!保持良好的工作。

更新新代码

我在跟踪代码中的逻辑时遇到了一些麻烦,我想我知道部分原因。它是命名约定和由内而外的对象组合而成。

首先,名称camelCaseNames确实令人困惑。当我在JavaScript中看到PascalCaseNames时,我想到了DOM元素(HTMLElement)或数组元素。我不确定这个formElement是代表其中一个还是两个都没有。

因此,我查看代码以了解其执行的操作,并且我发现它具有element属性,但代码后来将每个代码视为formElement而不是id:{}, name:{}, ...

Array

(其中Object是整数索引)

如果该代码是正确的,那么它们应该是数组:formElement.id[i] = ... formElement.name[i] = ... formElement.value[i] = ... formElement.required[i] = ... formElement.type[i] = ...

但这是一面红旗。当你看到自己在JavaScript中创建并行数组时,你可能做错了。在大多数情况下,您最好使用单个对象数组替换并行数组。该数组中的每个对象代表所有并行数组的单个切片,并为每个先前的数组提供一个属性。

所以,这个对象(我已经在iid:[], name:[], ...进行了更正以匹配其当前用途):

{}

应该是:

[]

然后你有代码的地方:

formElement = { id: [], name: [], value: [], required: [], type: [] };

应该是:

formInfo = [];

并调整其余代码以适应。例如:

formElement.id[i] = ...;
formElement.name[i] = ...;
formElement.value[i] = ...;
formElement.required[i] = ...;
formElement.type[i] = ...;

将是:

var info = {
    id: ...,
    name: ...,
    value: ...,
    required: ...,
    type: ...
};
formInfo.push( info );

甚至更简单,因为它具有相同的功能:

formElement.required[i]

请注意:我并不是说formInfo[i].required info.required 都是好名字:-)它们只是占位符,所以你可以想到一个更好的名字。主要思想是创建一个对象数组而不是一组并行数组。

最后一件事,然后我暂时没时间了。

info函数是一项复杂的小工作。你不需要它!只需在您需要的地方直接调用底层函数就更简单了:

formInfo

这也使您可以完全控制属性的解释方式 - 如上面的getDataAttribute()测试。 (这会为您提供适当的布尔值,因此当您稍后测试该值时,您不必使用var info = { ... required: formField.getAttribute('data-required') === 'true', type: formField.getAttribute('data-type') }; 。)

在风格上,是的,我在那里对两个=== 'true'名称进行了硬编码,我认为这样做的方式更好,更清晰..不要让你的C经验让你离开这里。定义字符串&#34;常量&#34;没有优势。在这种特殊情况下,除非你想要配置一些东西,否则这不是。

此外,即使你确实使字符串保持不变,拥有完整的=== true字符串而不仅仅是'data-xxxx'也是一个小优势。原因是,当有人读取您的HTML代码时,他们可能会在其中看到一个字符串并在JS代码中搜索该字符串。但是当他们搜索'data-whatever'时,如果'whatever'前缀自动添加到JS代码中,他们就不会找到它。

哦,我忘记了最后一件事。这段代码:

data-whatever

工作太难了!只需这样做:

data-

(当然,保留function Form(){ var args = Array.prototype.slice.call(arguments), formName = args[0], callback = args.pop(), 剩余的变量声明)

答案 1 :(得分:3)

我无法添加评论,所以这里有一点提示。我会将getFormElements()分成更小的私有函数。我会将errorMsgs添加到Form函数中。

但是对于JavaScript中的第一个脚本,它非常令人印象深刻。这实际上是我回应的真正原因。我认为它应该得到更多的赞成,我会对JS忍者回答这个问题非常感兴趣。

祝你好运!