jquery-validate v1.19.2 源码分析

一、代码主体结构

 二、源码分析

  2.1  外层结构

  

分析步骤

a. 简化结构如下

 1 (function(param){
 2 
 3       console.info(param);
 4 
 5   }( "123" ));
 6 
 7   执行结果:
 8 
 9   123
10 
11   立即执行函数

b. 根据步骤a即可理解外层结构的意思

 1     * 将封装的插件代码函数以参数的形式传入工厂方法, 函数内容根据环境探测结果采用不同方式执行,以兼容多种环境10     
11      * 如果当前执行环境是amd{Asynchronous Module Definition,即异步模块加载机制},则需要添加jquery依赖,回调函数 为传入的factory
12      if ( typeof define === "function" && define.amd ) {
13         define( ["jquery"], factory );
14       }    
15  
16      * 如果当前环境是 CommonJS Modules , 则如下方式引入依赖执行factory
17      if (typeof module === "object" && module.exports) {
18         module.exports = factory( require( "jquery" ) );
19     }
20    
21      * 其他普通浏览器环境直接执行函数
22       factory(jQuery);
23      

 2.2  $.validator 的定义

    

  分析步骤

  a. 简化结构如下

 1 // 定义构造函数
 2 $.validator = function( options, form ) {
 3     // do init method...
 4 };
 5 
 6 // 扩展 $.validator 函数属性,包括原型对象
 7 $.extend( $.validator, {
 8     prop: value,
 9     prototype: {
10         prop: value
11     }
12     // ... other props
13 });

  b.构造函数逻辑代码

