我的目的是在算法的设计,性能和跨浏览器兼容性方面对下面的脚本进行思考和批评.
我刚刚开始进入JavaScript,已经错过了很长一段时间.我的背景和经验是开发基于C/C++/PHP的RESTful后端.
为了理解语言和使用它的正确方法,我决定做一些我确信之前做过很多次的事情.但是学习使用新语言和范例通常会带来痛苦.
这是我尝试创建正常的表单处理和验证脚本/功能.为了降低复杂性并保持代码简单/干净,我决定使用HTML5自定义数据属性(data-*)为表单中的每个元素分配元数据:
数据要求:正确还是错误.如果设置为true,则此参数使表单字段成为必需,因此不能为空.设置为false的值表示该字段是可选的.默认值为false.>
数据类型:要执行的验证类型.示例包括"电子邮件","密码","数字"或任何其他"正则表达式".
这种形式的一个神话般的简单例子是:
提醒:这是我的第一段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." };
当我发布此内容供您审核时,请忽略我可能没有遵循的任何样式约定.我的目的是让我的专家评论我应该做的不同或者可以做更好的代码本身和算法.
除了造型惯例外,欢迎所有的批评和问题.
首先,我想澄清一个常见的误解.如果你已经清楚地理解了这一点,请原谅我; 也许对其他人有帮助.
学习和使用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 )
或if( ! foo )
.或类似地在条件表达式中:foo ? x : y
或!foo ? x : y
.
以上是一种冗长的说法,你应该改变这个:
if (empty_content === false) {
至:
if (!empty_content) {
你的getFormParam()
函数转到了将undefined
结果转换为的一些工作null
.通常没有理由这样做.我没有看到任何调用该函数的地方,所以我不能具体提出建议,但总的来说,你会在这样的事情上测试真实性,所以null
并且undefined
都会被视为false
.或者在您需要区分null
/ undefined
与其他值(例如,显式false
)的情况下,您可以使用!= null
或轻松地执行此操作== null
.这是一种情况,其中"松散"比较由==
并且!=
非常有用:两者null
并且undefined
对这些运算符进行评估.
你问忽略编码风格,但这里一个小建议:你的混合camelCaseNames
和names_with_underscores
.在JavaScript中,camelCaseNames
对于函数和变量名称更具惯用性,PascalCaseNames
对于构造函数.当然可以随意使用下划线更有意义,例如,如果您编写的代码适用于该格式的数据库列,您可能希望变量名称与列名匹配.
希望有所帮助!保持良好的工作.
我在跟踪代码中的逻辑时遇到了一些麻烦,我想我知道部分原因.它是命名约定和由内到外的对象组合而成.
首先,名字formElement
真的令人困惑.当我element
在JavaScript中看到时,我想到了DOM元素(HTMLElement)或数组元素.我不确定这是formElement
代表一个还是另一个或两者都不代表.
所以我看一下代码来弄清楚它在做什么,我看到它有id:{}, name:{}, ...
属性,但代码后来将每个代码视为一个Array
而不是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代码中搜索该字符串.但是当他们搜索时data-whatever
,如果data-
前缀自动添加到JS代码中,他们将无法找到它.
哦,我忘记了最后一件事.这段代码:
function Form(){ var args = Array.prototype.slice.call(arguments), formName = args[0], callback = args.pop(),
工作方式太难了!只需这样做:
function Form( formName, callback ) {
(并保留var
当然剩下的变量声明)