', {html: 'this is a div', class:'div'}).appendTo('body'); //添加在body的末尾
804 | $('
', {html: 'this is a div', class:'div'}).appendTo('body'); //添加在body的末尾
805 | $('div').first().css('background','red'); //第一个红
806 | $('div').last().css('background','yellow'); //最后一个黄
807 | $('div').eq(2).css('background','blue'); //第三个蓝
808 | ```
809 |
810 | ### 3.14 $().map()
811 |
812 | >源码
813 |
814 | ```javascript
815 | //[265-269]
816 | map: function( callback ) {
817 | //入栈
818 | //最终调了底层的工具方法
819 | return this.pushStack( jQuery.map(this, function( elem, i ) {
820 | return callback.call( elem, i, elem );
821 | }));
822 | },
823 | ```
824 |
825 | >内容解析
826 |
827 | ```javascript
828 | var arr = ['a','b','c'];
829 | arr = $.map(arr,function(item,index) {
830 | return item + index;
831 | });
832 | console.log(arr); //[a0,b1,c2]
833 | ```
834 |
835 | ### 3.15 $().push()/sort()/slice()
836 |
837 | - 内部用,不建议在外面使用,内部使用增加性能
838 |
839 | >源码
840 |
841 | ```javascript
842 | // [275-279]
843 | // 内部使用
844 | // 将Array的方法挂载到了jQuery对象下面
845 | push: core_push,
846 | sort: [].sort,
847 | splice: [].splice
848 | ```
849 |
--------------------------------------------------------------------------------
/元素属性.md:
--------------------------------------------------------------------------------
1 | ## 12.元素属性
2 |
3 | ``` javascript
4 |
5 | //对外使用的实例方法
6 | $.fn.extend({
7 | attr
8 | removeAttr
9 | prop
10 | removeProp
11 | addClass
12 | removeClass
13 | toggleClass
14 | hasClass
15 | val
16 | });
17 |
18 | //这些工具方法通常是内部使用
19 | $.extend({
20 | valHooks
21 | attr
22 | removeAttr
23 | attrHooks
24 | propFix
25 | prop
26 | propHooks
27 | });
28 |
29 | ```
30 |
31 | >内容解析
32 |
33 | `attr`和`prop`方法的区别(有些HTML属性其实也是element对象的属性,但是element对象的属性并不一定是HTML的属性,所以容易产生混淆)
34 |
35 | - `attr`方法是设置HTML属性
36 | - `prop`方法是设置element对象的属性
37 |
38 | ``` javascript
39 | //设置元素的默认属性
40 | var $div = $("#a");
41 | $div.attr("href","http://baidu.com");
42 | console.log($div.attr("href")); //http://baidu.com
43 | $div.prop("href","http://ziyi2.com");
44 | console.log($div.prop("href")); //http://ziyi2.com/
45 |
46 | //设置元素的自定义属性
47 | $div.attr("baidu","http://baidu.com");
48 | console.log($div.attr("baidu")); //http://baidu.com
49 | $div.prop("ziyi2","http://ziyi2.com");
50 | console.log($div.attr("ziyi2")); //undefined
51 | ```
52 |
53 |
54 |
55 | ### 12.1 `attr()`
56 |
57 |
58 | >源码
59 |
60 | ``` javascript
61 |
62 | //[3805] $().attr()
63 | attr: function( name, value ) {
64 | //最后一个参数用于判断是获取还是设置操作
65 | //access方法用于在这里用于遍历this这个elems,并且调用jQuery.attr函数进行操作
66 | return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
67 | },
68 |
69 |
70 | //[4091] $.attr()
71 | attr: function( elem, name, value ) {
72 | var hooks, ret,
73 | nType = elem.nodeType;
74 |
75 | // don't get/set attributes on text, comment and attribute nodes
76 | // 首先判断elem是不是非文本、注释和属性节点,这些节点不能进行对象属性设置
77 | if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
78 | return;
79 | }
80 |
81 | // Fallback to prop when attributes are not supported
82 | // 如果getAttribute方法不存在,例如console.log(document.getAttribute) //undefined
83 | if ( typeof elem.getAttribute === core_strundefined ) {
84 | // 那就调用prop方法设置属性,prop方法本质上设置对象的属性操作,使用.或[]方法
85 | return jQuery.prop( elem, name, value );
86 | }
87 |
88 | // All attributes are lowercase
89 | // Grab necessary hook if one is defined
90 | // 如果不是xml文档,或者不是element对象
91 | if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
92 | name = name.toLowerCase();
93 | //如果设置的是type属性,则走jQuery.attrHooks[ name ]
94 | //否则匹配这些属性[/^(?:checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped)$/i]
95 | //走boolHook函数,因为这些属性都应该可以通过布尔值进行设置
96 | //如果是其他的属性(例如自定义属性),那就走nodeHook其实是undefined
97 | hooks = jQuery.attrHooks[ name ] ||
98 | ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
99 | }
100 |
101 | //如果value存在
102 | if ( value !== undefined ) {
103 | //如果value设置为null则是移除属性
104 | if ( value === null ) {
105 | jQuery.removeAttr( elem, name );
106 | //否则判断hooks是否存在,且hooks.set方法存在(则需要做兼容性处理,使$().attr("checked",true)这样的方法也可以使用)
107 | } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
108 | return ret;
109 |
110 | } else {
111 | //可能value不是字符串,则转化为字符串
112 | //如果是普通自定义元素,就用原生方法设置
113 | elem.setAttribute( name, value + "" );
114 | return value;
115 | }
116 |
117 | //这个好像一般都不会满足
118 | } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
119 | return ret;
120 |
121 | } else {
122 | //Sizzle里的方法
123 | ret = jQuery.find.attr( elem, name );
124 |
125 | // Non-existent attributes return null, we normalize to undefined
126 | return ret == null ?
127 | undefined :
128 | ret;
129 | }
130 | },
131 |
132 |
133 | //[4159]
134 | attrHooks: {
135 | type: {
136 | //这里是对设置radio元素的type属性做兼容性处理
137 | set: function( elem, value ) {
138 | if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
139 | // Setting the type on a radio button after the value resets the value in IE6-9
140 | // Reset value to default in case type is set after value during creation
141 | var val = elem.value;
142 | elem.setAttribute( "type", value );
143 | if ( val ) {
144 | elem.value = val;
145 | }
146 | return value;
147 | }
148 | }
149 | }
150 | },
151 |
152 | // Hooks for boolean attributes
153 | // 使$().attr("checked",true)这样的方法也可以使用
154 | // 即第二个参数是boolean值也可以正确处理
155 | boolHook = {
156 | set: function( elem, value, name ) {
157 | if ( value === false ) {
158 | // Remove boolean attributes when set to false
159 | // 参数为false时移除属性
160 | jQuery.removeAttr( elem, name );
161 | } else {
162 | //参数为true时设置属性的值为属性
163 | //详见(二)
164 | elem.setAttribute( name, name );
165 | }
166 | return name;
167 | }
168 | };
169 |
170 | ```
171 |
172 |
173 | >内容解析
174 |
175 | (一)设置布尔值属性时可以使用布尔值参数
176 |
177 |
178 | ``` javascript
179 |
180 | //设置元素的默认属性
181 | var $input = $("#radio");
182 | $input.attr("checked","checked");
183 | $input.attr("checked",true); //
184 | $input.attr("checked",false); //可以
185 |
186 | //原生方法设置
187 | var input = document.getElementById("radio");
188 | input.setAttribute("checked",true);
189 | input.setAttribute("checked",false); //这样是不能取消被选中的状态
190 |
191 |
192 | //设置元素的默认属性
193 | var $input = $("#radio");
194 | $input.attr("checked",true);
195 | console.log($input.attr("checked")); //checked
196 | $input.attr("type","radio"); //其实是做了兼容性处理,这里需要先设置type属性,然后获取元素的value值并重新设置value的值
197 |
198 | ```
199 |
200 |
201 |
202 | (二)`element.setAttribute`
203 |
204 | - 可以获取和设置非标准的HTML属性,该方法的属性名不区分大小写
205 |
206 |
207 | ### 12.2 `removeAttr()`
208 |
209 |
210 | >源码
211 |
212 | ``` javascript
213 | //[3809] $().removeAttr()
214 | removeAttr: function( name ) {
215 | return this.each(function() {
216 | jQuery.removeAttr( this, name );
217 | });
218 | },
219 |
220 | //[4139] $.removeAttr()
221 | removeAttr: function( elem, value ) {
222 | var name, propName,
223 | i = 0,
224 | //详见(二)
225 | attrNames = value && value.match( core_rnotwhite );
226 |
227 | //第一个参数必须是element对象
228 | if ( attrNames && elem.nodeType === 1 ) {
229 | while ( (name = attrNames[i++]) ) {
230 | propName = jQuery.propFix[ name ] || name;
231 |
232 | // Boolean attributes get special treatment (#10870)
233 | if ( jQuery.expr.match.bool.test( name ) ) {
234 | // Set corresponding property to false
235 | // 布尔值的属性需要设置为false
236 | elem[ propName ] = false;
237 | }
238 |
239 | //原生方法删除属性
240 | elem.removeAttribute( name );
241 | }
242 | }
243 | },
244 |
245 | //兼容性,for和class本身是关键字
246 | propFix: {
247 | "for": "htmlFor",
248 | "class": "className"
249 | },
250 |
251 | ```
252 |
253 |
254 | >内容解析
255 |
256 |
257 | (一) 删除多个属性
258 |
259 |
260 | ``` javascript
261 | var $input = $("#radio");
262 | $input.removeAttr("id class checked"); //删除多个属性
263 |
264 | $input[0].checked = true;
265 | $input[0].removeAttribute("checked"); //element的属性checked仍然为false
266 |
267 | ```
268 |
269 | (二) 匹配多个空格
270 |
271 | ``` javascript
272 | var pattern = /\S+/g
273 | , str = "a b c d e f"
274 | , strArr = str.match(pattern);
275 |
276 | console.log(strArr); //['a','b','c','d','e','f']
277 | ```
278 |
279 |
280 |
281 | ### 12.3 `prop()`
282 |
283 |
284 | > 源码
285 |
286 | ``` javascript
287 | //[3815] $().prop()
288 | prop: function( name, value ) {
289 | return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
290 | },
291 |
292 |
293 |
294 | // $.
295 | propFix: {
296 | "for": "htmlFor",
297 | "class": "className"
298 | },
299 |
300 | prop: function( elem, name, value ) {
301 | var ret, hooks, notxml,
302 | nType = elem.nodeType;
303 |
304 | // don't get/set properties on text, comment and attribute nodes
305 | if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
306 | return;
307 | }
308 |
309 | notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
310 |
311 | // 如果不是xml,则可能有兼容性问题需要处理
312 | if ( notxml ) {
313 | // Fix name and attach hooks
314 | name = jQuery.propFix[ name ] || name;
315 | hooks = jQuery.propHooks[ name ];
316 | }
317 |
318 | // 设置值
319 | if ( value !== undefined ) {
320 | return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
321 | ret :
322 | //设置的其实是element对象属性
323 | ( elem[ name ] = value );
324 |
325 | // 获取值
326 | } else {
327 | return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
328 | ret :
329 | elem[ name ];
330 | }
331 | },
332 |
333 |
334 | propHooks: {
335 | //tabIndex具有兼容性问题
336 | tabIndex: {
337 | get: function( elem ) {
338 | return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ?
339 | elem.tabIndex :
340 | -1;
341 | }
342 | }
343 | }
344 | ```
345 |
346 |
347 |
348 |
349 | > 内容解析
350 |
351 |
352 | ``` javascript
353 | var input = $("#radio");
354 | input.prop('ziyi2',"ziyi2");
355 | console.log(input.prop('ziyi2')); //ziyi2
356 | ```
357 | ### 12.4 `removeProp()`
358 |
359 | >源码
360 |
361 | ``` javascript
362 | removeProp: function( name ) {
363 | return this.each(function() {
364 | delete this[ jQuery.propFix[ name ] || name ];
365 | });
366 | },
367 | ```
368 |
369 | ### 12.5 `addClass()`
370 |
371 |
372 | >源码
373 |
374 | ``` javascript
375 | addClass: function( value ) {
376 | var classes, elem, cur, clazz, j,
377 | i = 0,
378 | len = this.length,
379 | proceed = typeof value === "string" && value;
380 |
381 | // 如果参数是函数,则函数的参数是this的index和对应的className
382 | if ( jQuery.isFunction( value ) ) {
383 | return this.each(function( j ) {
384 | jQuery( this ).addClass( value.call( this, j, this.className ) );
385 | });
386 | }
387 |
388 | if ( proceed ) {
389 | // The disjunction here is for better compressibility (see removeClass)
390 | // value转换成数组
391 | classes = ( value || "" ).match( core_rnotwhite ) || [];
392 |
393 | for ( ; i < len; i++ ) {
394 | elem = this[ i ];
395 | // rclass = /[\t\r\n\f]/g, 制表符 换行符 回车符等
396 | // 将html中元素的class的值的制表符等转换为空字符
397 | cur = elem.nodeType === 1 && ( elem.className ?
398 | ( " " + elem.className + " " ).replace( rclass, " " ) :
399 | " "
400 | );
401 |
402 | if ( cur ) {
403 | j = 0;
404 | while ( (clazz = classes[j++]) ) {
405 | // 如果html的class中没有需要设置的class
406 | if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
407 | cur += clazz + " ";
408 | }
409 | }
410 | //去掉之前加上的两边的空格
411 | elem.className = jQuery.trim( cur );
412 |
413 | }
414 | }
415 | }
416 |
417 | return this;
418 | },
419 | ```
420 |
421 | >内容解析
422 |
423 | ``` javascript
424 |
426 |
427 |
437 | ```
438 |
439 | ### 12.6 `removeClass()`
440 |
441 |
442 |
443 | >源码
444 |
445 | ``` javascript
446 | removeClass: function( value ) {
447 | var classes, elem, cur, clazz, j,
448 | i = 0,
449 | len = this.length,
450 | //详见(一)
451 | proceed = arguments.length === 0 || typeof value === "string" && value;
452 |
453 | if ( jQuery.isFunction( value ) ) {
454 | return this.each(function( j ) {
455 | jQuery( this ).removeClass( value.call( this, j, this.className ) );
456 | });
457 | }
458 | if ( proceed ) {
459 | classes = ( value || "" ).match( core_rnotwhite ) || [];
460 |
461 | for ( ; i < len; i++ ) {
462 | elem = this[ i ];
463 | // This expression is here for better compressibility (see addClass)
464 | cur = elem.nodeType === 1 && ( elem.className ?
465 | ( " " + elem.className + " " ).replace( rclass, " " ) :
466 | ""
467 | );
468 |
469 | if ( cur ) {
470 | j = 0;
471 | while ( (clazz = classes[j++]) ) {
472 | // Remove *all* instances
473 | while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
474 | cur = cur.replace( " " + clazz + " ", " " );
475 | }
476 | }
477 | //如果value不存在,则去掉所有的class
478 | elem.className = value ? jQuery.trim( cur ) : "";
479 | }
480 | }
481 | }
482 |
483 | return this;
484 | },
485 | ```
486 |
487 | >内容解析
488 |
489 |
490 | (一) 优先级
491 |
492 | ``` javascript
493 | console.log(1 || 0 && 2); //1, 如果是||优先级高,则返回2,否则返回1,说明&&优先级高
494 | $("#div").removeClass();
495 | console.log($("div")[0].className); //''
496 | ```
497 |
498 |
499 | ### 12.7 `toggleClass()`
500 |
501 | >源码
502 |
503 | ``` javascript
504 | toggleClass: function( value, stateVal ) {
505 | var type = typeof value;
506 |
507 | //如果存在第二参数且是布尔值,则功能类似于addClass和removeClass
508 | if ( typeof stateVal === "boolean" && type === "string" ) {
509 | return stateVal ? this.addClass( value ) : this.removeClass( value );
510 | }
511 |
512 | //同样支持回调函数
513 | if ( jQuery.isFunction( value ) ) {
514 | return this.each(function( i ) {
515 | jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
516 | });
517 | }
518 |
519 | return this.each(function() {
520 | if ( type === "string" ) {
521 | // toggle individual class names
522 | var className,
523 | i = 0,
524 | //需要注意前面是this.each,所以这里的this并不指代$(),而是指示具体的元素
525 | //jQuery(this)就是获取了实例对象,所以才可以调用实例对象的方法
526 | self = jQuery( this ),
527 | classNames = value.match( core_rnotwhite ) || [];
528 |
529 | while ( (className = classNames[ i++ ]) ) {
530 | // check each className given, space separated list
531 | //如果有class
532 | if ( self.hasClass( className ) ) {
533 | self.removeClass( className );
534 | } else {
535 | self.addClass( className );
536 | }
537 | }
538 |
539 | // Toggle whole class name
540 | // 如果第一参数不存在或者是布尔值,则是反转所有的className,其实是通过data方法将之前的class全部缓存起来
541 | } else if ( type === core_strundefined || type === "boolean" ) {
542 | if ( this.className ) {
543 | // store className if set
544 | data_priv.set( this, "__className__", this.className );
545 | }
546 |
547 | // If the element has a class name or if we're passed "false",
548 | // then remove the whole classname (if there was one, the above saved it).
549 | // Otherwise bring back whatever was previously saved (if anything),
550 | // falling back to the empty string if nothing was stored.
551 | this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || "";
552 | }
553 | });
554 | },
555 | ```
556 |
557 | ### 12.8 `hasClass()`
558 |
559 | ``` javascript
560 | hasClass: function( selector ) {
561 | var className = " " + selector + " ",
562 | i = 0,
563 | l = this.length;
564 | for ( ; i < l; i++ ) {
565 | if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
566 | return true;
567 | }
568 | }
569 |
570 | return false;
571 | },
572 |
573 | ```
574 |
575 |
576 | ### 12.9 `val()`
577 |
578 |
579 | >源码
580 |
581 |
582 | ``` javascript
583 | val: function( value ) {
584 | var hooks, ret, isFunction,
585 | elem = this[0];
586 |
587 | //没有参数就是获取值
588 | if ( !arguments.length ) {
589 | if ( elem ) {
590 | //对于select元素,elem.type=select-one/select-multiple,因此要使用elem.nodeName.toLowerCase()来获取select属性
591 | hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
592 |
593 | //select/option/checkbox具有get属性
594 | if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
595 | return ret;
596 | }
597 |
598 | //对于普通的text texterea元素就会直接获取value属性值
599 | ret = elem.value;
600 |
601 | return typeof ret === "string" ?
602 | // handle most common string cases
603 | ret.replace(rreturn, "") :
604 | // handle cases where value is null/undef or number
605 | ret == null ? "" : ret;
606 | }
607 |
608 | return;
609 | }
610 |
611 | isFunction = jQuery.isFunction( value );
612 |
613 | return this.each(function( i ) {
614 | var val;
615 |
616 | if ( this.nodeType !== 1 ) {
617 | return;
618 | }
619 |
620 | if ( isFunction ) {
621 | val = value.call( this, i, jQuery( this ).val() );
622 | } else {
623 | val = value;
624 | }
625 |
626 | // Treat null/undefined as ""; convert numbers to string
627 | if ( val == null ) {
628 | val = "";
629 | } else if ( typeof val === "number" ) {
630 | val += "";
631 | } else if ( jQuery.isArray( val ) ) {
632 | val = jQuery.map(val, function ( value ) {
633 | return value == null ? "" : value + "";
634 | });
635 | }
636 |
637 | hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
638 |
639 | // If set returns undefined, fall back to normal setting
640 | if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
641 | this.value = val;
642 | }
643 | });
644 | }
645 |
646 |
647 | valHooks: {
648 | option: {
649 | get: function( elem ) {
650 | // attributes.value is undefined in Blackberry 4.7 but
651 | // uses .value. See #6932
652 | // elem.attributes 是元素的所有属性的集合
653 | // 判断value属性是不是存在
654 | var val = elem.attributes.value;
655 | // val.specified 判断value属性是否指定了值,如果指定了值则返回指定值,否则返回元素的text文本
656 | return !val || val.specified ? elem.value : elem.text;
657 | }
658 | },
659 | select: {
660 | get: function( elem ) {
661 | var value, option,
662 | //获取select元素的所有option
663 | options = elem.options,
664 | //获取选中元素的索引值,多选时是所有被选中元素的最小索引值
665 | index = elem.selectedIndex,
666 | //判断select的类型是单选还是多选
667 | one = elem.type === "select-one" || index < 0,
668 | //单选返回单个值,多选返回数组
669 | values = one ? null : [],
670 | //要遍历的最大值,其实单选可以不写,这里是为了让单选和多选做代码统一
671 | max = one ? index + 1 : options.length,
672 | //单选的时候i = index
673 | //多选时i = 0
674 | i = index < 0 ?
675 | max :
676 | one ? index : 0;
677 |
678 | // Loop through all the selected options
679 | // 单选只会遍历一次,从i开始到i+1结束
680 | for ( ; i < max; i++ ) {
681 | option = options[ i ];
682 |
683 | // IE6-9 doesn't update selected after form reset (#2551)
684 | if ( ( option.selected || i === index ) &&
685 | // Don't return options that are disabled or in a disabled optgroup
686 | ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
687 | ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
688 |
689 | // Get the specific value for the option
690 | // 获取选中的单选元素的值
691 | value = jQuery( option ).val();
692 |
693 | // We don't need an array for one selects
694 | if ( one ) {
695 | return value;
696 | }
697 |
698 | // Multi-Selects return an array
699 | values.push( value );
700 | }
701 | }
702 |
703 | return values;
704 | },
705 |
706 | set: function( elem, value ) {
707 | var optionSet, option,
708 | options = elem.options,
709 | values = jQuery.makeArray( value ),
710 | i = options.length;
711 |
712 | while ( i-- ) {
713 | option = options[ i ];
714 | if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
715 | optionSet = true;
716 | }
717 | }
718 |
719 | // force browsers to behave consistently when non-matching value is set
720 | if ( !optionSet ) {
721 | elem.selectedIndex = -1;
722 | }
723 | return values;
724 | }
725 | }
726 | },
727 |
728 |
729 | // Radios and checkboxes getter/setter
730 | jQuery.each([ "radio", "checkbox" ], function() {
731 | jQuery.valHooks[ this ] = {
732 | set: function( elem, value ) {
733 | if ( jQuery.isArray( value ) ) {
734 | return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
735 | }
736 | }
737 | };
738 | if ( !jQuery.support.checkOn ) {
739 | jQuery.valHooks[ this ].get = function( elem ) {
740 | // Support: Webkit
741 | // "" is returned instead of "on" if a value isn't specified
742 | return elem.getAttribute("value") === null ? "on" : elem.value;
743 | };
744 | }
745 | });
746 |
747 | ```
748 |
749 | >内容解析
750 |
751 |
752 | ``` javascript
753 |
754 | //1. 普通元素
755 | var $text = $("#text");
756 | $text.val('ziyi2'); //类似于$text[0].value = 'ziyi2';
757 | console.log($text.val()); //ziyi2
758 |
759 |
760 | //2. option元素
761 | /**
762 | *
767 | */
768 | var $option = $('option');
769 | //原生写法
770 | console.log($option.eq(0).get(0).value); //1
771 | console.log($option.eq(1).get(0).value); //value_1
772 | console.log($option.eq(2).get(0).value); //value_2
773 |
774 | //jquery写法
775 | console.log($option.eq(0).val()); //1
776 | console.log($option.eq(1).val()); //value_1 如果value = "", 则返回""
777 |
778 |
779 |
780 | //3. select单选元素,option当有value属性时获取value属性值,否则获取元素文本内容
781 |
782 | /**
783 | *
788 | */
789 | console.log($('select').eq(0).get(0).type); //select-one
790 | console.log($('select').eq(0).val()); //1 默认第一个元素是选中的
791 |
792 |
793 | //4. select多选元素
794 | /**
795 | *
800 | */
801 | var $select = $('select').eq(1); //select-multiple
802 | console.log($select.get(0).type);
803 | console.log($select.val()); //返回的是数组 ['2','value_3']
804 | ```
805 |
--------------------------------------------------------------------------------
/功能检测.md:
--------------------------------------------------------------------------------
1 | ## 9. 功能检测
2 |
3 | - 检测(不是解决,解决是`hooks`)内部源码的兼容性
4 | - 工具方法
5 |
6 |
7 | >源码
8 |
9 | ``` javascript
10 | //[3184]
11 | //工具方法 匿名函数自执行
12 | jQuery.support = (function( support ) {
13 |
14 | //1. 动态创建元素进行功能检测
15 | var input = document.createElement("input"),
16 | fragment = document.createDocumentFragment(),
17 | div = document.createElement("div"),
18 | select = document.createElement("select"),
19 | opt = select.appendChild( document.createElement("option") );
20 |
21 | // Finish early in limited environments
22 | // 这基本没什么必要
23 | if ( !input.type ) {
24 | return support;
25 | }
26 |
27 | // 改成复选框进行测试
28 | input.type = "checkbox";
29 |
30 | // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
31 | // Check the default checkbox/radio value ("" on old WebKit; "on" elsewhere)
32 | // 老版本下是"",其他都是"on"
33 | // 解决兼容性问题就是将""改成"on"
34 | support.checkOn = input.value !== "";
35 |
36 | // Must access the parent to make an option select properly
37 | // Support: IE9, IE10
38 | // select元素 选项时检测第一项是不是选中的
39 | support.optSelected = opt.selected;
40 |
41 | // Will be defined later
42 | // 等页面加载完才能做判断,因为要进行DOM节点的操作
43 | support.reliableMarginRight = true;
44 | support.boxSizingReliable = true;
45 | support.pixelPosition = false;
46 |
47 | // Make sure checked status is properly cloned
48 | // Support: IE9, IE10
49 | // IE9 IE10下没有选中 克隆出来的checkbox没有被选中(大部分浏览器可以被选中)
50 | input.checked = true;
51 | support.noCloneChecked = input.cloneNode( true ).checked;
52 |
53 | // Make sure that the options inside disabled selects aren't marked as disabled
54 | // (WebKit marks them as disabled)
55 | // 下拉菜单被禁止,子项一般不会被禁止
56 | select.disabled = true;
57 | support.optDisabled = !opt.disabled;
58 |
59 | // Check if an input maintains its value after becoming a radio
60 | // Support: IE9, IE10
61 | // 重新创建input
62 | input = document.createElement("input");
63 | // 先去设置value值(注意顺序)
64 | input.value = "t";
65 | // 再设置radio
66 | input.type = "radio";
67 | // IE9 10 11下都是false
68 | support.radioValue = input.value === "t";
69 |
70 | // #11217 - WebKit loses check when the name is after the checked attribute
71 | //
72 | input.setAttribute( "checked", "t" );
73 | input.setAttribute( "name", "t" );
74 |
75 | fragment.appendChild( input );
76 |
77 | // Support: Safari 5.1, Android 4.x, Android 2.3
78 | // old WebKit doesn't clone checked state correctly in fragments
79 | // 老版本下克隆文档碎片不能返回设置的checked属性
80 | support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
81 |
82 | // Support: Firefox, Chrome, Safari
83 | // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
84 | // onfocus事件不能冒泡 因此不能在父元素上监听到子元素的此事件
85 | // 在IE下onfocusin事件可以冒泡
86 | support.focusinBubbles = "onfocusin" in window;
87 |
88 | // 应该不影响原有的DIV的背景属性(所有背景属性都一样)
89 | // 在IE下都会影响
90 | div.style.backgroundClip = "content-box";
91 | div.cloneNode( true ).style.backgroundClip = "";
92 | support.clearCloneStyle = div.style.backgroundClip === "content-box";
93 |
94 | // 2. 注意这个只能在DOM加载完毕后才能进行检测工作
95 | // Run tests that need a body at doc ready
96 | jQuery(function() {
97 | var container, marginDiv,
98 | // Support: Firefox, Android 2.3 (Prefixed box-sizing versions).
99 | // box-sizing css3属性 content-box标准模式 border-box怪异模式(width包括padding border等)
100 | // 会影响盒模型
101 | // 设置成标准模式
102 | divReset = "padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",
103 | body = document.getElementsByTagName("body")[ 0 ];
104 |
105 | if ( !body ) {
106 | // Return for frameset docs that don't have a body
107 | return;
108 | }
109 |
110 | container = document.createElement("div");
111 | // 创建DIV元素需要添加到body当中进行检测,设置成-9999不会影响显示
112 | container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";
113 |
114 | // Check box-sizing and margin behavior.
115 | body.appendChild( container ).appendChild( div );
116 | div.innerHTML = "";
117 | // Support: Firefox, Android 2.3 (Prefixed box-sizing versions).
118 | // 将div设置成怪异模式 width = 4px
119 | div.style.cssText = "-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%";
120 |
121 | // Workaround failing boxSizing test due to offsetWidth returning wrong value
122 | // with some non-1 values of body zoom, ticket #13543
123 | // zoom设置页面的显示比例
124 | jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() {
125 | support.boxSizing = div.offsetWidth === 4; //怪异模式下不算padding等,所以是4
126 | });
127 |
128 | // Use window.getComputedStyle because jsdom on node.js will break without it.
129 | // node.js下不会走这个
130 | if ( window.getComputedStyle ) {
131 | // top属性设置百分比,其他浏览器都会转成px,而safri仍然会返回百分比 应该转成像素才能定位
132 | support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
133 | support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
134 |
135 | // Support: Android 2.3
136 | // Check if div with explicit width and no margin-right incorrectly
137 | // gets computed margin-right based on width of container. (#3333)
138 | // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
139 | marginDiv = div.appendChild( document.createElement("div") );
140 | marginDiv.style.cssText = div.style.cssText = divReset;
141 | marginDiv.style.marginRight = marginDiv.style.width = "0";
142 | div.style.width = "1px";
143 |
144 | support.reliableMarginRight =
145 | !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
146 | }
147 |
148 | // 删除创建好的元素
149 | body.removeChild( container );
150 | });
151 |
152 | return support;
153 | })( {} );
154 | ```
155 |
156 | >内容解析
157 |
158 | 使用案例
159 |
160 | ``` javascript
161 | // $.support其实是一个json
162 | // 内部是一个自执行的匿名函数,这个匿名函数返回的是一个json
163 | for(var key in $.support) {
164 | console.log(key + ":" + $.support[key]);
165 | }
166 |
167 |
168 | /*
169 | checkOn:true
170 | optSelected:true
171 | reliableMarginRight:true
172 | boxSizingReliable:true
173 | pixelPosition:false
174 | noCloneChecked:true
175 | optDisabled:true
176 | radioValue:true
177 | checkClone:true
178 | focusinBubbles:false
179 | clearCloneStyle:true
180 | cors:true
181 | ajax:true
182 | */
183 | ```
--------------------------------------------------------------------------------
/回调对象.md:
--------------------------------------------------------------------------------
1 | ## 7. 回调对象
2 |
3 | >源码
4 |
5 | ``` javascript
6 | //[2859]
7 | /*
8 | * Create a callback list using the following parameters:
9 | *
10 | * options: an optional list of space-separated options that will change how
11 | * the callback list behaves or a more traditional option object
12 | *
13 | * By default a callback list will act like an event callback list and can be
14 | * "fired" multiple times.
15 | *
16 | * Possible options:
17 | *
18 | * once: will ensure the callback list can only be fired once (like a Deferred)
19 | *
20 | * memory: will keep track of previous values and will call any callback added
21 | * after the list has been fired right away with the latest "memorized"
22 | * values (like a Deferred)
23 | *
24 | * unique: will ensure a callback can only be added once (no duplicate in the list)
25 | *
26 | * stopOnFalse: interrupt callings when a callback returns false
27 | *
28 | */
29 | jQuery.Callbacks = function( options ) {
30 | // Convert options from String-formatted to Object-formatted if needed
31 | // (we check in cache first)
32 | options = typeof options === "string" ?
33 | ( optionsCache[ options ] || createOptions( options ) ) :
34 | jQuery.extend( {}, options );
35 |
36 | var // Last fire value (for non-forgettable lists)
37 | memory,
38 | // Flag to know if list was already fired
39 | fired,
40 | // Flag to know if list is currently firing
41 | firing,
42 | // First callback to fire (used internally by add and fireWith)
43 | firingStart,
44 | // End of the loop when firing
45 | firingLength,
46 | // Index of currently firing callback (modified by remove if needed)
47 | firingIndex,
48 | // Actual callback list
49 | list = [],
50 | // Stack of fire calls for repeatable lists
51 | stack = !options.once && [],
52 |
53 | fire = function(data) {}
54 | // Actual Callbacks object
55 | self = {
56 | add: //添加监听的回调函数
57 | remove: //移除监听的回调函数
58 | has:
59 | empty:
60 | disable:
61 | disabled:
62 | lock:
63 | locked:
64 | fireWith:
65 | fire: //执行监听的回调函数
66 | fired:
67 | }
68 |
69 | return self;
70 | }
71 | ```
72 |
73 | >内容解析
74 |
75 |
76 | (一) 闭包
77 |
78 | 闭包可以捕捉到局部变量(和参数),并一直保存下来. 如果存在嵌套的函数,函数都有各自对应的作用域链,并且这个作用域链指向一个变量绑定对象,如果函数定义了嵌套函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套的函数,就不会被当做垃圾回收,它所指向的变量绑定对象也不会被当做垃圾回收,闭包容易造成内存泄漏.
79 |
80 | - 创建闭包的常见方式就是在一个函数内部创建另一个函数
81 |
82 |
83 | ``` javascript
84 | function campareFunction(propertyName){
85 | return function(obj1,obj2){ //一个匿名的内部函数
86 | var value1 = obj1[propertyName];
87 | var value2 = obj2[propertyName];
88 |
89 | if(value1 < value2){
90 | return -1;
91 | }else if(value1 > value2){
92 | return 1;
93 | }else{
94 | return 0;
95 | }
96 | }
97 | }
98 |
99 | //即使内部的匿名函数被返回了,并且在其他地方被调用了,但它仍然可以访问propertyName
100 | //因为内部函数中的作用域链包含了campareFunction()的作用域
101 |
102 | ```
103 |
104 | - 作用域链: 当某个函数被调用时会创建一个执行环境及相应的作用域链。然后使用arguments和其他命名参数的值来初始化函数的活动对象。但是在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位.....直至作为作用域链终点的全局执行环境
105 |
106 | ``` javascript
107 | function compare1(value1,value2){
108 | if(value1 < value2){
109 | return -1;
110 | }else if(value1 > value2){
111 | return 1;
112 | }else{
113 | return 0;
114 | }
115 | }
116 | var result = compare1(5,10);
117 |
118 | //第一次调用compare函数时会创建包含this、arguments、value1和value2的活动对象
119 | //全局执行环境的变量对象(包含this[全局this指向undeifned或window对象]result和compare)在compare()执行环境的作用域链中则处于第二位
120 |
121 |
122 | compare执行环境 <--------------------------------------------------------
123 | (scope chain) --------> scope Chain |
124 | 1 --------------> Global variable object |
125 | 0 ------ compare ------
126 | | result undefined
127 | |
128 | |--------> compare() activation object
129 | arguments [5,10]
130 | value1 5
131 | value2 10
132 |
133 |
134 | //后台每个执行环境都有一个表示变量的对象----变量对象,全局环境的变量对象始终存在,
135 | //而像compare()函数这样的局部环境的变量对象,则只在函数的执行过程中存在
136 | //在创建compare()函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中
137 | //当调用compare()函数时,会为函数创建一个执行环境,然后通过赋复制函数的[[Scope]]属性中的对象构建执行环境的作用域链
138 | //此后又有一个compare活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域的<前端>!
139 | //在这里的compare执行环境作用域链包含两个变量对象,本地活动对象和全局变量对象。
140 | //作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象
141 |
142 | //一般来说,函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(Global variable object)。
143 | //但是闭包的情况却不同。
144 |
145 |
146 | function campareFunction(propertyName){
147 | return function(obj1,obj2){ //一个匿名的内部函数
148 | var value1 = obj1[propertyName];
149 | var value2 = obj2[propertyName];
150 |
151 | if(value1 < value2){
152 | return -1;
153 | }else if(value1 > value2){
154 | return 1;
155 | }else{
156 | return 0;
157 | }
158 | }
159 | }
160 |
161 |
162 | //在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中
163 | //因此在campareFunction()函数内部定义的匿名函数的作用域链中,实际上会包含外部函数campareFunction()的活动对象
164 |
165 | var compare = campareFunction("name");
166 | //name传入propertyName,且被保存了下来,因为内部返回的匿名函数被外部的变量compare所引用
167 | var result = compare({name:"Victor"},{name:"Hugo"});
168 | write(result); //1
169 |
170 | /*
171 | campareFunction执行环境
172 | (scope chain) ----> Scope Chain
173 | 1 -----------> Global variable object
174 | 0 --- campareFunction ->[campareFunction执行环境]
175 | | result
176 | compare
177 | |
178 | |
179 | --------> campareFunction() activation object
180 | arguments
181 | propertyName
182 |
183 | annoymous(匿名函数)执行环境
184 | (scope chain) ---------> Scope Chain
185 | 2 ------------> Global variable object(和上面一样)
186 | 1 ------------> campareFunction() activation object
187 | 0 ------------> Closure activation object
188 | arguments
189 | obj1
190 | obj2
191 | */
192 |
193 | //在匿名函数从campareFunction()函数中被返回后,它的作用域链初始化为包含campareFunction活动对象和全局变量对象
194 | //匿名函数就可以访问在campareFunction()函数中定义的所有变量
195 | //并且campareFunction()函数在执行完毕后活动对象也不会被销毁,
196 | //因为返回的是匿名函数,匿名函数的作用域链仍然在引用这个(campareFunction()函数的)活动对象
197 | //campareFunction返回后,campareFunction执行环境中的作用域链被销毁了,但是它的活动对象仍然会留在内存中,
198 | //直到匿名函数被销毁,campareFunction的活动对象才会被销毁
199 |
200 | //解除对匿名函数的引用(以便释放内存)
201 | compare = null;//通知垃圾回收例程将其清除,随着匿名函数的作用域链被销毁,其他作用域链(除了全局作用域)也都可以
202 |
203 | //由于闭包会携带包含它的函数的作用域
204 | //会比其他函数占用更多的内存
205 | //过度使用闭包会导致内存占用过多
206 | //在绝对必要时考虑使用闭包
207 |
208 | ```
209 |
210 | 深入理解闭包
211 |
212 | ``` javascript
213 | function creatFunction(){
214 | var result = new Array();
215 |
216 | for(var i=0; i<10; i++){
217 | result[i] = function(){
218 | return i; //注意i是外部函数的活动对象的属性,而不是匿名函数对象的属性
219 | };
220 | }
221 | return result; //返回的是一个函数数组,这个数组里的元素都是函数
222 | }
223 |
224 | var result = [];
225 | result = creatFunction();
226 |
227 | write(result[0]()); //10
228 |
229 | for(var i=0; i<10; i++){
230 | write(result[i]()); //每一个都是10
231 | }
232 |
233 |
234 | //闭包只能取得包含函数中任何变量的最后一个值
235 | //闭包保存的是整个变量对象,而不是某个特殊的变量
236 | //每个函数都返回10
237 | //因为每个函数的作用域链中都保存着creatFunction()函数的活动对象
238 | //所以它们引用的都是同一个变量i
239 | //当creatFunction函数返回后,变量i的值都是10
240 |
241 |
242 | //总结一下就是返回外部函数的时候,因为返回的是内部的匿名函数,根据匿名函数的作用域链包含着全局对象和包含它的外部函数的活动对象
243 | //所以匿名函数的作用域链仍然在引用这个外部函数的活动对象,这个外部函数的活动对象在外部函数执行完毕后仍然不会销毁
244 | //但是匿名函数指针只能指向包含外部函数最后一次执行情况的对应的活动对象里的属性值的匿名函数
245 | //闭包保存的是整个外部函数的活动对象,而不是某个变量值,这个活动对象包括arguments,函数参数以及函数内的局部变量等
246 |
247 | ```
248 |
249 | 闭包中的`this`对象
250 |
251 | ``` javascript
252 | //匿名函数的执行环境具有全局性,因此this对象通常指向window
253 | var f = function(){
254 | return function(){
255 | write(this);
256 | }();
257 | }
258 |
259 | f();//[object Window]
260 |
261 | var name = "The Window";
262 |
263 | var object = {
264 | name: "The Object",
265 |
266 | getNameFun: function(){
267 | return function(){
268 | return this.name;
269 | }
270 | }
271 | };
272 | write(object.getNameFun()()); //The Window
273 |
274 | //为什么匿名函数没有取得其包含作用域(或外部作用域)的this对象呢?
275 | //每个函数在被调用时都会自动取得两个特殊变量:this和arguments
276 | //因为这个是函数的内部属性,所以内部函数在搜索这两个变量时,只会搜索到其活动对象为止,
277 | //每个活动对象都有自己的arguments和this属性
278 | //因此永远不可能直接访问外部函数中的这两个变量
279 | //又因为匿名函数中的this对象通常指代window对象,所以返回的是The Window
280 |
281 |
282 | //补救方法
283 | var age = 13;
284 | var obj = {
285 | age:14,
286 | getAgeFun:function(){
287 | var that = this; //调试结果:that = Object {age: 14} this指代的是上下环境中的对象本身
288 | return function(){
289 | return that.age;
290 | };
291 | }
292 | };
293 |
294 | write(obj.getAgeFun()()); //14
295 |
296 | //this和arguments都存在同样的问题,如果想访问作用域中的arguments对象,
297 | //必须将对该对象的引用保存到另一个闭包能够访问的变量中
298 | ```
299 |
300 |
301 |
302 | (二) $.Callback的闭包架构
303 |
304 | ``` javascript
305 | (function(window) {
306 | ziyi2 = {};
307 |
308 | ziyi2.info = function() {
309 | //list变量是info函数的作用域链对应的活动对象的属性
310 | var list = []
311 | //返回的是一个对象,该对象的每一个属性都是函数
312 | //这些函数的活动对象不会被释放
313 | , self = {
314 | push: function(item) {
315 | //push函数的作用域可以访问外部info函数的变量
316 | //push函数的作用域链包含了外部info函数对应的活动对象
317 | list.push(item);
318 | },
319 | shift: function() {
320 | list.shift();
321 | },
322 | log: function() {
323 | console.log(list);
324 | }
325 | };
326 | return self;
327 | };
328 | window.ziyi2 = ziyi2;
329 | })(window,undefined)
330 |
331 | var info = ziyi2.info(); //info函数执行完毕后它的作用域链被销毁,但是因为内部有函数被外部info变量(var info)所引用,所以ziyi2.info函数的活动对象并没有被释放,而是放在了内部函数(push/shift/log)的作用域链中了,此时ziyi2.info函数的list数组变量并不会像其他函数一样在执行完毕后被认作局部变量而释放(垃圾回收机制判定list数组一直被保持引用,所以不会释放它)
332 | info.push(1);
333 | info.push(2);
334 | info.log(); //[1,2] 此时list数组没有被释放,所以可以得到push后的值
335 | info.shift();
336 | info.log(); //[2] list数组仍然没有被释放
337 |
338 |
339 | var info_copy = info; //ziyi2.info内部的函数被info_copy所引用
340 | info = null; //释放了info变量的引用
341 |
342 | info_copy.log(); //[2] list数组仍然没有被释放
343 | //info_copy = null; //此时释放了list数组,内存不会被泄露
344 |
345 | info = ziyi2.info();
346 | info.log(); //[] 需要注意的是这是一个新的list数组内存,和info_copy所引用的不一样
347 |
348 | info_copy = null;
349 |
350 | info.log(); //[]
351 | info = null; //释放所有内存
352 | ```
353 |
354 | (三) 使用案例解析
355 |
356 | 按顺序触发想要执行的函数
357 | ``` javascript
358 | function fn1() {
359 | console.log('111');
360 | }
361 |
362 | function fn2() {
363 | console.log('222');
364 | }
365 |
366 |
367 | var callbacks = $.Callbacks();
368 |
369 | callbacks.add(fn1);
370 | callbacks.add(fn2);
371 |
372 | callbacks.fire(); //111 222
373 | ```
374 |
375 |
376 | 即使不在同一个作用域,也可以按顺序触发想要执行的函数
377 |
378 | ``` javascript
379 | var callbacks = $.Callbacks();
380 |
381 | function fn1() {
382 | console.log('111');
383 | }
384 |
385 | function fn2() {
386 | console.log('222');
387 | }
388 |
389 | callbacks.add(fn1);
390 | callbacks.add(fn2);
391 |
392 | (function() {
393 | function fn3() {
394 | console.log('333');
395 | }
396 |
397 | callbacks.add(fn3);
398 |
399 | })();
400 |
401 | callbacks.fire(); //111 222 333 这样在外部也可以执行fn3
402 |
403 | fn3(); //fn3 is not defined(…) 默认外部不能执行
404 | ```
405 |
406 | 也可以根据条件移除不需要执行的回调函数
407 | ``` javascript
408 | var callbacks = $.Callbacks();
409 |
410 | function fn1() {
411 | console.log('111');
412 | }
413 |
414 | function fn2() {
415 | console.log('222');
416 | }
417 |
418 | callbacks.add(fn1);
419 | callbacks.add(fn2);
420 |
421 | callbacks.remove(fn2);
422 |
423 | (function() {
424 | function fn3() {
425 | console.log('333');
426 | }
427 |
428 | callbacks.add(fn3);
429 |
430 | })();
431 |
432 | callbacks.fire(); //111 333
433 | ```
434 |
435 | 同时`add`多个回调函数
436 |
437 | ``` javascript
438 | $callback = $.Callbacks();
439 | function fn1() {
440 | console.log(1);
441 | }
442 |
443 | function fn2() {
444 | console.log(2);
445 | }
446 |
447 | $callback.add(fn1,fn2);
448 | //$callback.add([fn1,fn2]) 数组也行
449 | $callback.fire(); //1 2
450 | ```
451 |
452 |
453 | (四) 参数解析
454 | - `once` 回调函数只能被执行一次
455 | - `memory` Callback.fired()之后的回调函数也会被追踪并执行
456 | - `unique` 确保回调的函数只能被添加一次
457 | - `stopOnFalse` 如果回调函数返回false则中断执行回调
458 |
459 |
460 | 所有需要执行的回调函数都会放在一个闭包的`list`数组中,只要`$.Callbacks()`不被释放,则`list`数组的内存不会被释放,执行`add`函数会添加回调函数到`list`数组中,而执行`fire`函数则会遍历执行`list`数组,需要注意`fire`函数可以传入回调函数需要执行的参数.
461 |
462 |
463 |
464 | `once`
465 |
466 | ``` javascript
467 | //没有参数
468 | var callbacks = $.Callbacks();
469 |
470 | function fn1() {
471 | console.log('111');
472 | }
473 |
474 | function fn2() {
475 | console.log('222');
476 | }
477 |
478 |
479 | callbacks.add(fn1);
480 |
481 | callbacks.fire(); //111
482 | callbacks.fire(); //111
483 |
484 | //有参数
485 | var callbacks = $.Callbacks('once');
486 |
487 | function fn1() {
488 | console.log('111');
489 | }
490 |
491 | function fn2() {
492 | console.log('222');
493 | }
494 |
495 |
496 | callbacks.add(fn1);
497 |
498 | callbacks.fire(); //111 因为有once参数,第一次执行完fire之后清空了list数组
499 | callbacks.fire(); //这个不会执行
500 | ```
501 |
502 | `memory`
503 |
504 | ``` javascript
505 | //没有参数
506 | var callbacks = $.Callbacks();
507 |
508 | function fn1() {
509 | console.log('111');
510 | }
511 |
512 | function fn2() {
513 | console.log('222');
514 | }
515 |
516 | callbacks.add(fn1);
517 | callbacks.fire(); //111
518 | callbacks.add(fn2);
519 |
520 | //有参数
521 | var callbacks = $.Callbacks("memory");
522 |
523 | function fn1() {
524 | console.log('111');
525 | }
526 |
527 | function fn2() {
528 | console.log('222');
529 | }
530 |
531 | callbacks.add(fn1);
532 | callbacks.fire(); //111
533 | callbacks.add(fn2); //222 在add的同时fire了
534 | ```
535 |
536 | `unique`
537 |
538 | ``` javascript
539 | var callbacks = $.Callbacks("unique");
540 |
541 | function fn1() {
542 | console.log('111');
543 | }
544 |
545 | function fn2() {
546 | console.log('222');
547 | }
548 |
549 |
550 | callbacks.add(fn1);
551 | callbacks.add(fn1); //第二次不会在add同样的回调函数了
552 |
553 | callbacks.fire(); //111 list数组中只有一个需要fire的回调函数
554 | ```
555 |
556 | `stopOnFalse`
557 | ``` javascript
558 | //有参数
559 | var callbacks = $.Callbacks('stopOnFalse');
560 |
561 | function fn1() {
562 | console.log('111');
563 | return false;
564 | }
565 |
566 | function fn2() {
567 | console.log('222');
568 | }
569 |
570 | callbacks.add(fn1);
571 | callbacks.add(fn2);
572 |
573 | callbacks.fire(); //111 遇到false之后break出了list,后面的回调函数就不会执行了
574 | ```
575 |
576 |
577 |
578 | ### 7. 1 `options`
579 |
580 | >源码
581 | ``` javascript
582 | // [2846]
583 | // String to Object options format cache
584 | var optionsCache = {};
585 |
586 | // Convert String-formatted options into Object-formatted ones and store in cache
587 | function createOptions( options ) {
588 | // 给每一个传入的参数创建一个optionsCache对象的属性
589 | // 因为使用$Calllback的情况可能很多
590 | // 相当于为每一个调用的$Callback创建一个设置参数的属性
591 | // 这个object可以加速设置属性值的速度
592 | var object = optionsCache[ options ] = {};
593 | // core_rnotwhite匹配空格
594 | // 例如 options -> "memory unique"
595 | // 最后变成了 optionsCache["memory unique"] = {memory:true,unique:true}
596 | jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
597 | object[ flag ] = true;
598 | });
599 | return object;
600 | }
601 |
602 |
603 | // [2882]
604 | // Convert options from String-formatted to Object-formatted if needed
605 | // (we check in cache first)
606 | // 如果传入的不是字符串,如果是对象则options = options
607 | // 否则options = {} 空对象
608 | options = typeof options === "string" ?
609 | ( optionsCache[ options ] || createOptions( options ) ) :
610 | jQuery.extend( {}, options );
611 | ```
612 |
613 |
614 |
615 | ### 7. 2 `$.Callback().add()`
616 |
617 | >源码
618 |
619 | ``` javascript
620 | // Add a callback or a collection of callbacks to the list
621 | add: function() {
622 | // 第一次进入的时候list = []
623 | if ( list ) {
624 | // First, we save the current length
625 | var start = list.length;
626 | // 这个自执行的匿名函数有什么作用?
627 | (function add( args ) {
628 | jQuery.each( args, function( _, arg ) {
629 | var type = jQuery.type( arg );
630 | if ( type === "function" ) {
631 | // 如果options.unique = true
632 | // 则继续判断是否已经添加了该回调函数
633 | // 如果已经添加,则不会push
634 | // 否则可以push
635 | if ( !options.unique || !self.has( arg ) ) {
636 | list.push( arg );
637 | }
638 | // 如果$.Callback的参数不是fn
639 | // 如果arguments是数组
640 | } else if ( arg && arg.length && type !== "string" ) {
641 | // Inspect recursively
642 | // 递归调用一个个push
643 | add( arg );
644 | }
645 | });
646 | })( arguments );
647 | // Do we need to add the callbacks to the
648 | // current firing batch?
649 | //
650 | if ( firing ) {
651 | firingLength = list.length;
652 | // With memory, if we're not firing then
653 | // we should call right away
654 | // 如果memory存在,则直接fire()
655 | // memory在内部的fire函数中会被赋值
656 | // 需要注意这个memory只有在fire函数调用之后才会继续执行
657 | // 详见7.5 (三) memory直接执行fire
658 | } else if ( memory ) {
659 | firingStart = start;
660 | fire( memory );
661 | }
662 | }
663 | // 链式调用?
664 | return this;
665 | },
666 | ```
667 |
668 |
669 | ### 7. 3 `$.Callback().remove()`
670 |
671 |
672 | ``` javascript
673 | // Remove a callback from the list
674 | remove: function() {
675 | if ( list ) {
676 | jQuery.each( arguments, function( _, arg ) {
677 | var index;
678 | // 查看是否在list数组中存在
679 | // 这里index很巧妙
680 | // 如果找不到这个函数,则不会从起始位置开始搜索
681 | // 而是从当前搜索过的index开始继续向后搜索
682 | while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
683 | // 删除数组中的当前回调函数
684 | list.splice( index, 1 );
685 | // Handle firing indexes
686 | if ( firing ) {
687 | if ( index <= firingLength ) {
688 | firingLength--;
689 | }
690 | if ( index <= firingIndex ) {
691 | firingIndex--;
692 | }
693 | }
694 | }
695 | });
696 | }
697 | return this;
698 | },
699 | ```
700 |
701 | ### 7. 4 `$.Callback().has()`
702 |
703 | ``` javascript
704 | // Check if a given callback is in the list.
705 | // If no argument is given, return whether or not list has callbacks attached.
706 | has: function( fn ) {
707 | // 如果fn存在 则遍历是否存在 存在返回true
708 | // 否则返回false
709 | // 如果不传参数则看list.length 如果有则返回true
710 | // 如果list为空,则返回false
711 | return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
712 | },
713 | ```
714 |
715 |
716 |
717 | ### 7. 5 `$.Callback().fire()/firewith()/fire()`
718 |
719 | ``` javascript
720 | // [3030]
721 | // self = { fire: function() {}}
722 | // Call all the callbacks with the given arguments
723 | fire: function() {
724 | // 传入参数arguments
725 | // 详见(一)
726 | self.fireWith( this, arguments );
727 | // 链式调用
728 | return this;
729 | },
730 |
731 | // Call all callbacks with the given context and arguments
732 | // [3017]
733 | fireWith: function( context, args ) {
734 | // 第一次fired = false
735 | // !fired = true
736 | // 之后 fired = true 详见[2905] fired
737 | // 因此要看stack
738 | // [2903] stack = !options.once && [],
739 | // 如果options.once = true 则stack = false
740 | // 因此不会fire第二次了
741 | // 如果once = false 则stack = []
742 | // 则可以继续第二次的fire
743 | // 详见(三),此时stack = false
744 | if ( list && ( !fired || stack ) ) {
745 | // 保存参数
746 | args = args || [];
747 | // args.length = 2
748 | args = [ context, args.slice ? args.slice() : args ];
749 | //详见(二)
750 | //如果[2905] fire函数正在执行回调函数的时候
751 | //在回调函数中调用了$callback.fire()函数
752 | //此时这个if就会执行了,stack默认是空数组 [].push(args)
753 | if ( firing ) {
754 | stack.push( args );
755 | // 执行fire
756 | } else {
757 | fire( args );
758 | }
759 | }
760 | return this;
761 | },
762 |
763 | // Fire callbacks
764 | // [2905]
765 | fire = function( data ) {
766 | //memory 如果为true memory = data
767 | memory = options.memory && data;
768 | //表明已经fire过一次了
769 | fired = true;
770 | firingIndex = firingStart || 0;
771 | firingStart = 0;
772 | firingLength = list.length;
773 | //正在fire
774 | firing = true;
775 | for ( ; list && firingIndex < firingLength; firingIndex++ ) {
776 | //apply第二个参数可以是数组
777 | //第一个是需要传入的this
778 | //如果stopOnFlase =true 且回调函数返回false
779 | //则跳出循环
780 | if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
781 | memory = false; // To prevent further calls using add
782 | break;
783 | }
784 | }
785 | //回调执行结束
786 | firing = false;
787 | if ( list ) {
788 | //详见(二)
789 | //如果在回调函数执行的同时进行了fire操作
790 | if ( stack ) {
791 | if ( stack.length ) {
792 | //则继续执行fire
793 | fire( stack.shift() );
794 | }
795 | //考虑 $.Callback('once memory')情况
796 | //详见(三)
797 | } else if ( memory ) {
798 | list = [];
799 | } else {
800 | self.disable();
801 | }
802 | }
803 | },
804 | ```
805 |
806 |
807 | >内容解析
808 |
809 | (一) 传入回调函数的参数
810 |
811 | ``` javascript
812 | $callback = $.Callbacks();
813 | function fn1(n) {
814 | console.log(n);
815 | }
816 |
817 | function fn2(n) {
818 | console.log(n);
819 | }
820 |
821 |
822 | $callback.add(fn1,fn2);
823 | $callback.fire('hello'); //hello hello
824 |
825 | $callback.remove(fn1,fn2).add(fn1,fn2).fire('hello1');
826 | //hello hello
827 | ```
828 |
829 | (二) 正在执行回调时进行`Callback`函数的动作
830 |
831 |
832 |
833 | ``` javascript
834 | $callback = $.Callbacks();
835 | function fn1(n) {
836 |
837 | console.log('fn1' + n);
838 | $callback.fire('hello1'); //死循环了,一直执行fn1和fn2,导致栈溢出
839 | //需要注意的是,如果没有做特殊处理,起始一直会执行fn1
840 | //但是这里也处理了fn2
841 | //内部操作,等所有的回调函数都执行完毕了,继续执行回调函数中的fire函数
842 | }
843 |
844 | function fn2(n) {
845 | console.log("fn2" + n);
846 | }
847 |
848 |
849 | $callback.add(fn1,fn2);
850 | $callback.fire('hello');
851 | ```
852 |
853 | (三) 多个参数一起使用
854 |
855 | ``` javascript
856 | var $callback = $.Callbacks('memory once');
857 |
858 | function fn1() {
859 | console.log('fn1');
860 | }
861 |
862 | function fn2() {
863 | console.log('fn2');
864 | }
865 |
866 | $callback.add(fn1);
867 | $callback.fire(); //因为memory参数,fire完毕后 list= []
868 | console.log($callback.has(fn1)); //false
869 | $callback.add(fn2); //因为memory参数,此时直接fire了, list = []
870 | console.log($callback.has(fn1)); //false
871 | console.log($callback.has(fn2)); //false
872 | $callback.fire(); //因为once,此时不会fire了
873 | ```
874 |
875 | ### 7.6 other API
876 |
877 | ``` javascript
878 | // Remove all callbacks from the list
879 | empty: function() {
880 | list = [];
881 | firingLength = 0;
882 | return this;
883 | },
884 | // Have the list do nothing anymore
885 | disable: function() {
886 | list = stack = memory = undefined;
887 | return this;
888 | },
889 | // Is it disabled?
890 | disabled: function() {
891 | return !list;
892 | },
893 | // Lock the list in its current state
894 | lock: function() {
895 | stack = undefined;
896 | if ( !memory ) {
897 | self.disable();
898 | }
899 | return this;
900 | },
901 | // Is it locked?
902 | locked: function() {
903 | return !stack;
904 | },
905 |
906 | // To know if the callbacks have already been called at least once
907 | fired: function() {
908 | return !!fired;
909 | }
910 | ```
--------------------------------------------------------------------------------
/工具方法.md:
--------------------------------------------------------------------------------
1 | ## 5. 工具方法
2 |
3 | 利用`4. $.extend()`**拷贝继承**构建工具方法,工具方法是`jQuery`的最底层方法,通常实例方法中会调用工具方法
4 |
5 | ``` javascript
6 | jQuery.extend({
7 | expando: 唯一的jQuery字符串(内部使用)
8 | noConflict: 防冲突
9 | isReady: DOM是否加载完毕(内部使用)
10 | readyWait: 等待异步文件先执行后执行DOM加载完毕事件的计数器(内部使用)
11 | holdReady(): 推迟DOM触发
12 | ready(): 准备触发DOM加载完毕后的事件
13 | isFunction(): 是否为函数
14 | isArray(): 是否为数组(不支持IE6、7、8)
15 | isWindow(): 是否为window对象
16 | isNumeric(): 是否为数字
17 | type(): 判断数据类型
18 | isPlantObject(): 判断是否为对象字面量
19 | isEmptyObject(): 判断是否为空对象
20 | error(): 抛弃异常
21 | parseHTML(): 将字符串转换成DOM数组
22 | parseJSON(): JSON.parse()
23 | parseXML():
24 | globalEval(): 类似于eval()
25 | camelCase(): 转驼峰
26 | nodeName(): 判断节点的Name
27 | each(): (类)数组遍历
28 | trim(): 去掉首位空字符
29 | makeArray(): 转数组
30 | inArray(): 查看元素在数组中的索引
31 | merge(): 合并数组
32 | grep(): 数组过滤
33 | map(): 遍历数组并修改数组元素
34 | guid: 绑定事件ID
35 | proxy(): 改变this指向
36 | access(): 多功能函数底层方法
37 | now(): 获取时间毫秒数
38 | swap(): 样式交换
39 | })
40 | ```
41 |
42 | ### 5.1 $.expando
43 | - 唯一性
44 |
45 | >源码
46 |
47 | ``` javascript
48 | //[351]
49 | // Unique for each copy of jQuery on the page
50 | //生成随机数并去掉小数点
51 | expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
52 | ```
53 |
54 | >内容解析
55 | >
56 | ``` javascript
57 | console.log($.expando); //jQuery20305959261594460556
58 | ```
59 |
60 |
61 | ### 5.2 $.noConflict
62 | - 防冲突
63 |
64 | >源码
65 |
66 | ``` javascript
67 |
68 | [37~41]
69 | //在引用jQuery库之前有使用$命令的变量,则保存引用之前的变量
70 | // Map over jQuery in case of overwrite
71 | _jQuery = window.jQuery,
72 |
73 | // Map over the $ in case of overwrite
74 | _$ = window.$,
75 |
76 |
77 | //[353]
78 | noConflict: function( deep ) {
79 | //在[37-41]利用_$缓存引用库之前的$值
80 | //库加载完毕后[8826]先执行
81 | //此时把$值在jQuery中的引用放弃掉
82 | //详见(二)
83 | if ( window.$ === jQuery ) {
84 | window.$ = _$;
85 | }
86 | //加入参数true以后和(二)一样,放弃掉jQuery变量的命名
87 | if ( deep && window.jQuery === jQuery ) {
88 | window.jQuery = _jQuery;
89 | }
90 |
91 | //详见(一)
92 | return jQuery;
93 | },
94 |
95 |
96 | //[8826]
97 | window.jQuery = window.$ = jQuery
98 | ```
99 |
100 | >内容解析
101 |
102 | (一)、`$`在引用`jQuery`库之后改变
103 |
104 | ``` javascript
105 | var new$ = $.noConflict(); //创建$变量的副本
106 | $ = 2017; //$本身的值改变
107 |
108 | new$(function(){
109 | alert(new$().jquery); //2.0.3 new$ = 未改变之前的$值
110 | alert($); //2017 $值被改变
111 | })
112 | ```
113 |
114 | (二)、`$`在引用`jQuery`库之前已经存在
115 |
116 | ``` javascript
117 |
118 | //保留引用库前的$
119 |
122 |
123 |
124 |
125 |
126 |
130 |
131 |
132 | //保留引用库前的jQuery
133 |
136 |
137 |
138 |
139 |
140 |
144 |
145 | ```
146 |
147 | ### 5.3 $.ready()
148 | - `DOM`加载完毕的触发事件
149 | - `$(function(){})`
150 | - `$(document).ready()`
151 | - `DOMContentLoaded`事件(等文档流、普通脚本、延迟脚本加载完毕后触发)
152 | - `load`事件(等文档流、普通脚本、延迟脚本、异步脚本、图片等所有内容加载完毕后触发)
153 |
154 | >源码
155 |
156 | ``` javascript
157 |
158 | //历程: $(function(){}) -> $(document).ready() -> $.ready.promise().done(fn) -> complete()回调 -> $.ready()
159 |
160 | // 步骤一、[182]
161 | // HANDLE: $(function)
162 | // Shortcut for document ready
163 | } else if ( jQuery.isFunction( selector ) ) {
164 | //$(document).ready(function(){}) 调用了[240]的$().ready()
165 | return rootjQuery.ready( selector );
166 | }
167 |
168 | // 步骤二、[240-245]
169 | //$().ready()
170 | ready: function( fn ) {
171 | // 使用Promise的形式等待回调
172 | // 创建了延迟对象
173 | jQuery.ready.promise().done( fn );
174 | return this;
175 | },
176 |
177 |
178 | // 步骤三、[819]
179 | jQuery.ready.promise = function( obj ) {
180 | //第一次是空对象,可以进入if
181 | if ( !readyList ) {
182 | //创建延迟对象
183 | readyList = jQuery.Deferred();
184 |
185 | // Catch cases where $(document).ready() is called after the browser event has already occurred.
186 | // we once tried to use readyState "interactive" here, but it caused issues like the one
187 | // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
188 |
189 | //if和else都是在DOM加载完毕后执行$.ready()
190 |
191 | //详见(一) DOM加载完毕 IE会提前出发
192 | if ( document.readyState === "complete" ) {
193 | // Handle it asynchronously to allow scripts the opportunity to delay ready
194 | // hack写法,兼容IE
195 | setTimeout( jQuery.ready );
196 |
197 | } else {
198 |
199 | // Use the handy event callback
200 | // DOM没有加载完毕时,监测
201 | document.addEventListener( "DOMContentLoaded", completed, false );
202 |
203 | // A fallback to window.onload, that will always work
204 | // 如果浏览器有缓存事件,则load会比DOMContentLoaded先触发,所以两个事件都要监听
205 | window.addEventListener( "load", completed, false );
206 | }
207 | }
208 | //promise的状态不能被修改
209 | return readyList.promise( obj );
210 | };
211 |
212 |
213 |
214 | //步骤四、[89]
215 | //complete()回调
216 | //这是一个自执行匿名函数中的局部函数,在自执行匿名函数内都可见,所以上述监听事件可以直接调用
217 |
218 | // The ready event handler and self cleanup method
219 | completed = function() {
220 | //尽管在jQuery.ready.promise两个事件都监听了,但是这里都取消了,所以任何一个监听事件触发,另外一个监听事件因为取消了不会再次执行,jQuery.ready();只会执行一次
221 | document.removeEventListener( "DOMContentLoaded", completed, false );
222 | window.removeEventListener( "load", completed, false );
223 | jQuery.ready();
224 | };
225 |
226 | //步骤五、[382]
227 | //$.ready()
228 |
229 | // Handle when the DOM is ready
230 | ready: function( wait ) {
231 | //和$.holdRady()有关
232 | //--jQuery.readyWait如果hold了N次,则不会触发DOM加载事件,而是返回
233 | //如果jQuery.isReady为true则已经触发了一次了
234 | // Abort if there are pending holds or we're already ready
235 | if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
236 | return;
237 | }
238 |
239 | // Remember that the DOM is ready
240 | jQuery.isReady = true;
241 |
242 | // If a normal DOM Ready event fired, decrement, and wait if need be
243 | // 如果释放hold,则全部释放完后才能继续执行下面的DOM加载事件,否则return
244 | if ( wait !== true && --jQuery.readyWait > 0 ) {
245 | return;
246 | }
247 |
248 | // 在jQuery.ready.promise()中的延迟对象触发回调
249 | // 触发步骤二的jQuery.ready.promise().done( fn );回调函数done()
250 | // 平时用readyList.resolve()
251 | // 但是readyList.resolveWith()可以传递参数
252 | // 使this指向document
253 | // 传入的参数指向jQuery,详见(二)
254 | // If there are functions bound, to execute
255 | readyList.resolveWith( document, [ jQuery ] );
256 |
257 | // Trigger any bound ready events
258 | //主动触发
259 | //$(documnent).on('ready',function(){
260 | //})
261 | //详见(三)
262 | if ( jQuery.fn.trigger ) {
263 | jQuery( document ).trigger("ready").off("ready");
264 | }
265 | },
266 |
267 | ```
268 |
269 | >内容解析
270 |
271 | (一)、浏览器加载页面过程
272 |
273 | - 创建`Document`对象,解析Web页面,解析HTML元素和相应的文本内容添加`Element`对象和`Text`节点到文档中,此时`document.readyState = 'loading'`
274 | - 当HTML解析器遇到没有`async`和`defer`属性的`
559 |
564 |