1 $.validator = function( options, form ) {
2     // 对当前对象的配置赋值 = 将{}, 默认配置,自定义配置 合并到目标对象 settings 
3     this.settings = $.extend( true, {}, $.validator.defaults, options );
4     // 对当前的form赋值
5     this.currentForm = form;
6     // 初始化当前表单的 validator对象
7     this.init();
8 };

 

  c. 分析$.validator 函数 扩展的属性

  1 // 默认配置,可自定义修改
  2 defaults: {
  3         // 提示消息
  4         messages: {},
  5         // 分组
  6         groups: {},
  7         // 自定义校验规则
  8         rules: {},
  9         // 错误提示的类样式
 10         errorClass: "error",
 11         // 等待的类样式 
 12         pendingClass: "pending",
 13         // 有效的类样式
 14         validClass: "valid",
 15         // 错误提示的元素
 16         errorElement: "label",
 17         // xx
 18         focusCleanup: false,
 19         // xx
 20         focusInvalid: true,
 21         // xx 
 22         errorContainer: $( [] ),
 23         // xx
 24         errorLabelContainer: $( [] ),
 25         // 提交事件之前校验规则 
 26         onsubmit: true,
 27         // 忽略hidden属性
 28         ignore: ":hidden",
 29         // 默认忽略title的校验
 30         ignoreTitle: false,
 31         
 32         // 聚焦,当 focusCleanup 为true时,取消错误高亮效果
 33         onfocusin: function( element ) {
 34             this.lastActive = element;
 35 
 36             // Hide error label and remove error class on focus if enabled
 37             if ( this.settings.focusCleanup ) {
 38                 if ( this.settings.unhighlight ) {
 39                     this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass );
 40                 }
 41                 this.hideThese( this.errorsFor( element ) );
 42             }
 43         },
 44         
 45         // 失去焦点 校验元素
 46         onfocusout: function( element ) {
 47             if ( !this.checkable( element ) && ( element.name in this.submitted || !this.optional( element ) ) ) {
 48                 this.element( element );
 49             }
 50         },
 51         
 52         // 检查键盘事件,是否触发提交 
 53         onkeyup: function( element, event ) {
 54 
 55             // Avoid revalidate the field when pressing one of the following keys
 56             // Shift       => 16
 57             // Ctrl        => 17
 58             // Alt         => 18
 59             // Caps lock   => 20
 60             // End         => 35
 61             // Home        => 36
 62             // Left arrow  => 37
 63             // Up arrow    => 38
 64             // Right arrow => 39
 65             // Down arrow  => 40
 66             // Insert      => 45
 67             // Num lock    => 144
 68             // AltGr key   => 225
 69             var excludedKeys = [
 70                 16, 17, 18, 20, 35, 36, 37,
 71                 38, 39, 40, 45, 144, 225
 72             ];
 73 
 74             if ( event.which === 9 && this.elementValue( element ) === "" || $.inArray( event.keyCode, excludedKeys ) !== -1 ) {
 75                 return;
 76             } else if ( element.name in this.submitted || element.name in this.invalid ) {
 77                 this.element( element );
 78             }
 79         },
 80         
 81         // selects, radio, checkbox , 其他方式的option 元素的 点击事件
 82         onclick: function( element ) {
 83             
 84             // Click on selects, radiobuttons and checkboxes
 85             if ( element.name in this.submitted ) {
 86                 this.element( element );
 87 
 88             // Or option elements, check parent select in that case
 89             } else if ( element.parentNode.name in this.submitted ) {
 90                 this.element( element.parentNode );
 91             }
 92         },
 93         // 高亮效果
 94         highlight: function( element, errorClass, validClass ) {
 95             if ( element.type === "radio" ) {
 96                 this.findByName( element.name ).addClass( errorClass ).removeClass( validClass );
 97             } else {
 98                 $( element ).addClass( errorClass ).removeClass( validClass );
 99             }
100         },
101         
102         // 取消高亮效果
103         unhighlight: function( element, errorClass, validClass ) {
104             if ( element.type === "radio" ) {
105                 this.findByName( element.name ).removeClass( errorClass ).addClass( validClass );
106             } else {
107                 $( element ).removeClass( errorClass ).addClass( validClass );
108             }
109         }
110 }
 1     // 修改掉 $.validator.defaults 默认配置
 2     setDefaults: function( settings ) {
 3         $.extend( $.validator.defaults, settings );
 4     },
 5     
 6     // 类规则设置
 7     classRuleSettings: {
 8         required: { required: true },
 9         email: { email: true },
10         url: { url: true },
11         date: { date: true },
12         dateISO: { dateISO: true },
13         number: { number: true }, // 包含小数
14         digits: { digits: true }, // 数字
15         creditcard: { creditcard: true }
16     },
17     
18     // 默认的规则提示
19     messages: {
20         required: "This field is required.",
21         remote: "Please fix this field.",
22         email: "Please enter a valid email address.",
23         url: "Please enter a valid URL.",
24         date: "Please enter a valid date.",
25         dateISO: "Please enter a valid date (ISO).",
26         number: "Please enter a valid number.",
27         digits: "Please enter only digits.",
28         equalTo: "Please enter the same value again.",
29         maxlength: $.validator.format( "Please enter no more than {0} characters." ),
30         minlength: $.validator.format( "Please enter at least {0} characters." ),
31         rangelength: $.validator.format( "Please enter a value between {0} and {1} characters long." ),
32         range: $.validator.format( "Please enter a value between {0} and {1}." ),
33         max: $.validator.format( "Please enter a value less than or equal to {0}." ),
34         min: $.validator.format( "Please enter a value greater than or equal to {0}." ),
35         step: $.validator.format( "Please enter a multiple of {0}." )
36     },
37 
38     // 自动创建范围 : false
39     autoCreateRanges: false,
 1     //原型函数分析
 2     
 3     // 检验提示错误,错误列表大小
 4     size: function() {
 5         return this.errorList.length;
 6     },
 7     
 8     // 当前大小 = 0, 校验通过
 9     valid: function() {
10         return this.size() === 0;
11     }
12     
13     // 校验错误的个数
14     numberOfInvalids: function() {
15         return this.objectLength( this.invalid );
16     },
17     
18     // 返回传入对象长度
19     objectLength: function( obj ) {
20         /* jshint unused: false */
21         var count = 0,
22             i;
23         for ( i in obj ) {
24     
25             // This check allows counting elements with empty error
26             // message as invalid elements
27             if ( obj[ i ] !== undefined && obj[ i ] !== null && obj[ i ] !== false ) {
28                 count++;
29             }
30         }
31         return count;
32     },
33     
34     // 聚焦到最后一个错误提示的元素
35     focusInvalid: function() {
36         if ( this.settings.focusInvalid ) {
37             try {
38                 $( this.findLastActive() || this.errorList.length && this.errorList[ 0 ].element || [] )
39                 .filter( ":visible" )
40                 .trigger( "focus" )
41     
42                 // Manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
43                 .trigger( "focusin" );
44             } catch ( e ) {
45     
46                 // Ignore IE throwing errors when focusing hidden elements
47             }
48         }
49     },
50     
51     // 查找最后一个元素active
52     findLastActive: function() {
53         var lastActive = this.lastActive;
54         return lastActive && $.grep( this.errorList, function( n ) {
55             return n.element.name === lastActive.name;
56         } ).length === 1 && lastActive;
57     },
 1 // xx
 2     clean: function( selector ) {
 3         return $( selector )[ 0 ];
 4     },
 5     
 6     // 错误样式 
 7     errors: function() {
 8         var errorClass = this.settings.errorClass.split( " " ).join( "." );
 9         return $( this.settings.errorElement + "." + errorClass, this.errorContext );
10     },
11     
12     // 重置 
13     resetInternals: function() {
14         this.successList = [];
15         this.errorList = [];
16         this.errorMap = {};
17         this.toShow = $( [] );
18         this.toHide = $( [] );
19     },
20     
21     // 重置
22     reset: function() {
23         this.resetInternals();
24         this.currentElements = $( [] );
25     },
26     
27     // form 准备
28     prepareForm: function() {
29         this.reset();
30         this.toHide = this.errors().add( this.containers );
31     },
32     
33     // 元素准备
34     prepareElement: function( element ) {
35         this.reset();
36         this.toHide = this.errorsFor( element );
37     },
38     
39     //  从错误列表里过滤出元素
40     errorsFor: function( element ) {
41         var name = this.escapeCssMeta( this.idOrName( element ) ),
42             describer = $( element ).attr( "aria-describedby" ),
43             selector = "label[for='" + name + "'], label[for='" + name + "'] *";
44     
45         // 'aria-describedby' should directly reference the error element
46         if ( describer ) {
47             selector = selector + ", #" + this.escapeCssMeta( describer )
48                 .replace( /s+/g, ", #" );
49         }
50         
51         // [1,23,3].filter( function(i ) { return  i == 23;}); // 从数组中过滤
52         
53         return this
54             .errors()
55             .filter( selector );
56     },
57     
58     // 正则替换字符串返回结果
59     escapeCssMeta: function( string ) {
60         return string.replace( /([\!"#$%&'()*+,./:;<=>?@[]^`{|}~])/g, "\$1" );
61     },
62     
63     // 获取元素的id或name值
64     idOrName: function( element ) {
65         return this.groups[ element.name ] || ( this.checkable( element ) ? element.name : element.id || element.name );
66     },
  1     // 返回元素的message【自定义或默认】
  2     customDataMessage: function( element, method ) {
  3         return $( element ).data( "msg" + method.charAt( 0 ).toUpperCase() +
  4             method.substring( 1 ).toLowerCase() ) || $( element ).data( "msg" );
  5     },
  6     
  7     // 通过名称读取message
  8     customMessage: function( name, method ) {
  9         var m = this.settings.messages[ name ];
 10         return m && ( m.constructor === String ? m : m[ method ] );
 11     },
 12     
 13     // 返回第一个非undefined的参数值
 14     findDefined: function() {
 15         for ( var i = 0; i < arguments.length; i++ ) {
 16             if ( arguments[ i ] !== undefined ) {
 17                 return arguments[ i ];
 18             }
 19         }
 20         return undefined;
 21     },
 22     
 23     // The second parameter 'rule' used to be a string, and extended to an object literal
 24     // of the following form:
 25     // rule = {
 26     //     method: "method name",
 27     //     parameters: "the given method parameters"
 28     // }
 29     // 现在还支持老的行为(rule == string),下一个版本没了
 30     // The old behavior still supported, kept to maintain backward compatibility with
 31     // old code, and will be removed in the next major release. ; 
 32     defaultMessage: function( element, rule ) {
 33         if ( typeof rule === "string" ) {
 34             rule = { method: rule };
 35         }
 36         // 按次序读取message,直到找到一个非undefined的message
 37         var message = this.findDefined(
 38                 this.customMessage( element.name, rule.method ),
 39                 this.customDataMessage( element, rule.method ),
 40     
 41                 // 'title' is never undefined, so handle empty string as undefined
 42                 !this.settings.ignoreTitle && element.title || undefined,
 43                 $.validator.messages[ rule.method ],
 44                 "<strong>Warning: No message defined for " + element.name + "</strong>"
 45             ),
 46             theregex = /$?{(d+)}/g;
 47         if ( typeof message === "function" ) {
 48             message = message.call( this, rule.parameters, element );
 49         } else if ( theregex.test( message ) ) {
 50             message = $.validator.format( message.replace( theregex, "{$1}" ), rule.parameters );
 51         }
 52     
 53         return message;
 54     },
 55     
 56     // 通过元素和规则格式化message,放到错误列表
 57     formatAndAdd: function( element, rule ) {
 58         var message = this.defaultMessage( element, rule );
 59     
 60         this.errorList.push( {
 61             message: message,
 62             element: element,
 63             method: rule.method
 64         } );
 65     
 66         this.errorMap[ element.name ] = message;
 67         this.submitted[ element.name ] = message;
 68     },
 69     
 70     // 检查是否有包裹层
 71     addWrapper: function( toToggle ) {
 72         if ( this.settings.wrapper ) {
 73             toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) );
 74         }
 75         return toToggle;
 76     },
 77     
 78     // 显示错误提示 
 79     defaultShowErrors: function() {
 80         var i, elements, error;
 81         // 迭代errorlist,显示错误提示
 82         for ( i = 0; this.errorList[ i ]; i++ ) {
 83             error = this.errorList[ i ];
 84             if ( this.settings.highlight ) {
 85                 this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass );
 86             }
 87             this.showLabel( error.element, error.message );
 88         }
 89         
 90         // 如果错误个数大于0 ; xx
 91         if ( this.errorList.length ) {
 92             this.toShow = this.toShow.add( this.containers );
 93         }
 94         
 95         // 如果setting.success 为true,显示正常校验的提示
 96         if ( this.settings.success ) {
 97             for ( i = 0; this.successList[ i ]; i++ ) {
 98                 this.showLabel( this.successList[ i ] );
 99             }
100         }
101         
102         // 校验通过的元素取消高亮效果
103         if ( this.settings.unhighlight ) {
104             for ( i = 0, elements = this.validElements(); elements[ i ]; i++ ) {
105                 this.settings.unhighlight.call( this, elements[ i ], this.settings.errorClass, this.settings.validClass );
106             }
107         }
108         
109         //  $([11,2,3]).not([2,3]); console.info(a.toArray());
110         this.toHide = this.toHide.not( this.toShow );
111         
112         // xx
113         this.hideErrors();
114         
115         // xx
116         this.addWrapper( this.toShow ).show();
117     },
118     
119     showLabel: function( element, message ) {
120         var place, group, errorID, v,
121             error = this.errorsFor( element ),
122             elementID = this.idOrName( element ),
123             describedBy = $( element ).attr( "aria-describedby" );
124     
125         if ( error.length ) {
126     
127             // Refresh error/success class 干掉有效的样式添加错误提示的样式
128             error.removeClass( this.settings.validClass ).addClass( this.settings.errorClass );
129     
130             // Replace message on existing label 替换错误提示
131             error.html( message );
132         } else {
133     
134             // Create error element 创建错误提示元素
135             error = $( "<" + this.settings.errorElement + ">" )
136                 .attr( "id", elementID + "-error" )
137                 .addClass( this.settings.errorClass )
138                 .html( message || "" );
139     
140             // Maintain reference to the element to be placed into the DOM
141             place = error;
142             if ( this.settings.wrapper ) {
143     
144                 // Make sure the element is visible, even in IE
145                 // actually showing the wrapped element is handled elsewhere
146                 place = error.hide().show().wrap( "<" + this.settings.wrapper + "/>" ).parent();
147             }
148             
149             // 插入提示文本
150             if ( this.labelContainer.length ) {
151                 this.labelContainer.append( place );
152             } else if ( this.settings.errorPlacement ) {
153                 // 自定义 错误替换 函数
154                 this.settings.errorPlacement.call( this, place, $( element ) );
155             } else {
156                 place.insertAfter( element );
157             }
158     
159             // link 错误到元素
160             if ( error.is( "label" ) ) {
161     
162                 // If the error is a label, then associate using 'for'
163                 error.attr( "for", elementID );
164     
165                 // If the element is not a child of an associated label, then it's necessary
166                 // to explicitly apply aria-describedby
167             } else if ( error.parents( "label[for='" + this.escapeCssMeta( elementID ) + "']" ).length === 0 ) {
168                 errorID = error.attr( "id" );
169     
170                 // Respect existing non-error aria-describedby
171                 if ( !describedBy ) {
172                     describedBy = errorID;
173                 } else if ( !describedBy.match( new RegExp( "\b" + this.escapeCssMeta( errorID ) + "\b" ) ) ) {
174     
175                     // Add to end of list if not already present
176                     describedBy += " " + errorID;
177                 }
178                 $( element ).attr( "aria-describedby", describedBy );
179     
180                 // If this element is grouped, then assign to all elements in the same group
181                 group = this.groups[ element.name ];
182                 if ( group ) {
183                     v = this;
184                     $.each( v.groups, function( name, testgroup ) {
185                         if ( testgroup === group ) {
186                             $( "[name='" + v.escapeCssMeta( name ) + "']", v.currentForm )
187                                 .attr( "aria-describedby", error.attr( "id" ) );
188                         }
189                     } );
190                 }
191             }
192         }
193         // 定义success的时候,
194         if ( !message && this.settings.success ) {
195             error.text( "" );
196             if ( typeof this.settings.success === "string" ) {
197                 // string时 添加样式 
198                 error.addClass( this.settings.success );
199             } else {
200                 // function时,调用函数
201                 this.settings.success( error, element );
202             }
203         }
204         this.toShow = this.toShow.add( error );
205     },
 1         // 从当前元素中去掉校验不通过的元素
 2         validElements: function() {
 3             return this.currentElements.not( this.invalidElements() );
 4         },
 5         
 6         // 从errorList中返回 校验不通过的元素集合
 7         invalidElements: function() {
 8             return $( this.errorList ).map( function() {
 9                 return this.element;
10             } );
11         },
12 
13         // 校验目标 
14         validationTargetFor: function( element ) {
15 
16             // If radio/checkbox, validate first element in group instead 如果元素是radio,checkbox;查找组
17             if ( this.checkable( element ) ) {
18                 element = this.findByName( element.name );
19             }
20 
21             // Always apply ignore filter
22             return $( element ).not( this.settings.ignore )[ 0 ];
23         },
24 
25         // 检查是否是radio或checkbox
26         checkable: function( element ) {
27             return ( /radio|checkbox/i ).test( element.type );
28         },
29 
30         // 表单元素查找
31         findByName: function( name ) {
32             return $( this.currentForm ).find( "[name='" + this.escapeCssMeta( name ) + "']" );
33         },
34         
35         // 获取元素节点的长度
36         getLength: function( value, element ) {
37             switch ( element.nodeName.toLowerCase() ) {
38             case "select":
39                 // 下拉
40                 return $( "option:selected", element ).length;
41             case "input":
42                 if ( this.checkable( element ) ) { 
43                     // radio checkbox
44                     return this.findByName( element.name ).filter( ":checked" ).length;
45                 }
46             }
47             // 普通的表单
48             return value.length;
49         },
50 
51         // 
52         depend: function( param, element ) {
53             return this.dependTypes[ typeof param ] ? this.dependTypes[ typeof param ]( param, element ) : true;
54         },
55         
56         // 类型依赖    
57         dependTypes: {
58             "boolean": function( param ) {
59                 return param;
60             },
61             "string": function( param, element ) {
62                 return !!$( param, element.form ).length;
63             },
64             "function": function( param, element ) {
65                 return param( element );
66             }
67         },
68 
69         // 非必填项 
70         optional: function( element ) {
71             var val = this.elementValue( element );
72             return !$.validator.methods.required.call( this, val, element ) && "dependency-mismatch";
73         },
 1         // 构建errorlist和successlist 调用函数显示错误label
 2         showErrors: function( errors ) {
 3             if ( errors ) {
 4                 var validator = this;
 5         
 6                 // Add items to error list and map
 7                 $.extend( this.errorMap, errors );
 8                 this.errorList = $.map( this.errorMap, function( message, name ) {
 9                     return {
10                         message: message,
11                         element: validator.findByName( name )[ 0 ]
12                     };
13                 } );
14         
15                 // Remove items from success list
16                 this.successList = $.grep( this.successList, function( element ) {
17                     return !( element.name in errors );
18                 } );
19             }
20             if ( this.settings.showErrors ) {
21                 this.settings.showErrors.call( this, this.errorMap, this.errorList );
22             } else {
23                 this.defaultShowErrors();
24             }
25         },
26         
27         // 检测$.fn.resetForm 是否存在
28         resetForm: function() {
29             if ( $.fn.resetForm ) {
30                 $( this.currentForm ).resetForm();
31             }
32             this.invalid = {};
33             this.submitted = {};
34             this.prepareForm();
35             this.hideErrors();
36             var elements = this.elements()
37                 .removeData( "previousValue" )
38                 .removeAttr( "aria-invalid" );
39             
40             this.resetElements( elements );
41         },
42         
43         // 重置元素提示样式
44         resetElements: function( elements ) {
45             var i;
46         
47             if ( this.settings.unhighlight ) {
48                 for ( i = 0; elements[ i ]; i++ ) {
49                     this.settings.unhighlight.call( this, elements[ i ],
50                         this.settings.errorClass, "" );
51                     this.findByName( elements[ i ].name ).removeClass( this.settings.validClass );
52                 }
53             } else {
54                 elements
55                     .removeClass( this.settings.errorClass )
56                     .removeClass( this.settings.validClass );
57             }
58         },
59         
60         // 隐藏错误
61         hideErrors: function() {
62             this.hideThese( this.toHide );
63         },
64         
65         // xx
66         hideThese: function( errors ) {
67             errors.not( this.containers ).text( "" );
68             this.addWrapper( errors ).hide();
69         },
  1 // 规范化所有规则
  2         normalizeRules: function( rules, element ) {
  3             
  4             // 迭代rules
  5             $.each( rules, function( prop, val ) {
  6         
  7                 // 当规则值为false,非必填项,规则去除
  8                 if ( val === false ) {
  9                     delete rules[ prop ];
 10                     return;
 11                 }
 12                 // 检查参数是否是否有依赖
 13                 if ( val.param || val.depends ) {
 14                     var keepRule = true;
 15                     switch ( typeof val.depends ) {
 16                     case "string":
 17                         keepRule = !!$( val.depends, element.form ).length;
 18                         break;
 19                     case "function":
 20                         keepRule = val.depends.call( element, element );
 21                         break;
 22                     }
 23                     // 规则有效用规则,规则无效删除规则
 24                     if ( keepRule ) {
 25                         rules[ prop ] = val.param !== undefined ? val.param : true;
 26                     } else {
 27                         $.data( element.form, "validator" ).resetElements( $( element ) );
 28                         delete rules[ prop ];
 29                     }
 30                 }
 31             } );
 32         
 33             // Evaluate parameters
 34             $.each( rules, function( rule, parameter ) {
 35                 rules[ rule ] = $.isFunction( parameter ) && rule !== "normalizer" ? parameter( element ) : parameter;
 36             } );
 37         
 38             // 格式化数字 最大长度,最小长度
 39             $.each( [ "minlength", "maxlength" ], function() {
 40                 if ( rules[ this ] ) {
 41                     rules[ this ] = Number( rules[ this ] );
 42                 }
 43             } );
 44             
 45             // 范围规则
 46             $.each( [ "rangelength", "range" ], function() {
 47                 var parts;
 48                 if ( rules[ this ] ) {
 49                     if ( $.isArray( rules[ this ] ) ) {
 50                         rules[ this ] = [ Number( rules[ this ][ 0 ] ), Number( rules[ this ][ 1 ] ) ];
 51                     } else if ( typeof rules[ this ] === "string" ) {
 52                         parts = rules[ this ].replace( /[[]]/g, "" ).split( /[s,]+/ );
 53                         rules[ this ] = [ Number( parts[ 0 ] ), Number( parts[ 1 ] ) ];
 54                     }
 55                 }
 56             } );
 57         
 58             // 是否自动创建范围规则, 是: 删除大小规则,创建范围规则
 59             if ( $.validator.autoCreateRanges ) {
 60         
 61                 // Auto-create ranges
 62                 if ( rules.min != null && rules.max != null ) {
 63                     rules.range = [ rules.min, rules.max ];
 64                     delete rules.min;
 65                     delete rules.max;
 66                 }
 67                 if ( rules.minlength != null && rules.maxlength != null ) {
 68                     rules.rangelength = [ rules.minlength, rules.maxlength ];
 69                     delete rules.minlength;
 70                     delete rules.maxlength;
 71                 }
 72             }
 73         
 74             return rules;
 75         },
 76         
 77         // 规范化rule, e.g., "required" to {required:true}
 78         normalizeRule: function( data ) {
 79             if ( typeof data === "string" ) {
 80                 var transformed = {};
 81                 $.each( data.split( /s/ ), function() {
 82                     transformed[ this ] = true;
 83                 } );
 84                 data = transformed;
 85             }
 86             return data;
 87         },
 88         
 89         // 元素
 90         element: function( element ) {
 91             var cleanElement = this.clean( element ),
 92                 checkElement = this.validationTargetFor( cleanElement ),
 93                 v = this,
 94                 result = true,
 95                 rs, group;
 96             
 97             if ( checkElement === undefined ) {
 98                 delete this.invalid[ cleanElement.name ];
 99             } else {
100                 this.prepareElement( checkElement );
101                 this.currentElements = $( checkElement );
102 
103                 // If this element is grouped, then validate all group elements already
104                 // containing a value
105                 group = this.groups[ checkElement.name ];
106                 if ( group ) {
107                     $.each( this.groups, function( name, testgroup ) {
108                         if ( testgroup === group && name !== checkElement.name ) {
109                             cleanElement = v.validationTargetFor( v.clean( v.findByName( name ) ) );
110                             if ( cleanElement && cleanElement.name in v.invalid ) {
111                                 v.currentElements.push( cleanElement );
112                                 result = v.check( cleanElement ) && result;
113                             }
114                         }
115                     } );
116                 }
117 
118                 rs = this.check( checkElement ) !== false;
119                 result = result && rs;
120                 if ( rs ) {
121                     this.invalid[ checkElement.name ] = false;
122                 } else {
123                     this.invalid[ checkElement.name ] = true;
124                 }
125 
126                 if ( !this.numberOfInvalids() ) {
127 
128                     // Hide error containers on last error
129                     this.toHide = this.toHide.add( this.containers );
130                 }
131                 this.showErrors();
132 
133                 // Add aria-invalid status for screen readers
134                 $( element ).attr( "aria-invalid", !rs );
135             }
136 
137             return result;
138         },
139         
140         // 迭代表单元素
141         elements: function() {
142             var validator = this,
143                 rulesCache = {};
144 
145             // 查找通过的元素不包含submit和reset按纽, image,dissabled, 校验外元素
146             return $( this.currentForm )
147             .find( "input, select, textarea, [contenteditable]" )
148             .not( ":submit, :reset, :image, :disabled" )
149             .not( this.settings.ignore )
150             .filter( function() {
151                 var name = this.name || $( this ).attr( "name" ); // For contenteditable
152                 var isContentEditable = typeof $( this ).attr( "contenteditable" ) !== "undefined" && $( this ).attr( "contenteditable" ) !== "false";
153 
154                 if ( !name && validator.settings.debug && window.console ) {
155                     console.error( "%o has no name assigned", this );
156                 }
157 
158                 // Set form expando on contenteditable
159                 if ( isContentEditable ) {
160                     // 找到当前元素的最接近的父节点form元素
161                     this.form = $( this ).closest( "form" )[ 0 ];
162                     this.name = name;
163                 }
164 
165                 // Ignore elements that belong to other/nested forms
166                 if ( this.form !== validator.currentForm ) {
167                     return false;
168                 }
169 
170                 // Select only the first element for each name, and only those with rules specified
171                 if ( name in rulesCache || !validator.objectLength( $( this ).rules() ) ) {
172                     return false;
173                 }
174 
175                 rulesCache[ name ] = true;
176                 return true;
177             } );
178         },
179 
180         elementValue: function( element ) {
181             var $element = $( element ),
182                 type = element.type,
183                 isContentEditable = typeof $element.attr( "contenteditable" ) !== "undefined" && $element.attr( "contenteditable" ) !== "false",
184                 val, idx;
185 
186             if ( type === "radio" || type === "checkbox" ) {
187                 // radio或checkbox
188                 return this.findByName( element.name ).filter( ":checked" ).val();
189             } else if ( type === "number" && typeof element.validity !== "undefined" ) {
190                 // 数字的情况下,返回元素的值是否正确
191                 return element.validity.badInput ? "NaN" : $element.val();
192             }
193 
194             if ( isContentEditable ) {
195                 // 文本编辑器 获取文本内容 text
196                 val = $element.text();
197             } else {
198                 // 获取元素值
199                 val = $element.val();
200             }
201             
202             // 文件类型,获取文件名,读取文件最后一个/
203             if ( type === "file" ) {
204 
205                 // Modern browser (chrome & safari)
206                 if ( val.substr( 0, 12 ) === "C:\fakepath\" ) {
207                     return val.substr( 12 );
208                 }
209 
210                 // Legacy browsers
211                 // Unix-based path
212                 idx = val.lastIndexOf( "/" );
213                 if ( idx >= 0 ) {
214                     return val.substr( idx + 1 );
215                 }
216 
217                 // Windows-based path
218                 idx = val.lastIndexOf( "\" );
219                 if ( idx >= 0 ) {
220                     return val.substr( idx + 1 );
221                 }
222 
223                 // Just the file name
224                 return val;
225             }
226             
227             // 如果是字符串类型,去掉回车符
228             if ( typeof val === "string" ) {
229                 return val.replace( /
/g, "" );
230             }
231             return val;
232         },
233         
234         validationTargetFor: function( element ) {
235             // If radio/checkbox, validate first element in group instead
236             if ( this.checkable( element ) ) {
237                 element = this.findByName( element.name );
238             }
239             // Always apply ignore filter
240             return $( element ).not( this.settings.ignore )[ 0 ];
241         },
242         
243         // 检查 校验元素
244         check: function( element ) {
245             element = this.validationTargetFor( this.clean( element ) );
246 
247             var rules = $( element ).rules(),
248                 rulesCount = $.map( rules, function( n, i ) {
249                     return i;
250                 } ).length,
251                 dependencyMismatch = false,
252                 val = this.elementValue( element ),
253                 result, method, rule, normalizer;
254 
255             // Prioritize the local normalizer defined for this element over the global one
256             // if the former exists, otherwise user the global one in case it exists.
257             if ( typeof rules.normalizer === "function" ) {
258                 normalizer = rules.normalizer;
259             } else if (    typeof this.settings.normalizer === "function" ) {
260                 normalizer = this.settings.normalizer;
261             }
262 
263             // If normalizer is defined, then call it to retreive the changed value instead
264             // of using the real one.
265             // Note that `this` in the normalizer is `element`.
266             if ( normalizer ) {
267                 val = normalizer.call( element, val );
268 
269                 // Delete the normalizer from rules to avoid treating it as a pre-defined method.
270                 delete rules.normalizer;
271             }
272 
273             for ( method in rules ) {
274                 rule = { method: method, parameters: rules[ method ] };
275                 try {
276                     // 重要
277                     result = $.validator.methods[ method ].call( this, val, element, rule.parameters );
278 
279                     // If a method indicates that the field is optional and therefore valid,
280                     // don't mark it as valid when there are no other rules
281                     if ( result === "dependency-mismatch" && rulesCount === 1 ) {
282                         dependencyMismatch = true;
283                         continue;
284                     }
285                     dependencyMismatch = false;
286 
287                     if ( result === "pending" ) {
288                         this.toHide = this.toHide.not( this.errorsFor( element ) );
289                         return;
290                     }
291 
292                     if ( !result ) {
293                         this.formatAndAdd( element, rule );
294                         return false;
295                     }
296                 } catch ( e ) {
297                     if ( this.settings.debug && window.console ) {
298                         console.log( "Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e );
299                     }
300                     if ( e instanceof TypeError ) {
301                         e.message += ".  Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.";
302                     }
303 
304                     throw e;
305                 }
306             }
307             if ( dependencyMismatch ) {
308                 return;
309             }
310             if ( this.objectLength( rules ) ) {
311                 this.successList.push( element );
312             }
313             return true;
314         },
 1         form: function() {
 2             this.checkForm();
 3             $.extend( this.submitted, this.errorMap );
 4             this.invalid = $.extend( {}, this.errorMap );
 5             if ( !this.valid() ) {
 6                 $( this.currentForm ).triggerHandler( "invalid-form", [ this ] );
 7             }
 8             this.showErrors();
 9             return this.valid();
10         },
11         
12         // 校验form表单所有元素,获取校验结果 当 this.valid()> 0的时候,表示当前的form表单校验不通过
13         checkForm: function() {
14             this.prepareForm();
15             for ( var i = 0, elements = ( this.currentElements = this.elements() ); elements[ i ]; i++ ) {
16                 this.check( elements[ i ] );
17             }
18             return this.valid();
19         },
20         
21         // 旧值
22         previousValue: function( element, method ) {
23             method = typeof method === "string" && method || "remote";
24         
25             return $.data( element, "previousValue" ) || $.data( element, "previousValue", {
26                 old: null,
27                 valid: true,
28                 message: this.defaultMessage( element, { method: method } )
29             } );
30         },
31         
32         // Cleans up all forms and elements, removes validator-specific events 重置
33         destroy: function() {
34             this.resetForm();
35         
36             $( this.currentForm )
37                 .off( ".validate" )
38                 .removeData( "validator" )
39                 .find( ".validate-equalTo-blur" )
40                     .off( ".validate-equalTo" )
41                     .removeClass( "validate-equalTo-blur" )
42                 .find( ".validate-lessThan-blur" )
43                     .off( ".validate-lessThan" )
44                     .removeClass( "validate-lessThan-blur" )
45                 .find( ".validate-lessThanEqual-blur" )
46                     .off( ".validate-lessThanEqual" )
47                     .removeClass( "validate-lessThanEqual-blur" )
48                 .find( ".validate-greaterThanEqual-blur" )
49                     .off( ".validate-greaterThanEqual" )
50                     .removeClass( "validate-greaterThanEqual-blur" )
51                 .find( ".validate-greaterThan-blur" )
52                     .off( ".validate-greaterThan" )
53                     .removeClass( "validate-greaterThan-blur" );
54         }
 1 init: function() {
 2     this.labelContainer = $(this.settings.errorLabelContainer);
 3     this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
 4     this.containers = $(this.settings.errorContainer).add(this.settings.errorLabelContainer);
 5     this.submitted = {};
 6     this.valueCache = {};
 7     this.pendingRequest = 0;
 8     this.pending = {};
 9     this.invalid = {};
10     this.reset();
11 
12     var currentForm = this.currentForm,
13         groups = (this.groups = {}),
14         rules;
15     $.each(this.settings.groups, function(key, value) {
16         if (typeof value === "string") {
17             value = value.split(/s/);
18         }
19         $.each(value, function(index, name) {
20             groups[name] = key;
21         });
22     });
23     
24     // 格式化规则
25     rules = this.settings.rules;
26     $.each(rules, function(key, value) {
27         rules[key] = $.validator.normalizeRule(value);
28     });
29     
30     // 事件绑定
31     function delegate(event) {
32         var isContentEditable = typeof $(this).attr("contenteditable") !== "undefined" && $(this).attr("contenteditable") !==
33             "false";
34 
35         // Set form expando on contenteditable
36         if (!this.form && isContentEditable) {
37             this.form = $(this).closest("form")[0];
38             this.name = $(this).attr("name");
39         }
40 
41         // Ignore the element if it belongs to another form. This will happen mainly
42         // when setting the `form` attribute of an input to the id of another form.
43         if (currentForm !== this.form) {
44             return;
45         }
46 
47         var validator = $.data(this.form, "validator"),
48         // 如:onfocusin , 查看onfocusin实现逻辑
49             eventType = "on" + event.type.replace(/^validate/, ""),
50             settings = validator.settings;
51         if (settings[eventType] && !$(this).is(settings.ignore)) {
52             settings[eventType].call(validator, this, event);
53         }
54     }
55     
56     // 给未来的元素[ [type=xx]表单元素 ] 绑定事件 onfocusin, onfocusout , onkeyup
57     $(this.currentForm)
58         .on("focusin.validate focusout.validate keyup.validate",
59             ":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], " +
60             "[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], " +
61             "[type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], " +
62             "[type='radio'], [type='checkbox'], [contenteditable], [type='button']" delegate)
63 
64         // Support: Chrome, oldIE
65         // "select" is provided as event.target when clicking a option 兼容IE,chrome,绑定click, select事件
66         .on("click.validate", "select, option, [type='radio'], [type='checkbox']", delegate);
67 
68     if (this.settings.invalidHandler) {
69         $(this.currentForm).on("invalid-form.validate", this.settings.invalidHandler);
70     }
71 }

  d. 默认的校验规则函数

  1 // 规则校验函数
  2 
  3         check: function(){
  4                 ...
  5                 result = $.validator.methods[ method ].call( this, val, element, rule.parameters );
  6                 ...
  7         }
  8         
  9          
 10         // https://jqueryvalidation.org/jQuery.validator.methods/
 11         methods: {
 12             
 13             // required 校验规则
 14             required: function( value, element, param ) {
 15         
 16                 // Check if dependency is met    
 17                 if ( !this.depend( param, element ) ) {
 18                     return "dependency-mismatch";
 19                 }
 20                 // 检查元素节点是否是select
 21                 if ( element.nodeName.toLowerCase() === "select" ) {
 22         
 23                     // Could be an array for select-multiple or a string, both are fine this way
 24                     var val = $( element ).val();
 25                     // 已选中至少一个值, 且值长度大于0
 26                     return val && val.length > 0;
 27                 }
 28                 // 检查元素是否是checkable[radio,checkbox]元素 至少一个值
 29                 if ( this.checkable( element ) ) {
 30                     return this.getLength( value, element ) > 0;
 31                 }
 32                 // 检查元素值存在,且值长度大于0
 33                 return value !== undefined && value !== null && value.length > 0;
 34             },
 35         
 36             // email  校验规则
 37             email: function( value, element ) {    
 38         
 39                 // From https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address
 40                 // Retrieved 2014-01-14 
 41                 // If you have a problem with this implementation, report a bug against the above spec
 42                 // Or use custom methods to implement your own email validation
 43                 return this.optional( element ) || /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test( value );
 44             },
 45         
 46             // url校验规则
 47             url: function( value, element ) {
 48         
 49                 // Copyright (c) 2010-2013 Diego Perini, MIT licensed
 50                 // https://gist.github.com/dperini/729294
 51                 // see also https://mathiasbynens.be/demo/url-regex
 52                 // modified to allow protocol-relative URLs
 53                 return this.optional( element ) || /^(?:(?:(?:https?|ftp):)?//)(?:S+(?::S*)?@)?(?:(?!(?:10|127)(?:.d{1,3}){3})(?!(?:169.254|192.168)(?:.d{1,3}){2})(?!172.(?:1[6-9]|2d|3[0-1])(?:.d{1,3}){2})(?:[1-9]d?|1dd|2[01]d|22[0-3])(?:.(?:1?d{1,2}|2[0-4]d|25[0-5])){2}(?:.(?:[1-9]d?|1dd|2[0-4]d|25[0-4]))|(?:(?:[a-zu00a1-uffff0-9]-*)*[a-zu00a1-uffff0-9]+)(?:.(?:[a-zu00a1-uffff0-9]-*)*[a-zu00a1-uffff0-9]+)*(?:.(?:[a-zu00a1-uffff]{2,})).?)(?::d{2,5})?(?:[/?#]S*)?$/i.test( value );
 54             },
 55         
 56             // 日期规则
 57             date: ( function() {
 58                 var called = false;
 59         
 60                 return function( value, element ) {
 61                     if ( !called ) {
 62                         called = true;
 63                         if ( this.settings.debug && window.console ) {
 64                             console.warn(
 65                                 "The `date` method is deprecated and will be removed in version '2.0.0'.
" +
 66                                 "Please don't use it, since it relies on the Date constructor, which
" +
 67                                 "behaves very differently across browsers and locales. Use `dateISO`
" +
 68                                 "instead or one of the locale specific methods in `localizations/`
" +
 69                                 "and `additional-methods.js`."
 70                             );
 71                         }
 72                     }
 73         
 74                     return this.optional( element ) || !/Invalid|NaN/.test( new Date( value ).toString() );
 75                 };
 76             }() ),
 77         
 78             // https://jqueryvalidation.org/dateISO-method/
 79             dateISO: function( value, element ) {
 80                 return this.optional( element ) || /^d{4}[/-](0?[1-9]|1[012])[/-](0?[1-9]|[12][0-9]|3[01])$/.test( value );
 81             },
 82         
 83             // 数字 包括小数
 84             number: function( value, element ) {
 85                 return this.optional( element ) || /^(?:-?d+|-?d{1,3}(?:,d{3})+)?(?:.d+)?$/.test( value );
 86             },
 87         
 88             // 整数
 89             digits: function( value, element ) {
 90                 return this.optional( element ) || /^d+$/.test( value );
 91             },
 92         
 93             // 字符最小长度
 94             minlength: function( value, element, param ) {
 95                 var length = $.isArray( value ) ? value.length : this.getLength( value, element );
 96                 return this.optional( element ) || length >= param;
 97             },
 98         
 99             // 字符最大长度
100             maxlength: function( value, element, param ) {
101                 var length = $.isArray( value ) ? value.length : this.getLength( value, element );
102                 return this.optional( element ) || length <= param;
103             },
104         
105             // 范围长度
106             rangelength: function( value, element, param ) {
107                 var length = $.isArray( value ) ? value.length : this.getLength( value, element );
108                 return this.optional( element ) || ( length >= param[ 0 ] && length <= param[ 1 ] );
109             },
110         
111             // 最小值
112             min: function( value, element, param ) {
113                 return this.optional( element ) || value >= param;
114             },
115         
116             // 最大值
117             max: function( value, element, param ) {
118                 return this.optional( element ) || value <= param;
119             },
120         
121             // 范围值
122             range: function( value, element, param ) {
123                 return this.optional( element ) || ( value >= param[ 0 ] && value <= param[ 1 ] );
124             },
125         
126             step: function( value, element, param ) {
127                 var type = $( element ).attr( "type" ),
128                     errorMessage = "Step attribute on input type " + type + " is not supported.",
129                     supportedTypes = [ "text", "number", "range" ],
130                     re = new RegExp( "\b" + type + "\b" ),
131                     notSupported = type && !re.test( supportedTypes.join() ),
132                     decimalPlaces = function( num ) {
133                         var match = ( "" + num ).match( /(?:.(d+))?$/ );
134                         if ( !match ) {
135                             return 0;
136                         }
137         
138                         // Number of digits right of decimal point.
139                         return match[ 1 ] ? match[ 1 ].length : 0;
140                     },
141                     toInt = function( num ) {
142                         return Math.round( num * Math.pow( 10, decimals ) );
143                     },
144                     valid = true,
145                     decimals;
146         
147                 // Works only for text, number and range input types
148                 // TODO find a way to support input types date, datetime, datetime-local, month, time and week
149                 if ( notSupported ) {
150                     throw new Error( errorMessage );
151                 }
152         
153                 decimals = decimalPlaces( param );
154         
155                 // Value can't have too many decimals
156                 if ( decimalPlaces( value ) > decimals || toInt( value ) % toInt( param ) !== 0 ) {
157                     valid = false;
158                 }
159         
160                 return this.optional( element ) || valid;
161             },
162         
163             // 匹配某个元素
164             equalTo: function( value, element, param ) {
165         
166                 // Bind to the blur event of the target in order to revalidate whenever the target field is updated
167                 var target = $( param );
168                 if ( this.settings.onfocusout && target.not( ".validate-equalTo-blur" ).length ) {
169                     target.addClass( "validate-equalTo-blur" ).on( "blur.validate-equalTo", function() {
170                         $( element ).valid();
171                     } );
172                 }
173                 return value === target.val();
174             },
175         
176             // 远程校验
177             remote: function( value, element, param, method ) {
178                 if ( this.optional( element ) ) {
179                     return "dependency-mismatch";
180                 }
181         
182                 method = typeof method === "string" && method || "remote";
183         
184                 var previous = this.previousValue( element, method ),
185                     validator, data, optionDataString;
186         
187                 if ( !this.settings.messages[ element.name ] ) {
188                     this.settings.messages[ element.name ] = {};
189                 }
190                 previous.originalMessage = previous.originalMessage || this.settings.messages[ element.name ][ method ];
191                 this.settings.messages[ element.name ][ method ] = previous.message;
192         
193                 param = typeof param === "string" && { url: param } || param;
194                 optionDataString = $.param( $.extend( { data: value }, param.data ) );
195                 if ( previous.old === optionDataString ) {
196                     return previous.valid;
197                 }
198         
199                 previous.old = optionDataString;
200                 validator = this;
201                 this.startRequest( element );
202                 data = {};
203                 data[ element.name ] = value;
204                 $.ajax( $.extend( true, {
205                     mode: "abort",
206                     port: "validate" + element.name,
207                     dataType: "json",
208                     data: data,
209                     context: validator.currentForm,
210                     success: function( response ) {
211                         var valid = response === true || response === "true",
212                             errors, message, submitted;
213         
214                         validator.settings.messages[ element.name ][ method ] = previous.originalMessage;
215                         if ( valid ) {
216                             submitted = validator.formSubmitted;
217                             validator.resetInternals();
218                             validator.toHide = validator.errorsFor( element );
219                             validator.formSubmitted = submitted;
220                             validator.successList.push( element );
221                             validator.invalid[ element.name ] = false;
222                             validator.showErrors();
223                         } else {
224                             errors = {};
225                             message = response || validator.defaultMessage( element, { method: method, parameters: value } );
226                             errors[ element.name ] = previous.message = message;
227                             validator.invalid[ element.name ] = true;
228                             validator.showErrors( errors );
229                         }
230                         previous.valid = valid;
231                         validator.stopRequest( element, valid );
232                     }
233                 }, param ) );
234                 return "pending";
235             }
236         }
  1         
  2         // 添加类规则,如果时字符串,直接赋值,如果是对象,扩展classRuleSettings
  3         addClassRules: function( className, rules ) {
  4             if ( className.constructor === String ) {
  5                 this.classRuleSettings[ className ] = rules;
  6             } else {
  7                 $.extend( this.classRuleSettings, className );
  8             }
  9         },
 10         
 11         // 获取某个元素的class规则
 12         classRules: function( element ) {
 13             var rules = {},
 14                 classes = $( element ).attr( "class" );
 15         
 16             if ( classes ) { 
 17                 $.each( classes.split( " " ), function() {
 18                     if ( this in $.validator.classRuleSettings ) { 
 19                         // 防止重复和无用添加
 20                         $.extend( rules, $.validator.classRuleSettings[ this ] );
 21                     }
 22                 } );
 23             }
 24             return rules;
 25         },
 26         
 27         // 规范化属性规则 由于 html下可以通过属性定义规则
 28         normalizeAttributeRule: function( rules, type, method, value ) {
 29         
 30             // type={ number, range, text} method={min max step}
 31             if ( /min|max|step/.test( method ) && ( type === null || /number|range|text/.test( type ) ) ) {
 32                 value = Number( value );
 33         
 34                 // Support Opera Mini, which returns NaN for undefined minlength
 35                 if ( isNaN( value ) ) {
 36                     value = undefined;
 37                 }
 38             }
 39         
 40             if ( value || value === 0 ) {
 41                 rules[ method ] = value;
 42             } else if ( type === method && type !== "range" ) {
 43         
 44                 // Exception: the jquery validate 'range' method
 45                 // does not test for the html5 'range' type
 46                 rules[ method ] = true;
 47             }
 48         },
 49         
 50         // 获取元素的属性规则
 51         attributeRules: function( element ) {
 52             var rules = {},
 53                 $element = $( element ),
 54                 type = element.getAttribute( "type" ),
 55                 method, value;
 56         
 57             for ( method in $.validator.methods ) {
 58         
 59                 // Support for <input required> in both html5 and older browsers
 60                 if ( method === "required" ) {
 61                     value = element.getAttribute( method );
 62         
 63                     // Some browsers return an empty string for the required attribute
 64                     // and non-HTML5 browsers might have required="" markup
 65                     if ( value === "" ) {
 66                         value = true;
 67                     }
 68         
 69                     // Force non-HTML5 browsers to return bool
 70                     value = !!value;
 71                 } else {
 72                     value = $element.attr( method );
 73                 }
 74         
 75                 this.normalizeAttributeRule( rules, type, method, value );
 76             }
 77         
 78             // 'maxlength' may be returned as -1, 2147483647 ( IE ) and 524288 ( safari ) for text inputs
 79             if ( rules.maxlength && /-1|2147483647|524288/.test( rules.maxlength ) ) {
 80                 delete rules.maxlength;
 81             }
 82         
 83             return rules;
 84         },
 85         
 86         // 数据
 87         dataRules: function( element ) {
 88             var rules = {},
 89                 $element = $( element ),
 90                 type = element.getAttribute( "type" ),
 91                 method, value;
 92         
 93             for ( method in $.validator.methods ) {
 94                 value = $element.data( "rule" + method.charAt( 0 ).toUpperCase() + method.substring( 1 ).toLowerCase() );
 95         
 96                 // Cast empty attributes like `data-rule-required` to `true`
 97                 if ( value === "" ) {
 98                     value = true;
 99                 }
100         
101                 this.normalizeAttributeRule( rules, type, method, value );
102             }
103             return rules;
104         },
105         
106         // 静态规则
107         staticRules: function( element ) {
108             var rules = {},
109                 validator = $.data( element.form, "validator" );
110         
111             if ( validator.settings.rules ) {
112                 rules = $.validator.normalizeRule( validator.settings.rules[ element.name ] ) || {};
113             }
114             return rules;
115         },
116         
117         // 规范化规则及 删除非必需规则
118         normalizeRules: function( rules, element ) {
119             // Handle dependency check
120             $.each( rules, function( prop, val ) {
121                 // Ignore rule when param is explicitly false, eg. required:false
122                 if ( val === false ) {
123                     delete rules[ prop ];
124                     return;
125                 }
126                 if ( val.param || val.depends ) {
127                     var keepRule = true;
128                     switch ( typeof val.depends ) {
129                     case "string":
130                         keepRule = !!$( val.depends, element.form ).length;
131                         break;
132                     case "function":
133                         keepRule = val.depends.call( element, element );
134                         break;
135                     }
136                     if ( keepRule ) {
137                         rules[ prop ] = val.param !== undefined ? val.param : true;
138                     } else {
139                         $.data( element.form, "validator" ).resetElements( $( element ) );
140                         delete rules[ prop ];
141                     }
142                 }
143             } );
144         
145             // Evaluate parameters
146             $.each( rules, function( rule, parameter ) {
147                 rules[ rule ] = $.isFunction( parameter ) && rule !== "normalizer" ? parameter( element ) : parameter;
148             } );
149         
150             // Clean number parameters
151             $.each( [ "minlength", "maxlength" ], function() {
152                 if ( rules[ this ] ) {
153                     rules[ this ] = Number( rules[ this ] );
154                 }
155             } );
156             $.each( [ "rangelength", "range" ], function() {
157                 var parts;
158                 if ( rules[ this ] ) {
159                     if ( $.isArray( rules[ this ] ) ) {
160                         rules[ this ] = [ Number( rules[ this ][ 0 ] ), Number( rules[ this ][ 1 ] ) ];
161                     } else if ( typeof rules[ this ] === "string" ) {
162                         parts = rules[ this ].replace( /[[]]/g, "" ).split( /[s,]+/ );
163                         rules[ this ] = [ Number( parts[ 0 ] ), Number( parts[ 1 ] ) ];
164                     }
165                 }
166             } );
167             
168             // 
169             if ( $.validator.autoCreateRanges ) {
170                 // Auto-create ranges
171                 if ( rules.min != null && rules.max != null ) {
172                     rules.range = [ rules.min, rules.max ];
173                     delete rules.min;
174                     delete rules.max;
175                 }
176                 if ( rules.minlength != null && rules.maxlength != null ) {
177                     rules.rangelength = [ rules.minlength, rules.maxlength ];
178                     delete rules.minlength;
179                     delete rules.maxlength;
180                 }
181             }
182         
183             return rules;
184         },
185         
186         // 规范化规则,字符串变对象{}
187         normalizeRule: function( data ) {
188             if ( typeof data === "string" ) {
189                 var transformed = {};
190                 $.each( data.split( /s/ ), function() {
191                     transformed[ this ] = true;
192                 } );
193                 data = transformed;
194             }
195             return data;
196         },
197         
198         // 添加自定义的规则
199         addMethod: function( name, method, message ) {
200             $.validator.methods[ name ] = method;
201             $.validator.messages[ name ] = message !== undefined ? message : $.validator.messages[ name ];
202             // 默认参个数 < 3时
203             if ( method.length < 3 ) {
204                 $.validator.addClassRules( name, $.validator.normalizeRule( name ) );
205             }
206         },

   2.3  扩展jQuery实例

 

  a. 结构分析

  扩展jQuery实例,即可获取通过jQuery实例调用函数

1 $.extend( $.fn, {
2     log: function() {
3         console.info("currVal : " + $(this).val());
4     }
5 }); 

6 $('#kw').log("123");

7 VM415:3 currVal : 输入框里的值

  b. 具体函数分析

 1 // 操作规则 cmmand : add, remove
 2         rules: function( command, argument ) {
 3             var element = this[ 0 ],
 4                 isContentEditable = typeof this.attr( "contenteditable" ) !== "undefined" && this.attr( "contenteditable" ) !== "false",
 5                 settings, staticRules, existingRules, data, param, filtered;
 6         
 7             // If nothing is selected, return empty object; can't chain anyway
 8             if ( element == null ) {
 9                 return;
10             }
11         
12             if ( !element.form && isContentEditable ) {
13                 element.form = this.closest( "form" )[ 0 ];
14                 element.name = this.attr( "name" );
15             }
16         
17             if ( element.form == null ) {
18                 return;
19             }
20         
21             if ( command ) {
22                 settings = $.data( element.form, "validator" ).settings;
23                 staticRules = settings.rules;
24                 existingRules = $.validator.staticRules( element );
25                 switch ( command ) {
26                 case "add":
27                     $.extend( existingRules, $.validator.normalizeRule( argument ) );
28         
29                     // Remove messages from rules, but allow them to be set separately
30                     delete existingRules.messages;
31                     staticRules[ element.name ] = existingRules;
32                     if ( argument.messages ) {
33                         settings.messages[ element.name ] = $.extend( settings.messages[ element.name ], argument.messages );
34                     }
35                     break;
36                 case "remove":
37                     if ( !argument ) {
38                         delete staticRules[ element.name ];
39                         return existingRules;
40                     }
41                     filtered = {};
42                     $.each( argument.split( /s/ ), function( index, method ) {
43                         filtered[ method ] = existingRules[ method ];
44                         delete existingRules[ method ];
45                     } );
46                     return filtered;
47                 }
48             }
49         
50             data = $.validator.normalizeRules(
51             $.extend(
52                 {},
53                 $.validator.classRules( element ),
54                 $.validator.attributeRules( element ),
55                 $.validator.dataRules( element ),
56                 $.validator.staticRules( element )
57             ), element );
58         
59             // Make sure required is at front
60             if ( data.required ) {
61                 param = data.required;
62                 delete data.required;
63                 data = $.extend( { required: param }, data );
64             }
65         
66             // Make sure remote is at back
67             if ( data.remote ) {
68                 param = data.remote;
69                 delete data.remote;
70                 data = $.extend( data, { remote: param } );
71             }
72         
73             return data;
74         }
 1 // 校验
 2         valid: function() {
 3             var valid, validator, errorList;
 4             
 5             if ( $( this[ 0 ] ).is( "form" ) ) {
 6                 // 是form元素,直接校验form及其下属元素
 7                 valid = this.validate().form();
 8             } else {
 9                 // 构建当前元素的validator对象,校验当前元素
10                 errorList = [];
11                 valid = true;
12                 validator = $( this[ 0 ].form ).validate();
13                 this.each( function() {
14                     valid = validator.element( this ) && valid;
15                     if ( !valid ) {
16                         errorList = errorList.concat( validator.errorList );
17                     }
18                 } );
19                 validator.errorList = errorList;
20             }
21             return valid;
22         },
  1 // 校验对象
  2         validate: function( options ) {
  3         
  4             // 如果未选中任何元素,打印日志,直接return
  5             if ( !this.length ) {
  6                 if ( options && options.debug && window.console ) {
  7                     console.warn( "Nothing selected, can't validate, returning nothing." );
  8                 }
  9                 return;
 10             }
 11         
 12             // 检查当前元素是否已经构建过validator对象
 13             var validator = $.data( this[ 0 ], "validator" );
 14             if ( validator ) {
 15                 return validator;
 16             }
 17         
 18             // Add novalidate tag if HTML5.
 19             this.attr( "novalidate", "novalidate" );
 20             
 21             // 创建一个validator对象,new $.validator( options, this[ 0 ] );
 22             validator = new $.validator( options, this[ 0 ] );
 23             //  当前元素 data存储 validator
 24             $.data( this[ 0 ], "validator", validator );
 25             
 26             // 如果配置了onsubmit属性
 27             if ( validator.settings.onsubmit ) {
 28                 // submit绑定点击事件
 29                 this.on( "click.validate", ":submit", function( event ) {
 30                     // Track the used submit button to properly handle scripted
 31                     // submits later. 如果当前事件是提交按纽触发
 32                     validator.submitButton = event.currentTarget;
 33         
 34                     // Allow suppressing validation by adding a cancel class to the submit button
 35                     if ( $( this ).hasClass( "cancel" ) ) {
 36                         validator.cancelSubmit = true;
 37                     }
 38         
 39                     // Allow suppressing validation by adding the html5 formnovalidate attribute to the submit button
 40                     if ( $( this ).attr( "formnovalidate" ) !== undefined ) {
 41                         validator.cancelSubmit = true;
 42                     }
 43                 } );
 44         
 45                 // Validate the form on submit
 46                 this.on( "submit.validate", function( event ) {
 47                     if ( validator.settings.debug ) {
 48                         // debug 模式下,无需执行submit
 49                         event.preventDefault();
 50                     }
 51                     
 52                     function handle() {
 53                         var hidden, result;
 54         
 55                         // Insert a hidden input as a replacement for the missing submit button
 56                         // The hidden input is inserted in two cases:
 57                         //   - A user defined a `submitHandler`
 58                         //   - There was a pending request due to `remote` method and `stopRequest()`
 59                         //     was called to submit the form in case it's valid
 60                         if ( validator.submitButton && ( validator.settings.submitHandler || validator.formSubmitted ) ) {
 61                             hidden = $( "<input type='hidden'/>" )
 62                                 .attr( "name", validator.submitButton.name )
 63                                 .val( $( validator.submitButton ).val() )
 64                                 .appendTo( validator.currentForm );
 65                         }
 66         
 67                         if ( validator.settings.submitHandler && !validator.settings.debug ) {
 68                             result = validator.settings.submitHandler.call( validator, validator.currentForm, event );
 69                             if ( hidden ) {
 70         
 71                                 // And clean up afterwards; thanks to no-block-scope, hidden can be referenced
 72                                 hidden.remove();
 73                             }
 74                             if ( result !== undefined ) {
 75                                 return result;
 76                             }
 77                             return false;
 78                         }
 79                         return true;
 80                     }
 81         
 82                     // Prevent submit for invalid forms or custom submit handlers
 83                     if ( validator.cancelSubmit ) {
 84                         validator.cancelSubmit = false;
 85                         return handle();
 86                     }
 87                     if ( validator.form() ) {
 88                         if ( validator.pendingRequest ) {
 89                             validator.formSubmitted = true;
 90                             return false;
 91                         }
 92                         return handle();
 93                     } else {
 94                         validator.focusInvalid();
 95                         return false;
 96                     }
 97                 } );
 98             }
 99         
100             return validator;
101         },
102     
原文地址:https://www.cnblogs.com/pengsn/p/14071858.html