4

关于算法的设计、性能和跨浏览器兼容性,我的目的是听取您对以下脚本的看法和批评。

我刚刚开始接触 JavaScript,已经有一段时间错过了它的美妙之处。我的背景和经验是开发基于 C/C++/PHP 的 RESTful 后端。

为了理解这门语言和正确的使用方法,我决定做一些我确信以前做过很多次的事情。但无论如何,学习使用新的语言和范式通常都会带来痛苦。

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

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

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

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

<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 的要求,则会从配置中提取特定表单值的错误消息。因此,对于所有不满足此要求的值,将收集一组错误消息并将其传递给视图。

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."
};

当我发布此内容供您查看时,请忽略我可能未遵循的任何样式约定。我的目的是让你的专家评论我应该做的任何不同的事情,或者在代码本身和算法方面可以做得更好。

除了样式约定,欢迎所有批评和问题。

4

2 回答 2

9

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

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

事实上,我认为直接使用 DOM 可能会培养出不良的JavaScript 编码习惯,因为 DOM不是一个“JavaScript-ish”的 API。它被设计为在 JavaScript 和 Java 以及可能的其他语言中相同地工作,因此它完全没有很好地利用 JavaScript 语言的特性。

当然,正如您所说,您将其用作学习练习;我只是不想让你落入我看到很多人陷入的陷阱,“我不想学习 jQuery,因为我想学习 JavaScript!” 这是一种错误的二分法:在任何一种情况下,您都必须学习 JavaScript,并且将 jQuery 用于 DOM 根本不会干扰它。

现在一些细节...

虽然可以在对象字面量中引用属性名称并且在引用属性时引用属性,但习惯 - 并且更具可读性 - 当它们是有效的 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值与其他“真实”值区分开来的情况下使用测试。事实上,这条线似乎是一个有效的案例:

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

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

但是,如果您只是在测试真实性(大多数情况下应该如此),只需使用if( foo )or if( ! foo )。或者类似地在条件表达式中:foo ? x : yor !foo ? x : y.

上面是一个冗长的说法,你应该改变这个:

if (empty_content === false) {

到:

if (!empty_content) {

您的getFormParam()函数会进行一些工作以将undefined结果转换为null. 通常没有理由这样做。我没有看到调用该函数的任何地方,所以我不能具体建议,但一般来说,你会在这样的事情上测试真实性,所以nullundefined都会被视为false. 或者,如果您确实需要将null/undefined与其他值区分开来(例如,明确的false),您可以使用!= nullor轻松完成== null==这是由and执行的“更宽松”比较!=非常有用的一种情况:两者nullundefined使用这些运算符进行相同的评估。

你要求忽略编码风格,但这里有一个小建议:你混合了camelCaseNamesnames_with_underscores。在 JavaScript 中,camelCaseNames函数名和变量名更惯用,PascalCaseNames构造函数名更常见。当然,在更有意义的地方随意使用下划线,例如,如果您正在编写与该格式的数据库列一起使用的代码,您可能希望变量名与列名匹配。

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

更新您的新代码

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

首先,这个名字formElement真的很混乱。当我element在 JavaScript 中看到时,我会想到 DOM 元素 (HTMLElement) 或数组元素。我不确定这是否formElement代表一个或另一个或两者都不是。

所以我查看代码以弄清楚它在做什么,我看到它具有id:{}, name:{}, ...属性,但代码稍后将每个属性视为 anArray而不是 an Object

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

(其中i是整数索引)

如果该代码是正确的,那些应该是数组而不是:id:[], name:[], ....

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

所以,这个对象(我已经从{}到进行了更正[]以匹配其当前使用):

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'上面的测试一样。(这为您提供了一个适当的布尔值,因此当您稍后测试该值时,您不必使用=== true它。)

就风格而言,是的,我确实在'data-xxxx'那里对这两个名称进行了硬编码,我认为这是一种更好、更清晰的方法。不要让你的 C 经验让你失望。在这种特殊情况下定义字符串“常量”没有任何好处,除非它是您想要配置的东西,但事实并非如此。

此外,即使您确实将字符串设为常量,使用完整的'data-whatever'字符串而不是仅使用'whatever'. 原因是当有人阅读您的 HTML 代码时,他们可能会在其中看到一个字符串并在 JS 代码中搜索该字符串。但是当他们搜索时,如果前缀自动添加到 JS 代码中,data-whatever他们将找不到它。data-

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

function Form(){

    var args = Array.prototype.slice.call(arguments),
        formName = args[0],
        callback = args.pop(),

工作太努力了!只需这样做:

function Form( formName, callback ) {

var当然保留剩余的变量声明)

于 2013-05-10T17:31:40.243 回答
3

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

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

祝你好运!

于 2013-05-10T15:39:21.250 回答