├── .gitignore ├── Jquery2.0.3.js ├── README.md ├── jQuery.md ├── jQuery对象的属性和方法.md ├── 事件操作.md ├── 元素属性.md ├── 功能检测.md ├── 回调对象.md ├── 工具方法.md ├── 延迟对象.md ├── 总体架构.md ├── 拷贝继承.md ├── 数据缓存.md ├── 私有属性.md └── 队列管理.md /.gitignore: -------------------------------------------------------------------------------- 1 | # html 2 | index.html 3 | 4 | # Idea 5 | .idea 6 | 7 | # MAC 8 | .DS_Store 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 这里加入了很多对于原生`JavaScript`的理解,忽略了`Sizzle`选择器(它可以单独抽离出来使用`Sizzle.js`框架)的源码分析,同时由于`13.事件操作`源码相对比较复杂,只是粗略的进行了源码的调试和说明,对于`Jquery`如何监听事件以及取消监听的原理,代码执行顺序和兼容性问题处理有了粗略理解,后续有空会继续深入分析源码的实现原理. 4 | 5 | ## 完整版 6 | 7 | - [jQuery 2.0.3源码分析](https://github.com/ziyi2/jQuery/blob/master/jQuery.md) 8 | 9 | ## 1. 总体架构 10 | 11 | - [自执行匿名函数](https://github.com/ziyi2/jQuery/blob/master/%E6%80%BB%E4%BD%93%E6%9E%B6%E6%9E%84.md#1-1-%E8%87%AA%E6%89%A7%E8%A1%8C%E5%8C%BF%E5%90%8D%E5%87%BD%E6%95%B0) 12 | 13 | ## 2.私有属性 14 | 15 | - [`rootjQuery`](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#21-rootjquery) 16 | - [`readyList`](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#22-readylist) 17 | - [`core_strundefined`](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#23-core_strundefined) 18 | - [`window`](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#24-window%E5%B1%9E%E6%80%A7) 19 | - [`_`](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#25-_%E5%8F%98%E9%87%8F) 20 | - [`class2type`](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#26-class2type) 21 | - [`core_deletedIds`](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#27-core_deletedids) 22 | - [`core_version`](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#28-core_version) 23 | - [数组、对象、字符串方法](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#29-%E6%95%B0%E7%BB%84%E5%AF%B9%E8%B1%A1%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%96%B9%E6%B3%95) 24 | - [`jQuery`](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#210-jquery%E9%87%8D%E7%82%B9) 25 | - [正则变量](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#211-%E6%AD%A3%E5%88%99%E5%8F%98%E9%87%8F) 26 | - [`fcamelCase`](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#212--fcamelcase) 27 | - [`completed`](https://github.com/ziyi2/jQuery/blob/master/%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7.md#213--completed) 28 | 29 | 30 | ## 3. jQuery对象的属性和方法 31 | 32 | - [`$().jquery`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#31-jquery) 33 | - [`$().constructor`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#32-constructor) 34 | - [`$().init()`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#33-init-jquery%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E6%96%B9%E6%B3%95) - jQuery构造函数方法 35 | - [`$().selector`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#34-selector) 36 | - [`$().length`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#35-length) 37 | - [`$().toArray()`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#36-toarray) 38 | - [`$().get()`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#37-get) 39 | - [`$().pushStack()`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#38-pushstack) 40 | - [`$().end()`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#39-end) 41 | - [`$().slice()`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#310-slice) 42 | - [`$().each()`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#311-each) 43 | - [`$().ready()`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#312-ready) 44 | - [`$().first()/last()/eq()`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#313-firstlast-eq) 45 | - [`$().map()`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#314-map) 46 | - [`$().push()/sort()/slice()`](https://github.com/ziyi2/jQuery/blob/master/jQuery%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.md#315-pushsortslice) 47 | 48 | 49 | ## 4. 拷贝继承 50 | 51 | - [拷贝继承](https://github.com/ziyi2/jQuery/blob/master/%E6%8B%B7%E8%B4%9D%E7%BB%A7%E6%89%BF.md#4-%E6%8B%B7%E8%B4%9D%E7%BB%A7%E6%89%BF) 52 | 53 | ## 5. 工具方法 54 | 55 | - [`$.expando`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#51-expando) 56 | - [`$.noConflict`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#52-noconflict) 57 | - [`$.ready()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#53-ready) 58 | - [`$.holdReady()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#54-holdready) 59 | - [`$.isFunction()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#55-isfunction) 60 | - [`$.isArray()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#56-isarray) 61 | - [`$.isWindow()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#57-iswindow) 62 | - [`$.isNumeric()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#58-isnumeric) 63 | - [`$.type()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#59-type) 64 | - [`$.isPlantObject()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#510-isplantobject) 65 | - [`$.isEmptyObject()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#511-isemptyobject) 66 | - [`$.error()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#512-error) 67 | - [`$.parseHTML()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#513-parsehtml) 68 | - [`$.parseJSON()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#514-parsejson) 69 | - [`$.parseXML()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#515-parsexml) 70 | - [`$.noop()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#516-noop) 71 | - [`$.globalEval()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#517-globaleval) 72 | - [`$.camelCase()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#518-camelcase) 73 | - [`$.nodeName()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#519-nodename) 74 | - [`$.each()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#520-each) 75 | - [`$.trim()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#521-trim) 76 | - [`$.makeArray()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#522-makearray) 77 | - [`$.inArray()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#523-inarray) 78 | - [`$.merge()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#524-merge) 79 | - [`$.grep()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#525-grep) 80 | - [`$.map()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#526-map) 81 | - [`$.guid`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#527-guid) 82 | - [`$.proxy()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#528-proxy) 83 | - [`$.access()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#529-access) 84 | - [`$.now()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#530-now) 85 | - [`$.swap()`](https://github.com/ziyi2/jQuery/blob/master/%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95.md#531-swap) 86 | 87 | ## 6. 选择器Sizzle 88 | 89 | 忽略了Sizzle选择器(它可以单独抽离出来使用Sizzle.js框架)的源码分析。 90 | 91 | 92 | ## 7. 回调对象 93 | 94 | - [`options`](https://github.com/ziyi2/jQuery/blob/master/%E5%9B%9E%E8%B0%83%E5%AF%B9%E8%B1%A1.md#7-1-options) 95 | - [`$.Callback().add()`](https://github.com/ziyi2/jQuery/blob/master/%E5%9B%9E%E8%B0%83%E5%AF%B9%E8%B1%A1.md#7-2-callbackadd) 96 | - [`$.Callback().remove()`](https://github.com/ziyi2/jQuery/blob/master/%E5%9B%9E%E8%B0%83%E5%AF%B9%E8%B1%A1.md#7-3-callbackremove) 97 | - [`$.Callback().has()`](https://github.com/ziyi2/jQuery/blob/master/%E5%9B%9E%E8%B0%83%E5%AF%B9%E8%B1%A1.md#7-4-callbackhas) 98 | - [`$.Callback().fire()/firewith()/fire()`](https://github.com/ziyi2/jQuery/blob/master/%E5%9B%9E%E8%B0%83%E5%AF%B9%E8%B1%A1.md#7-5-callbackfirefirewithfire) 99 | - [other API](https://github.com/ziyi2/jQuery/blob/master/%E5%9B%9E%E8%B0%83%E5%AF%B9%E8%B1%A1.md#76-other-api) 100 | 101 | 102 | ## 8. 延迟对象 103 | 104 | - [`$.Deffered()`](https://github.com/ziyi2/jQuery/blob/master/%E5%BB%B6%E8%BF%9F%E5%AF%B9%E8%B1%A1.md#81-deffered) 105 | - [`$.when()`](https://github.com/ziyi2/jQuery/blob/master/%E5%BB%B6%E8%BF%9F%E5%AF%B9%E8%B1%A1.md#82-when) 106 | 107 | ## 9. 功能检测 108 | 109 | - [功能检测](https://github.com/ziyi2/jQuery/blob/master/%E5%8A%9F%E8%83%BD%E6%A3%80%E6%B5%8B.md) 110 | 111 | ## 10. 数据缓存 112 | 113 | - [`Date`构造函数](https://github.com/ziyi2/jQuery/blob/master/%E6%95%B0%E6%8D%AE%E7%BC%93%E5%AD%98.md#101-date%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0) 114 | - [`data`工具方法](https://github.com/ziyi2/jQuery/blob/master/%E6%95%B0%E6%8D%AE%E7%BC%93%E5%AD%98.md#102-data%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95) 115 | - [`data`实例方法](https://github.com/ziyi2/jQuery/blob/master/%E6%95%B0%E6%8D%AE%E7%BC%93%E5%AD%98.md#103-data%E5%AE%9E%E4%BE%8B%E6%96%B9%E6%B3%95) 116 | 117 | ## 11. 队列管理 118 | 119 | - [`queue`工具方法](https://github.com/ziyi2/jQuery/blob/master/%E9%98%9F%E5%88%97%E7%AE%A1%E7%90%86.md#111-queue%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95) 120 | - [`queue`实例方法](https://github.com/ziyi2/jQuery/blob/master/%E9%98%9F%E5%88%97%E7%AE%A1%E7%90%86.md#112-queue%E5%AE%9E%E4%BE%8B%E6%96%B9%E6%B3%95) 121 | 122 | ## 12.元素属性 123 | 124 | - [`attr()`](https://github.com/ziyi2/jQuery/blob/master/%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7.md#121-attr) 125 | - [`removeAttr()`](https://github.com/ziyi2/jQuery/blob/master/%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7.md#122-removeattr) 126 | - [`prop()`](https://github.com/ziyi2/jQuery/blob/master/%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7.md#123-prop) 127 | - [`removeProp()`](https://github.com/ziyi2/jQuery/blob/master/%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7.md#124-removeprop) 128 | - [`addClass()`](https://github.com/ziyi2/jQuery/blob/master/%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7.md#125-addclass) 129 | - [`removeClass()`](https://github.com/ziyi2/jQuery/blob/master/%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7.md#126-removeclass) 130 | - [`hasClass()`](https://github.com/ziyi2/jQuery/blob/master/%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7.md#128-hasclass) 131 | - [`val()`](https://github.com/ziyi2/jQuery/blob/master/%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7.md#129-val) 132 | 133 | 134 | ## 13. 事件操作 135 | 136 | - [JQuery实例对象扩展`jQuery.fn.extend`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#131-jquery%E5%AE%9E%E4%BE%8B%E5%AF%B9%E8%B1%A1%E6%89%A9%E5%B1%95jqueryfnextend) 137 | - [`$().on()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1311-on) 138 | - [`$().one()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1312-one) 139 | - [`$().off()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1313-off) 140 | - [`$().trigger()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1314-trigger) 141 | - [`$().triggerHandler()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1315-triggerhandler) 142 | - [事件工具对象`jQuery.Event`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#132-%E4%BA%8B%E4%BB%B6%E5%B7%A5%E5%85%B7%E5%AF%B9%E8%B1%A1jqueryevent) 143 | - [`$.event.add()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1321-eventadd) 144 | - [`$.event.dispatch()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1322-eventdispatch) 145 | - [`$.event.handlers()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1323-eventhandlers) 146 | - [`$.event.fix()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1324-eventfix) 147 | - [`$.event.special()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1325-eventspecial) 148 | - [`$.event.trigger()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1326-eventtrigger) 149 | - [`$.event.simulate()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1327-eventsimulate) 150 | - [`$.event.remove()`](https://github.com/ziyi2/jQuery/blob/master/%E4%BA%8B%E4%BB%B6%E6%93%8D%E4%BD%9C.md#1328-eventremove) 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /jQuery对象的属性和方法.md: -------------------------------------------------------------------------------- 1 | ## 3. jQuery对象的属性和方法 2 | 3 | 为`jQuery`的原型添加方法和属性,这些方法和属性可以被`jQuery`的实例对象所使用.原型对象的属性会被实例对象继承,当然如果实例对象已经具备相应的属性,则会把原型对象的同名属性覆盖掉. 4 | 5 | ``` javascript 6 | jQuery.fn = jQuery.prototype = { 7 | jquery: 版本号, 8 | constructor: 修正指向问题, 9 | init(): 初始化和参数管理(构造函数), 10 | selector: 实例化对象时的初始化选择器, 11 | length: 默认的Jquery对象的长度是0, 12 | toArray(): 转数组(也可以是对外的实例方法), 13 | get(): 转原生集合,其实也是转成数组形式(对外方法), 14 | pushStack(): jQuery对象的一个入栈处理(外部用的不多,内部用的对), 15 | each(): 遍历集合, 16 | ready(): DOM加载的接口, 17 | slice(): 集合的截取, 18 | eq(): 集合的第一项, 19 | last(): 集合的最后一项, 20 | eq(): 集合的指定项, 21 | map(): 返回新集合, 22 | end(): 栈回溯,可以看做popStack(), 23 | push(): (内部使用), 24 | sort(): (内部使用), 25 | splice(): (内部使用) 26 | } 27 | ``` 28 | 29 | ### 3.1 $().jquery 30 | - 版本号 31 | ``` javascript 32 | console.log($().jquery); //2.0.3 33 | ``` 34 | 35 | ### 3.2 $().constructor 36 | 37 | 默认的构造函数的原型的`constructor`属性指向该构造函数,但是`constructor`属性很容易被修改.所以可以在原型对象的`constructor`属性中进行修正. 38 | 39 | >源码 40 | 41 | ```javascript 42 | //[100] 43 | constructor: jQuery, 44 | ``` 45 | 46 | 47 | 48 | >内容解析 49 | 50 | ``` javascript 51 | function Obj() {} 52 | alert(Obj.prototype.constructor); //function Obj() {} 53 | 54 | Obj.prototype.init = function(){ 55 | }; 56 | Obj.prototype.css = function(){ 57 | }; 58 | 59 | //使用对象字面量的方法,利用新的对象将Obj.prototype进行了覆盖 60 | Obj.prototype = { 61 | init: function(){ 62 | }, 63 | css: function(){ 64 | } 65 | }; 66 | alert(Obj.prototype.constructor); //function Object{[native code]} 67 | ``` 68 | 69 | ### 3.3 $().init() (jQuery构造函数方法) 70 | 71 | 对外提供的实例对象的接口是`$()`或者`jQuery()`,当调用`$()`的时候其实是调用了`init()或者说jQuery()`,然后返回的是`jQuery的实例对象`,这样就可以使用`jQuery对象`的`prototype`的方法和属性(因为继承关系),`init()`方法的功能是`初始化jQuery的实例对象`. 72 | 73 | 74 | >源码 75 | 76 | ``` javascript 77 | //[101] 78 | 79 | /** 80 | - selector: 选择器 81 | - context: 包含选择器的元素,选择器的上下文,详细说明:如果没有指定上下文context, 则默认情况将从根元素document对象开始查找,查找范围是整个文档,如果传入上下文,则可以限定查找范围,这样明显可以提高效率. 82 | - rootjQuery 83 | */ 84 | 85 | init: function( selector, context, rootjQuery ) { 86 | var match, elem; 87 | 88 | // HANDLE: $(""), $(null), $(undefined), $(false) 89 | if ( !selector ) { 90 | return this; //详见(一)、(二) 91 | } 92 | 93 | // Handle HTML strings 94 | if ( typeof selector === "string" ) { 95 | 96 | //判断是否为$('
  • '), $('
  • 1
  • 2
  • ')类型,当然也可能是$('
  • ')这种非合法类型 97 | //即判断是否为复杂的HTML标签(注意也包括单个吧) 98 | if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { 99 | // Assume that strings that start and end with <> are HTML and skip the regex check 100 | match = [ null, selector, null ]; 101 | 102 | } else { 103 | //判断是否为单个标签或者id 104 | // 比如'#div','

  • hello','<>','
    ' 105 | //注意'
    ',
  • 1
  • 2
  • ,也能被匹配,但是第一个if已经匹配走了 106 | //macth值 107 | //如果是html标签,则 match[2] = undefined 108 | //如果是#id,则match[1] = undefined 109 | match = rquickExpr.exec( selector ); 110 | } 111 | 112 | //匹配html标签(注意html标签还是可以有context的)或者或者没有context的#id 113 | //类似于if( match && (match[1] || (macth[2] && !context) ) 114 | //原因是match[1]如果是undefined那么macth[2]必定不是undefined 115 | //此时match[2]当然可以省略啦 116 | //还需要注意的是这里处理了包括第一部分的if 117 | //尽管第一部分的if里match[0] = null 118 | // Match html or make sure no context is specified for #id 119 | if ( match && (match[1] || !context) ) { 120 | //第一种情况匹配html标签 121 | //包括第一部分的if情况 122 | //需要注意的还有匹配html标签还是可以有context的 123 | //HANDLE: $(html) -> $(array) 124 | //这里当然是要把html转换成dom数组的形式 125 | //例如$('
  • 1
  • 2
  • ') 126 | //转换成 127 | //{ 128 | // 0: li, 129 | // 1: li, 130 | // length:2 131 | //} 132 | // HANDLE: $(html) -> $(array) 133 | if ( match[1] ) { 134 | //$('
  • ',document) 135 | //$('
  • ',contentWindow.document) iframe的document 136 | //当然如果context为undefined的情况下仍然是undefined 137 | //$('
  • ',$(document)) 此时 context instanceof jQuery = true 138 | //var context = $(document); 139 | //console.log(context instanceof jQuery); //true 140 | //之后可能会这么用 141 | //$('
  • ',$(document)); 142 | //console.log(context[0]); //document 143 | //所以如果context = $(document) jQuery对象 144 | //那么必须使context = $(document)[0] DOM对象 145 | //context详见(三) 146 | context = context instanceof jQuery ? context[0] : context; 147 | 148 | //合并DOM数组到this对象(this是json格式的类数组对象) 149 | //这样之后才可以进行css(),appendTo()等操作(操作this对象) 150 | // scripts is true for back-compat 151 | //详见(四)、(五) 152 | jQuery.merge( this, jQuery.parseHTML( 153 | match[1], 154 | context && context.nodeType ? context.ownerDocument || context : document, 155 | true 156 | ) ); 157 | 158 | 159 | //例如 $('
    ',{title: 'div', html: 'adcd', css: {background:'red'}}) 160 | //创建标签的同时带属性,平时用的很少 161 | //rsingleTag匹配单标签
  • 也就是selector第一个参数必须是单标签 162 | //第二个参数也就是context必须是对象字面量 163 | // HANDLE: $(html, props) 164 | if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { 165 | for ( match in context ) { 166 | // Properties of context are called as methods if possible 167 | //这里把match冲掉了,不用开辟新的变量 168 | //同时我们知道$().html()方法是存在的 169 | //所以遍历context时候,如果有html属性 170 | //这个if就会满足 171 | //并调用this.html('abcd') 172 | if ( jQuery.isFunction( this[ match ] ) ) { 173 | this[ match ]( context[ match ] ); 174 | //否则例如像titile的属性,没有$().title()方法 175 | //则调用this.attr(title,'div'); 176 | //当然也可以使用jQuery.attr() 177 | // ...and otherwise set as attributes 178 | } else { 179 | this.attr( match, context[ match ] ); 180 | } 181 | } 182 | } 183 | 184 | //到这里就返回this对象了,我们当然始终要记得这个this对象是在什么时候使用 185 | //$('div')返回带DOM节点的this对象,是一个类数组对象 186 | //然后d调用css(), appendTo()等方法使用喽,在这些方法里操作this 187 | //this始终是构造函数的上下文环境喽 188 | //那么一旦创建了实例,this当然也是实例对象喽 189 | //可以链式调用 190 | return this; 191 | 192 | // HANDLE: $(#id) 193 | //如果macth[1]是undefined 194 | //那么match[2]肯定不是undefined 195 | //match[2]当然是用来匹配#id的情况 196 | } else { 197 | //简单粗暴获取id为match[2]的节点试试 198 | elem = document.getElementById( match[2] ); 199 | 200 | // Check parentNode to catch when Blackberry 4.6 returns 201 | // nodes that are no longer in the document #6963 202 | //如果节点存在 203 | //当然如果黑莓4.6就算是节点不存在也会返回true 204 | //所以此时需要多个判断条件,就是判断节点的父节点是否存在 205 | //因为不存在的节点肯定是没有父节点的 206 | if ( elem && elem.parentNode ) { 207 | // Inject the element directly into the jQuery object 208 | //给构造函数jQuery添加length属性,因为是id嘛,所以当然只有一个DOM节点喽 209 | this.length = 1; 210 | //将DOM节点赋值给this类数组对象,方便日后操作 211 | this[0] = elem; 212 | } 213 | //没有context的#id上下文当然是document 214 | this.context = document; 215 | //选择器仍然是本身 216 | this.selector = selector; 217 | //返回this方便操作喽 218 | return this; 219 | } 220 | 221 | // HANDLE: $(expr, $(...)) 222 | //context.jquery判断如果是jQuery对象 223 | } else if ( !context || context.jquery ) { 224 | //例如$('ul',$(document)).find('li') 225 | //$(document).find() 226 | //调用了Sizzle选择器 227 | return ( context || rootjQuery ).find( selector ); 228 | 229 | // HANDLE: $(expr, context) 230 | //例如$('ul',document).find('li') 231 | // (which is just equivalent to: $(context).find(expr) 232 | } else { 233 | return this.constructor( context ).find( selector ); 234 | } 235 | //以上两种判断$(expr, $(...))、$(expr, context)最终变成$(document).find()调用Sizzle选择器 236 | 237 | // HANDLE: $(DOMElement) 238 | //如果不是字符串,是节点 239 | } else if ( selector.nodeType ) { 240 | //这里仍然要将this转换成类数组对象 241 | //console.log($(document)); 242 | /** 243 | * { 244 | * 0: document (节点对象) 245 | * context: document (仍然是节点对象,这个是传入的节点对象) 246 | * length: 1 247 | * } 248 | */ 249 | this.context = this[0] = selector; 250 | this.length = 1; 251 | return this; 252 | 253 | // HANDLE: $(function) 254 | // Shortcut for document ready 255 | //例如$(function(){}),实际仍然调用,$(document).ready(function(){}) 256 | } else if ( jQuery.isFunction( selector ) ) { 257 | //$(document).ready(function(){}) 258 | return rootjQuery.ready( selector ); 259 | } 260 | 261 | //看传入的参数是不是jQuery对象 例如 $( $('div') ) 262 | if ( selector.selector !== undefined ) { 263 | this.selector = selector.selector; 264 | this.context = selector.context; 265 | } 266 | //类似于jQuery.merge方法 267 | //写一个参数对外是用来转成数组 268 | //由于我们需要返回的是this这种类数组对象 269 | //所以写两个参数则是可以转成类数组对象this 270 | //详见(六) 271 | return jQuery.makeArray( selector, this ); 272 | }, 273 | ``` 274 | 275 | 276 | >内容解析 277 | 278 | (一)、构造函数返回值问题 279 | 280 | ``` javascript 281 | //构造函数一般不需要返回值 282 | function Obj() { 283 | this.a = 1; 284 | this.b = 2; 285 | } 286 | var o = new Obj(); 287 | alert(o.a); 288 | 289 | //返回this,实现链式调用 290 | function Obj1() { 291 | this.a = 1; 292 | this.b = 2; 293 | return this; 294 | } 295 | var o1 = new Obj1(); 296 | alert(o1.a); //1 297 | 298 | //返回引用类型的值,则返回有效 299 | function Obj2() { 300 | this.a = 1; 301 | this.b = 2; 302 | return { 303 | a: 3, 304 | b: 4 305 | }; 306 | } 307 | var o2 = new Obj2(); 308 | alert(o2.a); //3 309 | 310 | //返回非引用类型,返回值无效 311 | function Obj3() { 312 | this.a = 1; 313 | this.b = 2; 314 | return undefined; 315 | } 316 | var o3 = new Obj3(); 317 | alert(o3.a); //1 318 | ``` 319 | 320 | ``` javascript 321 | function a() { 322 | this.attr = 0; 323 | this.func = function() { 324 | alert("a-func"); 325 | } 326 | } 327 | a.prototype = { 328 | func2:function() { 329 | alert("a-func2"); 330 | return this; 331 | }, 332 | func3:function() { 333 | alert('chain'); 334 | return this; 335 | } 336 | } 337 | //链式调用 338 | var aa = (new a()).func2().func3(); 339 | ``` 340 | 341 | (二)、构造函数返回值`this`(可以理解为返回引用值类型有效么?) 342 | 343 | `this`作为类数组对象可以进行`for`循环处理,需要注意`$()`是`jQuery`对象,而`$()[0]`是DOM元素。 344 | 345 | 346 | | jQuery中构造函数的this属性(注意不是jQuery原型对象的属性) | 描述| 347 | | :-------- | :--------| 348 | | `this`中数字属性,例如0,1,2…,也可以说是类数组对象的数字属性(本来数组里的索引就是特殊的字符串属性嘛,例如数组a[0]其实就是a[‘0’]嘛)| 每一个属性代表一个被选中的DOM元素| 349 | | `length`| 选取DOM元素数组的长度,也是`this`类数组的长度 | 350 | | `context`| 选取的上下文环境,例如`document`或者`$(document)` | 351 | | `selector`| 选择器 | 352 | 353 | 354 | (三)、`context`具体使用案例(重点,加速查找DOM元素) 355 | 356 | ``` javascript 357 | $('h1').click(function() { 358 | $('strong',this).css('color','blue'); //this指代h1 限定查找范围 359 | }); 360 | //$('h1')因为没有指定上下文,所以调用浏览器原生方法document.getElementById方法查找属性为id的元素 361 | //$('strong',this)因为指定了上下文,则通过jQuery的.find()方法查找,因此等价于$('h1').find('strong') 362 | ``` 363 | 364 | (四)、`$.parseHTML`、`$.merge` 365 | 366 | ```javascript 367 | $('
  • 1
  • 2
  • ').appendTo('ul'); //添加成功 368 | //$('
  • 1
  • 2
  • ')当然要变成如下格式,然后才能调用appendTo方法去处理 369 | /** 370 | * this = { 371 | * 0:'li', 372 | * 1:'li', 373 | * length: 2 374 | * } 375 | */ 376 | //jQuery.parseHTML 把字符串转成节点数组 377 | //参数3个 378 | //1.str字符串 379 | //2.指定根节点 380 | //3:true or false 381 | var str = '
  • 3
  • 4
  • 需要注意,需要转义 382 | // var arr = jQuery.parseHTML(str,document); //不会弹alert script标签不被添加 383 | var arr = jQuery.parseHTML(str,document,true); //弹alert script标签被添加 384 | console.log(arr); 385 | /** 386 | * [ 387 | * 0: li, //变成DOM中的li节点了 388 | * 1: li, 389 | * 2: script, 390 | * length:3 391 | * ] 392 | * 393 | */ 394 | $.each(arr,function(i) { 395 | $('ul').append(arr[i]); //添加成功 396 | }); 397 | //jQuery.parseHTML返回的是数组,但是最终在$('
  • 1
  • 2
  • ')中我们发现需要的是转成json格式的this对象 398 | //于是jQuery.merge起作用啦 399 | var a = [1,2], 400 | b = [3,4]; 401 | 402 | console.log($.merge(a,b)); //[1,2,3,4] 对外是数组合并的功能 403 | var json = { //类数组对象 404 | 0: 1, 405 | 1: 2, 406 | length:2 407 | }; 408 | var arr = [3,4]; 409 | console.log($.merge(json,arr)); //将json和数组合在了一起 源代码中的this就是json格式 410 | /** 411 | * { 412 | 0: 1, 413 | 1: 2, 414 | 2: 3, 415 | 3: 4 416 | length:4 417 | }; 418 | */ 419 | ``` 420 | 421 | 422 | (五)、`this`在jQuery中的实际使用案例 423 | 424 | ``` javascript 425 | //传统写法 426 | var liArray = document.getElementsByTagName('li'); 427 | for(var index in liArray) { 428 | liArray[index].style.color = 'blue'; //liArray[index]是DOM元素对象 429 | } 430 | 431 | //jQuery使用案例 432 | $('li').css('color','red'); //$('li')是jQuery对象 433 | 434 | 435 | var J = $('li'); //jQuery实例对象 436 | 437 | J.css('color','red'); //J并不是DOM元素对象 438 | console.log(J); //是一个类数组对象 439 | /* 440 | { 441 | 0:li, 442 | 1:li, 443 | ... 444 | 7:li, 445 | context:document, //上下文环境是一个根元素 446 | length:8, 447 | prevObject:init[1] 448 | selector:'li' 449 | } 450 | */} 451 | J[2].style.color = 'blue'; //那么明显J[2]是DOM元素对象操作 452 | 453 | 454 | /* 455 | 然后可以想象在css()方法中遍历这个类数组去更改样式. 那么J和css()中怎么将这个类数组对象联系在一起,应为在两个函数中的变量都是局部变量哎,其实也很简单,因为两个方法都是实例对象的原型方法,那么在同一个实例对象上调用这两个方法的this是同一个啊,所以肯定是通过this对象啦,那么在css()方法中,会是这样处理 456 | 457 | for(var i=0 len=this.length; i', {html: 'this is a div', class:'div'}).appendTo('body'); //添加在body的末尾 471 | $('
    ', {html: 'this is a div', class:'div'}).appendTo('body'); //添加在body的末尾 472 | $('
    ', {html: 'this is a div', class:'div'}).appendTo('body'); //添加在body的末尾 473 | var arrDiv = document.getElementsByTagName('div'); 474 | console.log(arrDiv); //获取的是一个类数组对象 475 | //arrDiv.push(); //arrDiv.push is not a function 476 | $.makeArray(arrDiv).push(); //转化成数组就可以调用了 477 | console.log($.makeArray(arrDiv)); //数组 478 | //但是$()构造函数返回的是this类数组对象,而不是数组 479 | console.log($.makeArray(arrDiv,{length:0})); //类数组对象 480 | ``` 481 | 482 | 483 | 484 | ### 3.4 $().selector 485 | ### 3.5 $().length 486 | ### 3.6 $().toArray() 487 | 488 | - 类数组对象转数组 489 | 490 | >源码 491 | ``` javascript 492 | //[202] 493 | toArray: function() { 494 | return core_slice.call( this ); //Array.prototype.slice.call(类数组对象) 495 | } 496 | ``` 497 | 498 | >内容解析 499 | 500 | `call /apply` 可以显示指定调用所需的`this`值,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法,第一个参数是要调用函数的母对象,即调用上下文,在函数体内通过`this`来获得对它的引用 501 | 502 | ``` javascript 503 | function f() { 504 | alert(this.x); 505 | } 506 | 507 | f(); //undefined 508 | f.call({x:100}); //100,函数中的this指代传入的对象,call可以使函数中的this值指向传入的第一个参数 509 | 510 | //call和apply的区别 511 | function f(x,y) { 512 | alert(this.x + x + y); 513 | } 514 | 515 | f.call({x:1},2,3); //6,传入一个个参数 516 | f.apply({x:1},[3,4]); //8,传入一个数组 517 | ``` 518 | 519 | 520 | ```javascript 521 | var obj = { 522 | 0:'apple', 523 | 1:'huawei', 524 | length:2 525 | }; 526 | console.log(obj); //Object 527 | console.log(Array.prototype.slice.call(obj)); //Array[2] 528 | 529 | //Array.prototype.slice的可能源码 530 | Array.prototype.slice = function(start,end){ 531 | var result = new Array(); 532 | start = start || 0; 533 | end = end || this.length; //this指向调用的对象,当用了call后,能够改变this的指向 534 | for(var i = start; i < end; i++){ 535 | result.push(this[i]); 536 | } 537 | return result; 538 | } 539 | 540 | //jQuery的用法 541 | var $div = $('div'); //由于构造函数返回的是this, this是一个类数组对象 542 | 543 | /*this可能的值 544 | { 545 | 0:div, 546 | 1:div, 547 | ... 548 | 7:div, 549 | context:document, //上下文环境是一个根元素 550 | length:8, 551 | selector:'li' 552 | } 553 | */ 554 | 555 | $div.toArray(); //把$div的this对象传入了toArray的core_slice.call( this );后面懂了,就把this变成了数组 556 | ``` 557 | 558 | 附上`[].slice`源码分析 559 | 560 | ``` javascript 561 | //[].slice(start,end) 562 | //返回一个新数组,该数组是原数组从tart到end(不包含该元素)的元素 563 | //以下是slice的源码,我们可以发现它其实是可以将类数组对象进行转换为数组 564 | //开头说明不光光是普通的数组,类数组对象,NamedNodeMap,NodeList,HTMLCollection,DOM objects 都是可以转化成数组的 565 | // This will work for genuine arrays, array-like objects, 566 | // NamedNodeMap (attributes, entities, notations), 567 | // NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes), 568 | // and will not fail on other DOM objects (as do DOM elements in IE < 9) 569 | Array.prototype.slice = function(begin, end) { 570 | // IE < 9 gets unhappy with an undefined end argument 571 | //如果没有传入end参数,则默认是this.length长度 572 | end = (typeof end !== 'undefined') ? end : this.length; //这里终于知道为什么要用typeof a === 'undefined' 就是考虑到了IE的兼容性问题 573 | // 如果是原生数组对象,则调用原生数组对象的方法 574 | // For native Array objects, we use the native slice function 575 | // 以下方法普遍用来判断传入的参数是否是数组 576 | if (Object.prototype.toString.call(this) === '[object Array]'){ 577 | return _slice.call(this, begin, end); 578 | } 579 | // For array like object we handle it ourselves.、 580 | //如果是类数组对象 581 | var i, cloned = [], 582 | size, len = this.length; 583 | 584 | // 如果start默认没有传入参数则是0 585 | // Handle negative value for "begin" 586 | var start = begin || 0; 587 | //如果start>=0则选择start,否则选择你懂得,防止负数的情况下少于数组的长度 588 | start = (start >= 0) ? start : Math.max(0, len + start); 589 | 590 | //如果传入的end是number,则比较end和len,这种情况防止传入end大于数组本身的长度 591 | //如果不是,则默认处理成数组的长度 592 | // Handle negative value for "end" 593 | var upTo = (typeof end == 'number') ? Math.min(end, len) : len; 594 | //当然end<0 595 | if (end < 0) { 596 | upTo = len + end; 597 | } 598 | // Actual expected size of the slice 599 | size = upTo - start; 600 | if (size > 0) { 601 | cloned = new Array(size); 602 | //字符串的情况 603 | if (this.charAt) { 604 | for (i = 0; i < size; i++) { 605 | cloned[i] = this.charAt(start + i); 606 | } 607 | } else { 608 | //类数组情况 609 | for (i = 0; i < size; i++) { 610 | cloned[i] = this[start + i]; //这里就是将类数组对象转化为数组 611 | } 612 | } 613 | } 614 | return cloned; 615 | }; 616 | ``` 617 | 618 | 619 | ### 3.7 $().get() 620 | 621 | - 不传参数功能是`$().toArray()` 622 | - 传参数的功能是 `$()[num]` 623 | 624 | 625 | >源码 626 | ``` javascript 627 | //[206] 628 | // Get the Nth element in the matched element set OR 629 | // Get the whole matched element set as a clean array 630 | get: function( num ) { 631 | return num == null ? 632 | 633 | // Return a 'clean' array 634 | this.toArray() : 635 | 636 | // Return just the object 637 | ( num < 0 ? this[ this.length + num ] : this[ num ] ); 638 | }, 639 | ``` 640 | 641 | >内容解析 642 | 643 | ``` javascript 644 |
    1
    645 |
    2
    646 |
    3
    ] 647 | 648 | 649 | 650 | 655 | ``` 656 | 657 | ### 3.8 $().pushStack() 658 | 659 | >源码 660 | 661 | ``` javascript 662 | // Take an array of elements and push it onto the stack 663 | // (returning the new matched element set) 664 | // 使用元素数组并把当前选中的元素压入栈 665 | // 返回的是新的被匹配的元素 666 | pushStack: function( elems ) { 667 | 668 | // Build a new jQuery matched element set 669 | // this.constructor()是一个空的Jquery对象 670 | // 合并新的元素到新的this对象 671 | var ret = jQuery.merge( this.constructor(), elems ); 672 | 673 | // Add the old object onto the stack (as a reference) 674 | // 老的对象被保留到一个prevObject属性 675 | ret.prevObject = this; 676 | ret.context = this.context; 677 | 678 | // Return the newly-formed element set 679 | // 返回新的元素的结果 680 | return ret; 681 | }, 682 | ``` 683 | 684 | > 内容解析 685 | 686 | 687 | ``` javascript 688 | $('
    ', {html: 'this is a div', class:'div'}).appendTo('body'); //添加在body的末尾 689 | $('', {html: 'this is a span', class:'span'}).appendTo('body'); //添加在body的末尾 690 | console.log($('div').pushStack( $('span') )); 691 | $('div').pushStack( $('span') ).css('background','red'); //span变红 div没有 692 | //因为在栈中span在div上面 693 | $('div').pushStack( $('span') ).css('background','red').css('background','yellow'); //span变黄 694 | //{ 695 | // 0: span, 696 | // length: 1, 697 | // context: document, 698 | // prevObject: { 699 | // 0: div, 700 | // length: 1, 701 | // selector: "div", 702 | // prevObject: { 703 | // 0: document, 704 | // context: document, 705 | // length: 1, 706 | // } 707 | // } 708 | //} 709 | console.log($('div').pushStack( $('span') ).css('background','red').prevObject); //div 710 | console.log($('div').pushStack( $('span') ).css('background','red').context); //document 711 | $('div').pushStack( $('span') ).css('background','red').prevObject.css('fontSize','100px'); //div的字体变了 712 | //如果仍然想使用栈的下一层div(上一层是span),end方法回溯栈其实就利用了prevObject属性 713 | $('div').pushStack( $('span') ).css('background','red').end().css('background','yellow'); //span红色,div黄色 714 | ``` 715 | 716 | ### 3.9 $().end() 717 | 718 | >源码 719 | 720 | ```javascript 721 | // [271-273] 722 | end: function() { 723 | //主要是对于pushStack的回溯,返回被push之前的jQuery实例对象 724 | return this.prevObject || this.constructor(null); 725 | }, 726 | ``` 727 | 728 | ### 3.10 $().slice() 729 | 730 | >源码 731 | ``` javascript 732 | //[247-250] 733 | slice: function() { 734 | return this.pushStack( core_slice.apply( this, arguments ) ); 735 | }, 736 | ``` 737 | 738 | > 内容解析 739 | 740 | ```javascript 741 | $('
    ', {html: 'this is a div', class:'div'}).appendTo('body'); //添加在body的末尾 742 | $('
    ', {html: 'this is a div', class:'div'}).appendTo('body'); //添加在body的末尾 743 | $('
    ', {html: 'this is a div', class:'div'}).appendTo('body'); //添加在body的末尾 744 | $('
    ', {html: 'this is a div', class:'div'}).appendTo('body'); //添加在body的末尾 745 | $('div').slice(1,3).css('background','red'); //中间两个div背景变红色,注意和数组的方法一样不包括第三个 746 | //其实是在4个div的基础上又入栈了被选中的两个div 747 | //如果利用end回溯 748 | $('div').slice(1,3).css('background','red').end().css('background','red'); //4个div背景色都变成了红色 749 | ``` 750 | 751 | ### 3.11 $().each() 752 | 753 | >源码 754 | 755 | ``` javascript 756 | // [233-238] 757 | // 通过工具each方法,工具方法用于构建库的最底层,实例方法调用工具方法 758 | // 实例方法可以看成更高级别的层次 759 | each: function( callback, args ) { 760 | return jQuery.each( this, callback, args ); //后续分析$.each() 761 | }, 762 | ``` 763 | 764 | 765 | ### 3.12 $().ready() 766 | 767 | >源码 768 | 769 | ```javascript 770 | // [240-245] 771 | ready: function( fn ) { 772 | // 使用Promise的形式等待回调 773 | jQuery.ready.promise().done( fn ); 774 | return this; 775 | }, 776 | ``` 777 | 778 | ### 3.13 $().first()/last() /eq() 779 | 780 | >源码 781 | 782 | ``` javascript 783 | first: function() { 784 | return this.eq( 0 ); 785 | }, 786 | last: function() { 787 | return this.eq( -1 ); 788 | }, 789 | 790 | //[259-263] 791 | eq: function( i ) { 792 | var len = this.length, 793 | j = +i + ( i < 0 ? len : 0 ); 794 | return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); 795 | }, 796 | ``` 797 | 798 | >内容解析 799 | 800 | ``` javascript 801 | $('
    ', {html: 'this is a div', class:'div'}).appendTo('body'); //添加在body的末尾 802 | $('
    ', {html: 'this is a div', class:'div'}).appendTo('body'); //添加在body的末尾 803 | $('
    ', {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 | 565 | 566 | 567 | //index.html 568 | 569 | 570 | 571 | 572 | Jquery2.0.3源码分析 573 | 574 | 575 | 576 | 577 |

    index.html

    578 | 579 |
    1
    580 |
    2
    581 |
    3
    582 | 583 | 584 | 585 | 608 | 609 | 610 | ``` 611 | 612 | >提示: 使用`child_index.html`页面实例化的对象和`index.html`页面实例化的对象是两个不同的执行环境,所以没办法进行检测 613 | 614 | 615 | ### 5.10 $.isPlantObject() 616 | 617 | - 检测对象字面量 618 | 619 | >源码 620 | 621 | ``` javascript 622 | isPlainObject: function( obj ) { 623 | // Not plain objects: 624 | // - Any object or value whose internal [[Class]] property is not "[object Object]" 625 | // - DOM nodes 626 | // - window 627 | //1.如果不是对象,DOM节点和window用$.type方法会返回object 628 | //2.如果是Node节点 629 | //3.如果是window对象 630 | if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { 631 | return false; 632 | } 633 | 634 | // Support: Firefox <20 635 | // The try/catch suppresses exceptions thrown when attempting to access 636 | // the "constructor" property of certain host objects, ie. |window.location| 637 | // https://bugzilla.mozilla.org/show_bug.cgi?id=814622 638 | try { 639 | //4.系统自带的对象,例如window.location,不是node节点,$.type又会返回object 640 | //详见(一)、(二) 641 | //obj.constructor指向对象的构造函数 642 | //obj.constructor.prototype指向构造函数对应的原型对象 643 | if ( obj.constructor && 644 | //obj.constructor.prototype.hasOwnProperty('isPrototypeOf') 645 | //判断obj的原型是不是Object.prototype,而不是Array\Date等 646 | //var arr = []; 647 | //var bool = {}.hasOwnProperty.call( arr.constructor.prototype, "isPrototypeOf" ); 648 | //console.log(bool); //false 649 | //如果是Array类型则return false表明不是对象字面量 650 | !core_hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { 651 | return false; 652 | } 653 | } catch ( e ) { 654 | return false; 655 | } 656 | 657 | // If the function hasn't returned already, we're confident that 658 | // |obj| is a plain object, created by {} or constructed with new Object 659 | return true; 660 | }, 661 | ``` 662 | 663 | >内容解析 664 | 665 | (一)、`{}.hasOwnPrototype` 666 | 667 | - 判断是否是自身的属性 668 | - 判断是否是原型对象的属性 669 | 670 | ``` javascript 671 | function Obj() { 672 | this.name = 'ziyi2'; 673 | this.age = 23; 674 | } 675 | 676 | Obj.prototype.name = 'prototype.ziyi2'; 677 | Obj.prototype.addr = 'zjut'; 678 | 679 | //是否是原型对象的属性和方法 680 | function hasPrototypeProperty(obj,key) { 681 | //1.如果是自己的属性返回false,表明不是原型对象的属性 682 | //2.如果能使用in遍历,1返回true,则是原型对象的属性,使用in可以遍历自己的属性和原型对象的属性 683 | return !obj.hasOwnProperty(key) && (key in obj); 684 | } 685 | 686 | var obj = new Obj(); 687 | 688 | alert(hasPrototypeProperty(obj,'name')); //false 689 | alert(hasPrototypeProperty(obj,'age')); //false 690 | alert(hasPrototypeProperty(obj,'addr')); //true 691 | ``` 692 | 693 | (二)、`isPrototypeOf` 694 | 695 | 创建了自定义的构造函数后,其原型对象的默认只会取得`constructor`属性,其余都是从`Object`继承而来,当调用构造函数创建新实例后,该实例内部将包含一个指向构造函数原型对象的指针(`[[Prototype]]` 内部属性,注意是实例的属性而非构造函数的属性),脚本中没有标准的方式访问`[[Prototype]]`,但在一些浏览器诸如Firefox、Safari、Chrome在每个对象上都支持属性`__proto__`,这个指针连接存在于实例对象与构造函数的原型对象之间,不是实例对象与构造函数之间,调用构造函数创建的实例都有`[[Prototype]]`属性,但是无法访问。唯一的方法是可以通过`isPrototypeOf()`方法来确定实例对象和原型对象之间是否存在这种关系。 696 | 697 | ``` javascript 698 | function Obj() { 699 | this.name = 'ziyi2'; 700 | this.age = 23; 701 | } 702 | 703 | Obj.prototype.name = 'prototype.ziyi2'; 704 | Obj.prototype.addr = 'zjut'; 705 | 706 | function hasPrototypeProperty(obj,key) { 707 | return !obj.hasOwnProperty(key) && (key in obj); 708 | } 709 | 710 | 711 | var obj = new Obj(); 712 | 713 | console.log(Obj.prototype.isPrototypeOf(obj)); //true 714 | console.log(Object.prototype.isPrototypeOf(obj)); //true 715 | 716 | 717 | 718 | //来个原型链 719 | var data = new Date; 720 | console.log(Date.prototype.isPrototypeOf(data)); //true 721 | console.log(Object.prototype.isPrototypeOf(Date.prototype)); //true 722 | console.log(Object.prototype.isPrototypeOf(data)); //true 723 | // data -> Date.prototype 实例对象和构造函数对应的原型对象之间的关系 724 | // Date.prototype -> Object.prototype Date.prototype相对于Object.prototype而言就是实例对象 725 | ``` 726 | 727 | `isPrototypeOf`属性是`Object.prototype`的自有属性,其他对象所持有的该属性都是继承的。 728 | 729 | 730 | ``` javascript 731 | //是否是原型对象的属性和方法 732 | function hasPrototypeProperty(obj,key) { 733 | //1.如果是自己的属性返回false,表明不是原型对象的属性 734 | //2.如果能使用in遍历,1返回true,则是原型对象的属性,使用in可以遍历自己的属性和原型对象的属性 735 | return !obj.hasOwnProperty(key) && (key in obj); 736 | } 737 | 738 | //Obeject.prototype自有的属性isPrototypeOf 739 | console.log(Object.prototype.hasOwnProperty('isPrototypeOf')); //true 740 | //Date的该属性是原型对象Obeject.prototype那里继承过来的 741 | console.log(hasPrototypeProperty(Date,'isPrototypeOf')); //true 742 | ``` 743 | 744 | (三)、`try{} catch{}` 745 | 746 | 需要补上详细信息. 747 | 748 | 749 | ### 5.11 $.isEmptyObject() 750 | 751 | >源码 752 | 753 | ``` javascript 754 | isEmptyObject: function( obj ) { 755 | var name; 756 | //可以遍历原型对象的属性和方法 757 | //for-in只遍历可枚举的属性 758 | //原型对象的系统自带属性可能是不可枚举的,所以虽然可以遍历原型对象的属性和方法 759 | //但是for in遍历不到系统自带的属性和方法,可以用来检测对象是否为空对象 760 | for ( name in obj ) { 761 | return false; 762 | } 763 | return true; 764 | }, 765 | ``` 766 | 767 | ### 5.12 $.error() 768 | 769 | - 抛出异常错误 770 | 771 | >源码 772 | 773 | ``` javascript 774 | //[468] 775 | error: function( msg ) { 776 | throw new Error( msg ); 777 | }, 778 | ``` 779 | 780 | ### 5.13 $.parseHTML() 781 | 782 | - 将字符串转换成DOM数组 783 | 784 | >源码 785 | 786 | ``` javascript 787 | // data: string of html 788 | // context (optional): If specified, the fragment will be created in this context, defaults to document 789 | // keepScripts (optional): If true, will include scripts passed in the html string 790 | // context默认是document,如果被指定,则会在这个指定的context创建文档碎片 791 | // keepScripts如果是true,则会在文档中创建script标签 792 | parseHTML: function( data, context, keepScripts ) { 793 | // 如果是空,或者不是字符串 794 | if ( !data || typeof data !== "string" ) { 795 | return null; 796 | } 797 | // 如果省略了context参数,则第二个参数变成了keepScripts 798 | if ( typeof context === "boolean" ) { 799 | keepScripts = context; 800 | context = false; 801 | } 802 | 803 | // context默认是document对象 804 | context = context || document; 805 | 806 | // 匹配单标签 "
  • " 807 | var parsed = rsingleTag.exec( data ), 808 | scripts = !keepScripts && []; 809 | 810 | // Single tag 811 | // 单标签 812 | if ( parsed ) { 813 | // 单标签当然使用document.createElement方法创建DOM对象\ 814 | // 返回的仍然是数组 815 | return [ context.createElement( parsed[1] ) ]; 816 | } 817 | 818 | 819 | //多标签使用文档碎片的形式创建DOM对象数组 820 | parsed = jQuery.buildFragment( [ data ], context, scripts ); 821 | 822 | // keepScripts = true scripts = false 因此不会移除script标签, 否则移除script 标签 823 | if ( scripts ) { 824 | jQuery( scripts ).remove(); 825 | } 826 | 827 | // 返回的仍然是数组, jQuery.merge可以合并数组 828 | return jQuery.merge( [], parsed.childNodes ); 829 | }, 830 | ``` 831 | 832 | >内容解析 833 | 834 | ``` javascript 835 | //单标签 836 | document.body.appendChild($.parseHTML('
  • 1
  • ')[0]); //将li元素插入body元素 837 | //多标签 838 | document.body.appendChild($.parseHTML('
  • 1
  • 1
  • ')[0]); 839 | document.body.appendChild($.parseHTML('
  • 1
  • 1
  • ')[1]); 840 | ``` 841 | 842 | 843 | 844 | ### 5.14 $.parseJSON() 845 | 846 | >源码 847 | 848 | ``` javascript 849 | parseJSON: JSON.parse, 850 | ``` 851 | 852 | 853 | ### 5.15 $.parseXML() 854 | 855 | >源码 856 | 857 | ``` javascript 858 | // Cross-browser xml parsing 859 | parseXML: function( data ) { 860 | var xml, tmp; 861 | if ( !data || typeof data !== "string" ) { 862 | return null; 863 | } 864 | 865 | // Support: IE9 866 | try { 867 | tmp = new DOMParser(); 868 | xml = tmp.parseFromString( data , "text/xml" ); 869 | } catch ( e ) { 870 | xml = undefined; 871 | } 872 | 873 | if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { 874 | jQuery.error( "Invalid XML: " + data ); 875 | } 876 | return xml; 877 | }, 878 | ``` 879 | 880 | 881 | ### 5.16 $.noop() 882 | 883 | >源码 884 | 885 | ``` javascript 886 | noop: function() {}, 887 | ``` 888 | 889 | 890 | ### 5.17 $.globalEval() 891 | 892 | 893 | >源码 894 | 895 | ``` javascript 896 | // Evaluates a script in a global context 897 | globalEval: function( code ) { 898 | //详见(一) 899 | var script, 900 | indirect = eval; 901 | 902 | code = jQuery.trim( code ); 903 | 904 | if ( code ) { 905 | // If the code includes a valid, prologue position 906 | // strict mode pragma, execute code by injecting a 907 | // script tag into the document. 908 | // 如果是在严格模式下,详见(二) 909 | if ( code.indexOf("use strict") === 1 ) { 910 | script = document.createElement("script"); 911 | script.text = code; 912 | document.head.appendChild( script ).parentNode.removeChild( script ); 913 | } else { 914 | // Otherwise, avoid the DOM node creation, insertion 915 | // and removal by using an indirect global eval 916 | // 非严格模式使用全局eval() 917 | indirect( code ); 918 | } 919 | } 920 | }, 921 | ``` 922 | 923 | >内容解析 924 | 925 | (一)、`eval` 926 | 927 | - 直接调用`eval`,总是在调用它的上下文作用域内执行 928 | - 其他的间接调用则使用全局对象作为其上下文作用域 929 | 930 | ``` javascript 931 | var geval = eval; //使用别名调用eval将是全局eval,这算是间接调用 932 | var x = 'x global'; 933 | var y = 'y global'; 934 | function f(){ 935 | var x = 'x local'; 936 | eval("x += ' changed'"); //直接eval改变了局部变量的值 937 | return x; 938 | } 939 | function g(){ 940 | var y = 'y local'; 941 | geval("y += ' changed'"); //间接调用改变了全局变量的值 942 | return y; 943 | } 944 | console.log(f(),x);//x local changed x global 945 | console.log(g(),y);//y local y global changed 946 | //所以更可能会使用全局eval而不是局部eval 947 | ``` 948 | 949 | (二)、 严格模式 950 | 951 | ``` javascript 952 | function fn(){ 953 | eval('var i = 0'); 954 | console.log(i); //0 955 | } 956 | 957 | function f(){ 958 | "use strict"; 959 | eval('var i = 0'); 960 | console.log(i); //Uncaught ReferenceError: i is not defined(…) 961 | } 962 | 963 | fn(); 964 | f(); 965 | ``` 966 | 967 | 968 | ### 5.18 $.camelCase() 969 | 970 | - 字符串转驼峰 971 | 972 | >源码 973 | 974 | ``` javascript 975 | 976 | //[81] 977 | rmsPrefix = /^-ms-/, 978 | rdashAlpha = /-([\da-z])/gi, 979 | 980 | // Used by jQuery.camelCase as callback to replace() 981 | fcamelCase = function( all, letter ) { 982 | return letter.toUpperCase(); 983 | }, 984 | 985 | //[550] 986 | // Convert dashed to camelCase; used by the css and data modules 987 | // Microsoft forgot to hump their vendor prefix (#9572) 988 | camelCase: function( string ) { 989 | //解析一 990 | return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); 991 | }, 992 | ``` 993 | 994 | 995 | >内容解析 996 | 997 | (一)、`string.replace` 998 | 999 | - 参数一 规定子字符串或要替换的模式的 RegExp 对象 1000 | - 参数二 规定了替换文本或生成替换文本的函数 1001 | 1002 | 1003 | 1004 | ### 5.19 $.nodeName() 1005 | 1006 | 1007 | >源码 1008 | 1009 | ``` javascript 1010 | nodeName: function( elem, name ) { 1011 | return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); 1012 | }, 1013 | ``` 1014 | 1015 | 1016 | >内容解析 1017 | 1018 | ``` javascript 1019 | console.log($.nodeName($('div')[0],'DIV')); //true 1020 | ``` 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | ### 5.20 $.each() 1027 | 1028 | - 遍历 1029 | - 参数一 index 1030 | - 参数二 value 1031 | - 参数三 内部使用 1032 | 1033 | >源码 1034 | 1035 | ``` javascript 1036 | // args is for internal usage only 1037 | each: function( obj, callback, args ) { 1038 | var value, 1039 | i = 0, 1040 | length = obj.length, 1041 | //判断是否是类数组对象 1042 | isArray = isArraylike( obj ); 1043 | 1044 | //如果第三参数存在,内部使用 1045 | if ( args ) { 1046 | if ( isArray ) { 1047 | for ( ; i < length; i++ ) { 1048 | value = callback.apply( obj[ i ], args ); 1049 | if ( value === false ) { 1050 | break; 1051 | } 1052 | } 1053 | } else { 1054 | for ( i in obj ) { 1055 | value = callback.apply( obj[ i ], args ); 1056 | 1057 | if ( value === false ) { 1058 | break; 1059 | } 1060 | } 1061 | } 1062 | 1063 | // A special, fast, case for the most common use of each 1064 | // 外部使用 1065 | } else { 1066 | //数组或类数组对象 1067 | if ( isArray ) { 1068 | for ( ; i < length; i++ ) { 1069 | //call的第一个参数是this指向,后面的参数是callback函数的参数 1070 | //$.each(arr,function(index,value){}) 1071 | //callback -> functon(index,value){} 1072 | //并且callback传入了两个参数i obj[i] 1073 | //i -> index obj[i] -> value 1074 | //this -> obj[i] 详见(二) 1075 | value = callback.call( obj[ i ], i, obj[ i ] ); 1076 | //如果有return false 则终止遍历 1077 | //详见(三) 1078 | if ( value === false ) { 1079 | break; 1080 | } 1081 | } 1082 | } else { 1083 | //对象 1084 | for ( i in obj ) { 1085 | value = callback.call( obj[ i ], i, obj[ i ] ); 1086 | 1087 | if ( value === false ) { 1088 | break; 1089 | } 1090 | } 1091 | } 1092 | } 1093 | 1094 | return obj; 1095 | }, 1096 | ``` 1097 | 1098 | 1099 | >内容解析 1100 | 1101 | (一)、参数解析 1102 | 1103 | ``` javascript 1104 | var arr = [1,2,3]; 1105 | 1106 | $.each(arr,function(index,value) { 1107 | console.log(index); //0 1 2 1108 | console.log(value); //1 2 3 1109 | }); 1110 | ``` 1111 | 1112 | (二)、`this`指向 1113 | 1114 | 1115 | ``` javascript 1116 | var arr = [1,2,3]; 1117 | 1118 | $.each(arr,function(index,value) { 1119 | console.log(this.valueOf()); //1 2 3 1120 | }); 1121 | ``` 1122 | 1123 | 1124 | (三)、终止遍历 1125 | 1126 | ``` javascript 1127 | var arr = [1,2,3]; 1128 | 1129 | $.each(arr,function(index,value) { 1130 | console.log(index); //0 1131 | return false; //终止遍历 1132 | }); 1133 | ``` 1134 | 1135 | 1136 | ### 5.21 $.trim() 1137 | 1138 | >源码 1139 | 1140 | ``` javascript 1141 | trim: function( text ) { 1142 | return text == null ? "" : core_trim.call( text ); 1143 | }, 1144 | ``` 1145 | 1146 | ### 5.22 $.makeArray() 1147 | - 参数一 1148 | - 参数二 内部使用 1149 | 1150 | >源码 1151 | 1152 | ``` javascript 1153 | // results is for internal usage only 1154 | makeArray: function( arr, results ) { 1155 | //第二参数可能不存在,那么就是空数组 1156 | var ret = results || []; 1157 | 1158 | //第一参数如果不存在返回空数组 1159 | if ( arr != null ) { 1160 | //Object(arr) 1161 | //字符串形式 '123' -> ['123'] 1162 | //详见(一) 1163 | //需要注意数组是走这里 1164 | if ( isArraylike( Object(arr) ) ) { 1165 | jQuery.merge( ret, 1166 | typeof arr === "string" ? 1167 | [ arr ] : arr 1168 | ); 1169 | //数字形式,详见(二) 1170 | } else { 1171 | core_push.call( ret, arr ); 1172 | } 1173 | } 1174 | 1175 | return ret; 1176 | }, 1177 | ``` 1178 | 1179 | >内容解析 1180 | 1181 | (一) Object 1182 | 1183 | - 转换成包装对象 1184 | 1185 | ``` javascript 1186 | var str = '123' 1187 | console.log(Object(str)); 1188 | //String {0: "1", 1: "2", 2: "3", length: 3, [[PrimitiveValue]]: 1189 | console.log($.makeArray(str)); //['123'] 1190 | ``` 1191 | 1192 | (二) `[].push()` 1193 | 1194 | ``` javascript 1195 | var num = 123; 1196 | console.log(Object(num)); //Number(123); 1197 | 1198 | var arr = []; 1199 | 1200 | arr.push(num); //传入单个num 1201 | arr.push([1,2,3]); //传入数组 1202 | 1203 | console.log(arr); //[123,[1,2,3]] 1204 | 1205 | //注意apply和call的用法区别 1206 | [].push.call(arr,4,5,6); 1207 | console.log(arr); //[123,[1,2,3],4,5,6] 1208 | [].push.apply(arr,[7,8,9]); 1209 | console.log(arr); //[123,[1,2,3],4,5,6,7,8,9] 1210 | 1211 | 1212 | //走的不是源码的else 1213 | //如果是else 1214 | //变成了[[1,2,3],[4,5,6]] 1215 | console.log($.makeArray([1,2,3],[4,5,6])); //1,2,3,4,5,6 1216 | ``` 1217 | 1218 | ### 5.23 $.inArray() 1219 | 1220 | - 数组版的indexOf() 1221 | 1222 | >源码 1223 | 1224 | ``` javascript 1225 | inArray: function( elem, arr, i ) { 1226 | //i是indexOf的第二个参数,搜索的起始位置 1227 | //详见(一) 1228 | return arr == null ? -1 : core_indexOf.call( arr, elem, i ); 1229 | }, 1230 | ``` 1231 | 1232 | >内容解析 1233 | 1234 | (一) `indexOf()` 1235 | 1236 | ``` javascript 1237 | //字符串索引 1238 | console.log('12345'.indexOf('3',4)); //-1 1239 | console.log('12345'.indexOf('3',1)); //2 1240 | console.log('12345'.indexOf('3',2)); //2 1241 | console.log('12345'.indexOf('3')); //2 1242 | 1243 | 1244 | //数组索引 1245 | console.log([].indexOf.call([1,2,3,4,5],3)); //2 1246 | 1247 | //jQuery数组索引 1248 | console.log($.inArray(2,[1,2,3,4,5])); //1 1249 | ``` 1250 | 1251 | 1252 | ### 5.24 $.merge() 1253 | - 合并数组 1254 | - 对外 转数组 1255 | - 对内 转json 1256 | 1257 | - 针对情况`[] {}`, `{}`可能有`length`也可能没有`length` 1258 | 1259 | ``` javascript 1260 | merge: function( first, second ) { 1261 | var l = second.length, 1262 | i = first.length, 1263 | j = 0; 1264 | 1265 | // $.merge(['a','b'],['a','b']) 1266 | // second不是数组,没有length属性 1267 | if ( typeof l === "number" ) { 1268 | for ( ; j < l; j++ ) { 1269 | first[ i++ ] = second[ j ]; 1270 | } 1271 | // $.merge(['a','b'],{0:'a',1:'b'}) 1272 | } else { 1273 | while ( second[j] !== undefined ) { 1274 | first[ i++ ] = second[ j++ ]; 1275 | } 1276 | } 1277 | 1278 | first.length = i; 1279 | 1280 | return first; 1281 | }, 1282 | ``` 1283 | 1284 | 1285 | 1286 | 1287 | ### 5.25 $.grep() 1288 | 1289 | - 过滤数组,返回新数组 1290 | - 第三个参数 布尔值 1291 | 1292 | 1293 | >源码 1294 | 1295 | ``` javascript 1296 | grep: function( elems, callback, inv ) { 1297 | var retVal, 1298 | ret = [], 1299 | i = 0, 1300 | length = elems.length; 1301 | //!! 转换为布尔值 1302 | inv = !!inv; 1303 | 1304 | // Go through the array, only saving the items 1305 | // that pass the validator function 1306 | // 只有数组才会遍历(类数组) 1307 | for ( ; i < length; i++ ) { 1308 | retVal = !!callback( elems[ i ], i ); 1309 | if ( inv !== retVal ) { 1310 | ret.push( elems[ i ] ); 1311 | } 1312 | } 1313 | 1314 | return ret; 1315 | }, 1316 | ``` 1317 | 1318 | >内容解析 1319 | 1320 | 1321 | ``` javascript 1322 | var arr = [1,2,3,4]; 1323 | 1324 | var f = function(value,index) { 1325 | return 1 //1类似于true 1326 | }; 1327 | 1328 | var f1 = function(value,index) { 1329 | return value > 2 1330 | }; 1331 | 1332 | console.log($.grep(arr,f)); //[1,2,3,4] 1333 | console.log($.grep(arr,f1)); //[3,4] 1334 | console.log($.grep(arr,f1,true)); //[1,2] 1335 | ``` 1336 | 1337 | 1338 | 1339 | 1340 | ### 5.26 $.map() 1341 | 1342 | - 改变数组`value`,返回新数组 1343 | 1344 | >源码 1345 | ``` javascript 1346 | // arg is for internal usage only 1347 | map: function( elems, callback, arg ) { 1348 | var value, 1349 | i = 0, 1350 | length = elems.length, 1351 | //是否是数组和类数组 1352 | isArray = isArraylike( elems ), 1353 | ret = []; 1354 | 1355 | // Go through the array, translating each of the items to their 1356 | // 数组格式 1357 | if ( isArray ) { 1358 | for ( ; i < length; i++ ) { 1359 | value = callback( elems[ i ], i, arg ); 1360 | 1361 | if ( value != null ) { 1362 | // 注意是ret.length 会自动递增的 1363 | ret[ ret.length ] = value; 1364 | } 1365 | } 1366 | 1367 | // Go through every key on the object, 1368 | // Json格式 1369 | } else { 1370 | for ( i in elems ) { 1371 | value = callback( elems[ i ], i, arg ); 1372 | 1373 | if ( value != null ) { 1374 | ret[ ret.length ] = value; 1375 | } 1376 | } 1377 | } 1378 | 1379 | // Flatten any nested arrays 1380 | // 返回的是单数组,而不是复合数组 1381 | return core_concat.apply( [], ret ); 1382 | }, 1383 | ``` 1384 | 1385 | 1386 | >内容解析 1387 | 1388 | ``` javascript 1389 | var arr = [1,2,3]; 1390 | 1391 | var newArr = $.map(arr,function(value,index) { 1392 | return value + 1; 1393 | }); 1394 | 1395 | console.log(newArr); //[2,3,4] 1396 | ``` 1397 | 1398 | 1399 | ### 5.27 $.guid 1400 | 1401 | - 取消绑定事件有关系 1402 | - 唯一标识符,用于标识事件函数 1403 | 1404 | 1405 | // A global GUID counter for objects 1406 | guid: 1, 1407 | 1408 | 1409 | ### 5.28 $.proxy() 1410 | 1411 | - 类似于`call`和`apply`,改变`this`指向 1412 | 1413 | > 源码 1414 | 1415 | ``` javascript 1416 | // Bind a function to a context, optionally partially applying any 1417 | // arguments. 1418 | proxy: function( fn, context ) { 1419 | var tmp, args, proxy; 1420 | 1421 | // obj = {fn : function(){}} 1422 | // $.proxy(obj,'fn') 情况 1423 | // 详见(一) 1424 | if ( typeof context === "string" ) { 1425 | tmp = fn[ context ]; 1426 | context = fn; 1427 | fn = tmp; 1428 | } 1429 | 1430 | // Quick check to determine if target is callable, in the spec 1431 | // this throws a TypeError, but we will just return undefined. 1432 | // 如果不是函数 1433 | if ( !jQuery.isFunction( fn ) ) { 1434 | return undefined; 1435 | } 1436 | 1437 | // Simulated bind 1438 | // $.proxy()参数可以追加, 去除第一第二参数fn和context 1439 | // 详见(二) 1440 | args = core_slice.call( arguments, 2 ); 1441 | // $.proxy(arg1)(arg2) 这个扩展方法返回的是一个可执行的函数 1442 | // apply改变this指向 1443 | // 如果没有指定context 使用默认this 1444 | // apply第二个参数是[],call后面可以跟n个参数 1445 | // 将arguments类数组对象使用slice.call转化为数组 1446 | // 将arg1和arg2合并,注意arg1和arg2的归属函数 1447 | // arguments是arg2 1448 | // 详见(三) 1449 | proxy = function() { 1450 | return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); 1451 | }; 1452 | 1453 | // Set the guid of unique handler to the same of original handler, so it can be removed 1454 | // 设置唯一事件标识符 1455 | // 如果要取消事件就能找到 1456 | // 详见(四) 1457 | proxy.guid = fn.guid = fn.guid || jQuery.guid++; 1458 | 1459 | // 返回的是函数 1460 | return proxy; 1461 | }, 1462 | ``` 1463 | 1464 | 1465 | >内容解析 1466 | 1467 | (一) `$.proxy(obj, 'fn')` 1468 | 1469 | ``` javascript 1470 | var obj = { 1471 | show: function() { 1472 | console.log(this); 1473 | } 1474 | }; 1475 | 1476 | $(document).click(obj.show); //绑定事件函数中的this默认指向绑定对象$(document) 1477 | $(document).click($.proxy(obj,'show')); //改变了绑定事件函数中的this指向,指向了obj,需要注意的是$.proxy没有执行,点击事件之后才会执行 1478 | ``` 1479 | 1480 | (二) 转数组 1481 | 1482 | ``` javascript 1483 | var json = { 1484 | 0: 0, 1485 | 1: 1, 1486 | 2: 2, 1487 | length:3 1488 | } 1489 | 1490 | //slice默认不传参数就是起始开始,末尾结束 1491 | console.log(Array.isArray([].slice.call(json))) //true 1492 | console.log([].slice.call(json)); //[0,1,2] 1493 | ``` 1494 | 1495 | (三) `$.proxy()`参数详见 1496 | 1497 | 1498 | ``` javascript 1499 | var obj = { 1500 | show: function(a,b) { 1501 | console.log(a); 1502 | console.log(b); 1503 | console.log(this); 1504 | } 1505 | }; 1506 | 1507 | 1508 | $.proxy(obj.show,obj,1,2)(); //1,2,obj 1509 | $.proxy(obj.show,obj,1)(2); 1510 | $.proxy(obj.show,obj)(1,2); //全都是一样的 1511 | ``` 1512 | 1513 | 1514 | (四) 事件绑定 1515 | 1516 | 1517 | ``` javascript 1518 | function show() { 1519 | console.log(this); 1520 | } 1521 | 1522 | show(); //Window 1523 | 1524 | $(document).click( 1525 | show //绑定事件,Document 1526 | ); 1527 | 1528 | $(document).off() //取消绑定 1529 | ``` 1530 | 1531 | 需要注意的是一般情况下, 想要取消绑定事件,需要调用同一个绑定事件的引用,例如以下取消绑定事件是会失败的,所以就有了唯一标识符`guid`,因为使用`$.proxy()`很容易改变绑定的事件函数,不使用唯一标识符的话,就不能取消绑定了 1532 | 1533 | ``` javascript 1534 | //绑定事件 1535 | document.addEventListener('click',function(){ 1536 | alert(1); 1537 | }); 1538 | 1539 | //取消绑定,并不能取消,因为事件函数并不是同一个引用对象 1540 | document.removeEventListener('click',function(){ 1541 | alert(1); 1542 | }); 1543 | 1544 | 1545 | //正确的形式 1546 | 1547 | //绑定事件 1548 | document.addEventListener('click',show); 1549 | 1550 | 1551 | //取消绑定,因为事件函数指向了同一个事件函数的引用 1552 | document.removeEventListener('click',show); 1553 | 1554 | function show() { 1555 | alert(1); 1556 | } 1557 | 1558 | 1559 | ``` 1560 | 1561 | 1562 | 1563 | ### 5.29 $.access() 1564 | 1565 | - 多功能函数的操作 底层工具方法 1566 | - 内部使用 1567 | 1568 | >源码 1569 | 1570 | ``` javascript 1571 | // Multifunctional method to get and set values of a collection 1572 | // The value/s can optionally be executed if it's a function 1573 | // key -> witdh 1574 | // value -> 200px 1575 | // chainable -> 获取还是设置 1576 | access: function( elems, fn, key, value, chainable, emptyGet, raw ) { 1577 | var i = 0, 1578 | length = elems.length, 1579 | // 有值或者没值 1580 | bulk = key == null; 1581 | 1582 | // Sets many values 1583 | // 设置多组值 1584 | // $('#div1').css({width:'200px',background:'yellow'}) 1585 | // key是Object 1586 | if ( jQuery.type( key ) === "object" ) { 1587 | chainable = true; 1588 | for ( i in key ) { 1589 | //递归调用 1590 | jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); 1591 | } 1592 | 1593 | // Sets one value 1594 | // 如果是一组值 1595 | // $('#div1').css('width','200px') 1596 | } else if ( value !== undefined ) { 1597 | chainable = true; 1598 | 1599 | // value是否是函数 1600 | if ( !jQuery.isFunction( value ) ) { 1601 | raw = true; 1602 | } 1603 | 1604 | // 如果没有Key值 1605 | if ( bulk ) { 1606 | // Bulk operations run against the entire set 1607 | // 如果value是字符串 1608 | if ( raw ) { 1609 | fn.call( elems, value ); 1610 | fn = null; 1611 | 1612 | // ...except when executing function values 1613 | } else { 1614 | // 如果是函数,则套上一个fn,并不是立即执行的 1615 | bulk = fn; 1616 | fn = function( elem, key, value ) { 1617 | return bulk.call( jQuery( elem ), value ); 1618 | }; 1619 | } 1620 | } 1621 | 1622 | 1623 | // 存在key值得情况下 1624 | if ( fn ) { 1625 | for ( ; i < length; i++ ) { 1626 | fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); 1627 | } 1628 | } 1629 | } 1630 | 1631 | // 判断是设置还是获取 1632 | return chainable ? 1633 | elems : 1634 | 1635 | // Gets 1636 | // 获取 1637 | bulk ? 1638 | fn.call( elems ) : 1639 | length ? fn( elems[0], key ) : emptyGet; 1640 | }, 1641 | ``` 1642 | 1643 | 1644 | >内容解析 1645 | 1646 | ``` javascript 1647 | //$().css() \ $().val() \ $().attr()等方法都调用了$.access()工具方法 1648 | //$.access() 多功能值操作(内部) 1649 | 1650 | //获取样式 一个参数 1651 | console.log($('#div1').css('width')); //100px 1652 | 1653 | //设置样式 两个参数 1654 | $('#div1').css('width','200px') 1655 | 1656 | //设置样式 一个对象参数 1657 | $('#div1').css({width:'200px',background:'yellow'}) 1658 | ``` 1659 | 1660 | 1661 | 1662 | ### 5.30 $.now() 1663 | 1664 | - 获取时间 1665 | 1666 | 1667 | ``` javascript 1668 | //和(new Date()).getTime()功能类似 1669 | //ECMAScript 5方法 1670 | now: Date.now, 1671 | ``` 1672 | 1673 | ### 5.31 $.swap() 1674 | 1675 | - css属性交换 1676 | 1677 | ``` javascript 1678 | // A method for quickly swapping in/out CSS properties to get correct calculations. 1679 | // Note: this method belongs to the css module but it's needed here for the support module. 1680 | // If support gets modularized, this method should be moved back to the css module. 1681 | swap: function( elem, options, callback, args ) { 1682 | var ret, name, 1683 | old = {}; 1684 | 1685 | // Remember the old values, and insert the new ones 1686 | // options当然是要设置的属性 1687 | for ( name in options ) { 1688 | // 把旧的属性先保存下来 1689 | old[ name ] = elem.style[ name ]; 1690 | // 设置属性 1691 | elem.style[ name ] = options[ name ]; 1692 | } 1693 | 1694 | // 这里是获取元素的某些参数 1695 | ret = callback.apply( elem, args || [] ); 1696 | 1697 | // Revert the old values 1698 | // 还原css属性 1699 | for ( name in options ) { 1700 | elem.style[ name ] = old[ name ]; 1701 | } 1702 | 1703 | return ret; 1704 | } 1705 | ``` 1706 | 1707 | 1708 | >内容解析 1709 | 1710 | - 交换样式有时候就如例子这么有用 1711 | 1712 | ``` javascript 1713 | var $div = $('#div1') 1714 | , divDom = $div.get(0); 1715 | 1716 | console.log(divDom.offsetWidth); //100 1717 | 1718 | var oldStyle = divDom.style.cssText; //保留老的样式 1719 | 1720 | divDom.style.display = 'none'; 1721 | console.log(divDom.offsetWidth); //0 当display: none时不能获取 1722 | 1723 | 1724 | //让内容脱离正常流,绝对定位,不可见,此时看起来页面的样式没有变 1725 | divDom.style.display = 'block'; 1726 | divDom.style.visibility = 'hidden'; 1727 | divDom.style.position = 'absolute'; 1728 | console.log(divDom.offsetWidth); //100 此时可以获取宽度了 1729 | 1730 | //重新改回原来的样式 1731 | divDom.style.cssText = oldStyle; 1732 | ``` 1733 | 1734 | 1735 | ## 5-6.私有方法`isArraylike` 1736 | 1737 | 需要注意这是一个私有方法,不具备成立单独的一大节,所以此节命名为`5-6`,在整个自执行匿名函数中都可以调用,对外不可见. 1738 | 1739 | >源码 1740 | 1741 | ``` javascript 1742 | (function(window,undefined) { 1743 | 1744 | //[849] 1745 | function isArraylike( obj ) { 1746 | var length = obj.length, 1747 | type = jQuery.type( obj ); 1748 | 1749 | //如果是Window对象 1750 | if ( jQuery.isWindow( obj ) ) { 1751 | return false; 1752 | } 1753 | 1754 | //如果是Node节点集合,类数组形式 1755 | if ( obj.nodeType === 1 && length ) { 1756 | return true; 1757 | } 1758 | 1759 | //需要注意第一个|| 1760 | //obj不是函数且length=0 也算Array 1761 | //obj不是函数length!=0且length是num且length>0且length-1是obj的key 1762 | //需要注意如果去掉length = 0 的情况那么后面的就不好判断了,因为length-1 可能是-1了 1763 | return type === "array" || type !== "function" && 1764 | ( length === 0 || 1765 | typeof length === "number" && length > 0 && ( length - 1 ) in obj ); 1766 | } 1767 | 1768 | })(window); 1769 | ``` 1770 | 1771 | 内容解析: 1772 | 1773 | ``` javascript 1774 | isArraylike([]); 1775 | isArraylike({length:0}); //true 1776 | isArraylike({a:1,length:1}); //false, 0 in obj不存在 1777 | isArraylike({1:'a',length:1}); //false, 1 in obj也不存在 1778 | isArraylike({0:'a',length:1}) //true 1779 | //DOM节点也是可以的 1780 | ``` 1781 | -------------------------------------------------------------------------------- /延迟对象.md: -------------------------------------------------------------------------------- 1 | ## 8. 延迟对象 2 | 3 | 和**5. 工具方法**类似,都是在`JQuery`对象上添加新的属性方法 4 | 5 | ``` javascript 6 | jQuery.extend({ 7 | Deferred: function(){}, # 延迟对象 8 | when:function(){} # 延迟对象辅助方法 9 | }) 10 | ``` 11 | 12 | 13 | 14 | ## 8.1 `$.Deffered()` 15 | 16 | 17 | 18 | >源码 19 | 20 | ``` javascript 21 | //[3043] 22 | jQuery.extend({ 23 | 24 | Deferred: function( func ) { 25 | var tuples = [ 26 | // action, add listener, listener list, final state 27 | [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], 28 | [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], 29 | [ "notify", "progress", jQuery.Callbacks("memory") ] 30 | ], 31 | 32 | //详见(三) 33 | state = "pending", 34 | promise = { 35 | state: function() { 36 | return state; 37 | }, 38 | always: function() { 39 | deferred.done( arguments ).fail( arguments ); 40 | return this; 41 | }, 42 | then: function( /* fnDone, fnFail, fnProgress */ ) { 43 | // then(function(){},function(){},function(){}) 44 | // 所以arguments是的属性是三个函数 45 | // 利用fns保存arguments参数 46 | var fns = arguments; 47 | // return jQuery.Deffered(fn).promise() 48 | // 根据if ( func ) {func.call( deferred, deferred )} 49 | // 因此newDefer就是deffered对象 50 | // 且this指向了deffered对象 51 | // 并立马执行了func 52 | return jQuery.Deferred(function( newDefer ) { 53 | jQuery.each( tuples, function( i, tuple ) { 54 | // action : resolve reject notify 55 | var action = tuple[ 0 ], 56 | // 获取then()中对应三种状态的函数 57 | fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; 58 | // deferred[ done | fail | progress ] for forwarding actions to newDefer 59 | // deffered.done(fn) deffered.fail(fn) deffered.progress(fn) 60 | deferred[ tuple[1] ](function() { 61 | // 当resolve/reject/notify执行的时候 62 | // done/fail/progress就会触发,因此就可以执行then中对应的函数 63 | var returned = fn && fn.apply( this, arguments ); 64 | // 如果then(function(){return}) 65 | // 匿名函数中有返回值 66 | // 如果返回值是deffered对象 67 | // 详见(五) 68 | if ( returned && jQuery.isFunction( returned.promise ) ) { 69 | returned.promise() 70 | .done( newDefer.resolve ) 71 | .fail( newDefer.reject ) 72 | .progress( newDefer.notify ); 73 | } else { 74 | //详见(五) 75 | //如果返回值不是deffered对象 76 | //直接fireWith 可以触发done函数 77 | //需要注意的是newDefer和返回值dfd是怎么建立关系的,就是通过闭包的形式,将之前保留的deffered对象再次传入$.Deffered(fun)的fun中传入 78 | newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); 79 | } 80 | }); 81 | }); 82 | fns = null; 83 | }).promise(); 84 | }, 85 | 86 | //详见(二) 87 | //有参数的时候例如后面传入deffered 88 | //则将promise对象扩展到deffered对象 89 | //如果没有参数传入,则就返回promise对象本身 90 | //例如$.Deffered().promise() 91 | //返回的是promise对象而不是deffered对象 92 | promise: function( obj ) { 93 | return obj != null ? jQuery.extend( obj, promise ) : promise; 94 | } 95 | }, 96 | 97 | //注意闭包形式,因为外部调用$.Deffered()会一直保持着 引用 98 | //所以这个对象暂时是不会释放的 99 | //这个对象有很多属性是函数 100 | //相当于返回了这些函数,因此返回函数内部的嵌套函数就属于闭包形式 101 | deferred = {}; 102 | 103 | // Keep pipe for back-compat 104 | // 两个函数每种形式上是通用的 105 | promise.pipe = promise.then; 106 | 107 | // Add list-specific methods 108 | // 其实这里就相当于添加add和fire函数 109 | // 需要注意的是和tuples数组是对应起来的 110 | // 例如done对应 add 111 | // 那么resolve就对应 fireWith 112 | jQuery.each( tuples, function( i, tuple ) { 113 | // list 就是$.Callback() 114 | // 每一种状态就有一个Callback 115 | var list = tuple[ 2 ], 116 | stateString = tuple[ 3 ]; 117 | 118 | // promise[ done | fail | progress ] = list.add 119 | // 因为memory所以直接add就fire了? 120 | promise[ tuple[1] ] = list.add; 121 | 122 | // Handle state 123 | // notify是没有stateString 124 | // 只有resolve和reject才会执行 125 | if ( stateString ) { 126 | // 因为memory这里先添加add? 127 | // 这里状态是不能被改变的 128 | // 在执行任何一种状态的时候另外的状态都会被锁定 129 | list.add(function() { 130 | // state = [ resolved | rejected ] 131 | // 详见(三) 132 | state = stateString; 133 | 134 | // [ reject_list | resolve_list ].disable; progress_list.lock 135 | }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); 136 | } 137 | 138 | // deferred[ resolve | reject | notify ] 139 | // 需要注意这里后执行 140 | // 这里只有在外部调用 resolve reject等函数时才会执行 141 | // 后面的先执行所以deferred[ tuple[0] + "With" ]存在 142 | deferred[ tuple[0] ] = function() { 143 | // resoveWith rejectWith notifyWith 144 | deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); 145 | return this; 146 | }; 147 | // 这里先执行 148 | deferred[ tuple[0] + "With" ] = list.fireWith; 149 | }); 150 | 151 | // Make the deferred a promise 152 | // 详见(二) 153 | // 使deffered对象继承promise对象 154 | promise.promise( deferred ); 155 | 156 | // Call given func if any 157 | // 这一这个可以在外部使用,内部详见then方法 158 | if ( func ) { 159 | func.call( deferred, deferred ); 160 | } 161 | 162 | // All done! 163 | // 调用$.Defferd()返回的是deffered对象 164 | // 闭包形式 165 | return deferred; 166 | } 167 | }); 168 | ``` 169 | 170 | >内容解析 171 | 172 | (一) 案例说明 173 | 174 | 延迟对象其实是对回调对象的再次封装. 175 | 176 | 177 | ``` javascript 178 | var $callback = $.Callbacks('memory once'); 179 | var $deferred = $.Deferred(); 180 | 181 | function fn1() { 182 | console.log('callback fn1'); 183 | } 184 | 185 | function fn2() { 186 | console.log('deferred fn2'); 187 | } 188 | 189 | setTimeout(function() { 190 | console.log('defer'); //defer 191 | $callback.fire(); //callback fn1 192 | $deferred.resolve(); //deferred fn2 193 | },1000); 194 | 195 | $callback.add(fn1); 196 | $deferred.done(fn2); 197 | 198 | 199 | //add -> done 200 | //fire -> resolve 201 | //Callbacks -> Deferred 202 | ``` 203 | 204 | 延迟对象的`resolve`和`reject`对应`once`和`memory`参数 205 | 206 | ``` javascript 207 | 208 | //[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], 209 | //[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], 210 | //[ "notify", "progress", jQuery.Callbacks("memory") ] 211 | 212 | var $callback = $.Callbacks('memory once'); 213 | var $deferred = $.Deferred(); 214 | 215 | function fn1() { 216 | console.log('callback fn1'); 217 | } 218 | 219 | function fn2() { 220 | console.log('deferred fn2'); 221 | } 222 | 223 | 224 | setInterval(function() { 225 | console.log('defer'); //defer N次 226 | $callback.fire(); //callback fn1 只有一次 因为参数once 227 | $deferred.resolve(); //deferred fn2 只有一次 因为参数once 228 | },1000); 229 | 230 | $callback.add(fn1); 231 | $deferred.done(fn2); 232 | ``` 233 | 234 | 延迟对象的`notify`没有`once`参数 235 | 236 | ``` javascript 237 | var $callback = $.Callbacks('memory once'); 238 | var $deferred = $.Deferred(); 239 | 240 | function fn1() { 241 | console.log('callback fn1'); 242 | } 243 | 244 | function fn2() { 245 | console.log('deferred fn2'); 246 | } 247 | 248 | 249 | setInterval(function() { 250 | console.log('defer'); //defer N次 251 | $callback.fire(); //callback fn1 只有一次 因为参数once 252 | $deferred.notify(); //deferred fn2 N次 因为没有参数once 253 | },1000); 254 | 255 | $callback.add(fn1); 256 | $deferred.progress(fn2); 257 | ``` 258 | 259 | 延迟对象的`notify`只对应`memory`参数 260 | 261 | ``` javascript 262 | var $callback = $.Callbacks('memory once'); 263 | var $deferred = $.Deferred(); 264 | 265 | function fn1() { 266 | console.log('callback fn1'); 267 | } 268 | 269 | function fn2() { 270 | console.log('deferred fn2'); 271 | } 272 | 273 | $callback.add(fn1); 274 | $deferred.progress(fn2); 275 | 276 | $deferred.notify(); //deferred fn2 277 | $deferred.progress(fn2); //deferred fn2 因为memory 直接fire 278 | $deferred.progress(fn2); //deferred fn2 因为memory 直接fire 279 | ``` 280 | 281 | 282 | (二) `promise`和`deffered`对象的区别 283 | 284 | - `promise`(使用`promise`对象不可以修改外部状态) 285 | - `state` 286 | - `always` 287 | - `promise` 288 | - `pipe` 289 | - `then` 290 | - `done` 291 | - `fail` 292 | - `progress` 293 | 294 | - `deffered`(多了三个状态,使用`deffered`可以修改状态) 295 | - `resolve` 296 | - `resolveWith` 297 | - `reject` 298 | - `rejectWith` 299 | - `notify` 300 | - `notifyWith` 301 | - `state`(这之后都是从`promise`对象继承而来) 302 | - `always` 303 | - `promise` 304 | - `pipe` 305 | - `then` 306 | - `done` 307 | - `fail` 308 | - `progress` 309 | 310 | 使用`deffered`对象可以在外部修改内部状态 311 | 312 | ``` javascript 313 | function fn() { 314 | var dfd = $.Deferred(); 315 | 316 | setTimeout(function() { 317 | dfd.resolve(); //因为先reject所以状态被改变 318 | },1000); 319 | 320 | return dfd; 321 | } 322 | 323 | var dfd = fn(); 324 | 325 | dfd.done(function() { 326 | console.log('success'); 327 | }).fail(function() { 328 | console.log('fail'); //fail 329 | }); 330 | 331 | dfd.reject(); //失败,说明在外面可以改变状态,因为用的是deffered对象 332 | ``` 333 | 334 | 使用`promise`对象不可以在外部修改内部状态 335 | 336 | ``` javascript 337 | function fn() { 338 | var dfd = $.Deferred(); 339 | 340 | setTimeout(function() { 341 | dfd.resolve(); //内部resolve状态不能被外部的reject修改 342 | },1000); 343 | 344 | return dfd.promise(); 345 | } 346 | 347 | var dfd = fn(); 348 | 349 | dfd.done(function() { 350 | console.log('success'); //success 351 | }).fail(function() { 352 | console.log('fail'); 353 | }); 354 | 355 | dfd.reject(); //Uncaught TypeError: dfd.reject is not a function, 因为promise对象没有reject属性 356 | ``` 357 | 358 | (三) `state`状态 359 | 360 | ``` javascript 361 | function fn() { 362 | var dfd = $.Deferred(); 363 | 364 | console.log(dfd.state()); //pending 365 | 366 | setTimeout(function() { 367 | dfd.resolve(); 368 | console.log(dfd.state()); //resolved 369 | },1000); 370 | 371 | return dfd.promise(); 372 | } 373 | 374 | var dfd = fn(); 375 | 376 | dfd.done(function() { 377 | console.log('success'); //success 378 | }).fail(function() { 379 | console.log('fail'); 380 | }); 381 | ``` 382 | 383 | (四) `always` 384 | 385 | ``` javascript 386 | function fn() { 387 | var dfd = $.Deferred(); 388 | 389 | //dfd.resolve(); 390 | dfd.reject(); //不管是什么状态,always都会触发 391 | 392 | return dfd.promise(); 393 | } 394 | 395 | var dfd = fn(); 396 | 397 | dfd.always(function() { 398 | console.log('111'); 399 | }) 400 | ``` 401 | 402 | 403 | (五) `then` 404 | 405 | ``` javascript 406 | function fn() { 407 | var dfd = $.Deferred(); 408 | //dfd.resolve(); //success 409 | //dfd.reject(); //fail 410 | dfd.notify('hi'); //progress 411 | return dfd.promise(); 412 | } 413 | 414 | var dfd = fn(); 415 | 416 | dfd.then( 417 | function() { 418 | alert('success'); 419 | }, 420 | function() { 421 | alert('fail'); 422 | }, 423 | function() { 424 | alert('progress'); 425 | alert(arguments[0]); //hi 426 | } 427 | ); 428 | ``` 429 | 430 | `then`的函数如果有返回值 431 | 432 | ``` javascript 433 | function fn1() { 434 | var dfd = $.Deferred(); 435 | 436 | dfd.resolve(); 437 | 438 | return dfd; 439 | } 440 | 441 | 442 | var dfd = fn1(); 443 | 444 | dfd = dfd.then(function(){ 445 | return 'then return value'; //如果返回值不是deffered或promise对象,则在源代码内部直接fireWith,会触发下面的done函数 446 | }); 447 | 448 | dfd.done(function() { 449 | console.log(arguments[0]); //then return value 450 | }); 451 | ``` 452 | 453 | `then`/`pipe(管道)`的函数如果返回值是`deffered`对象 454 | 455 | ``` javascript 456 | function fn1() { 457 | var dfd = $.Deferred(); 458 | 459 | dfd.resolve(); 460 | 461 | return dfd; 462 | } 463 | 464 | 465 | var dfd = fn1(); 466 | 467 | dfd = dfd.then(function(){ 468 | return dfd; //如果返回值是deffered对象 469 | }); 470 | 471 | dfd.done(function() { 472 | console.log(arguments[0]); 473 | }); 474 | ``` 475 | 476 | 477 | 478 | 479 | (五) `pipe` 480 | 481 | 管道的意思,需要注意和`then`方法其实进行了合并,其实用的不是特别多 482 | 483 | 484 | ``` javascript 485 | var dfd = $.Deferred(); 486 | 487 | dfd.resolve('hi'); 488 | 489 | //其实pipe和then是一样的,因此也是三个参数函数 490 | //分别对应resolve reject notify 491 | var newDfd = dfd.pipe(function() { 492 | return arguments[0] + 'pass then'; 493 | }); 494 | 495 | newDfd.done(function() { 496 | console.log(arguments[0]) //hipass then 497 | }) 498 | ``` 499 | 500 | (六) `when` 501 | 502 | - 所有的都是`resolve`才会`done` 503 | - 只要有一个`reject`就`done` 504 | 505 | ``` javascript 506 | function fn1() { 507 | var dfd = $.Deferred(); 508 | 509 | dfd.resolve(); 510 | 511 | return dfd; 512 | } 513 | 514 | function fn2() { 515 | var dfd = $.Deferred(); 516 | 517 | dfd.resolve(); 518 | return dfd; 519 | } 520 | 521 | 522 | $.when(fn1(),fn2()).done(function() { 523 | console.log("success"); //fn1和fn2都成功才会成功 524 | }) 525 | ``` 526 | 527 | 528 | ## 8.2 `$.when()` 529 | 530 | >源码 531 | 532 | ``` javascript 533 | jQuery.extend({ 534 | // Deferred helper 535 | when: function( subordinate /* , ..., subordinateN */ ) { 536 | var i = 0, 537 | //将arguments转化为数组 538 | resolveValues = core_slice.call( arguments ), 539 | length = resolveValues.length, 540 | 541 | // the count of uncompleted subordinates 542 | // 如果只有一个参数,会判断返回值是否是延迟对象,如果是则返回length = 1 543 | // 多参数remaining是length 544 | remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, 545 | 546 | // the master Deferred. If resolveValues consist of only a single Deferred, just use that. 547 | // 如果只有一个参数 remaining = 1,如果返回值是延迟对象,则deffered是when中的fn返回的延迟对象 548 | // 如果返回值不是deffered对象,则执行后面的$.Deffered 549 | // 550 | deferred = remaining === 1 ? subordinate : jQuery.Deferred(), 551 | 552 | // Update function for both resolve and progress values 553 | updateFunc = function( i, contexts, values ) { 554 | return function( value ) { 555 | contexts[ i ] = this; 556 | values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; 557 | if( values === progressValues ) { 558 | deferred.notifyWith( contexts, values ); 559 | } else if ( !( --remaining ) ) { 560 | deferred.resolveWith( contexts, values ); 561 | } 562 | }; 563 | }, 564 | 565 | progressValues, progressContexts, resolveContexts; 566 | 567 | // add listeners to Deferred subordinates; treat others as resolved 568 | if ( length > 1 ) { 569 | progressValues = new Array( length ); 570 | progressContexts = new Array( length ); 571 | resolveContexts = new Array( length ); 572 | for ( ; i < length; i++ ) { 573 | if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { 574 | resolveValues[ i ].promise() 575 | .done( updateFunc( i, resolveContexts, resolveValues ) ) 576 | .fail( deferred.reject ) 577 | .progress( updateFunc( i, progressContexts, progressValues ) ); 578 | } else { 579 | --remaining; 580 | } 581 | } 582 | } 583 | 584 | // if we're not waiting on anything, resolve the master 585 | // 如果没有参数需要执行 $.when().done() 586 | // 如果是一个参数且返回值是延迟对象,这里不执行 587 | // 如果是一个参数返回值不是延迟对象,这里也执行 588 | if ( !remaining ) { 589 | deferred.resolveWith( resolveContexts, resolveValues ); 590 | } 591 | 592 | // 如果是一个参数fn,则返回的是这个参数的延迟对象对应的promise() 593 | return deferred.promise(); 594 | } 595 | }); 596 | ``` 597 | 598 | 599 | >内容解析 600 | 601 | - 所有的都是`resolve`才会`done` 602 | - 只要有一个`reject`就`done` 603 | 604 | ``` javascript 605 | function fn1() { 606 | var dfd = $.Deferred(); 607 | dfd.resolve(); 608 | return dfd; 609 | } 610 | function fn2() { 611 | var dfd = $.Deferred(); 612 | dfd.reject(); 613 | return dfd; 614 | } 615 | //when中的fn参数必须返回延迟对象 616 | //如果不是返回延迟对象,则会跳过这个fn 617 | //$.when().done(function) 会执行成功 618 | //$.when(arg1,arg2).done() 可以传参数处理 619 | //$.when(fn1(),'111').done(function) 仍然会执行成功 620 | $.when(fn1(),fn2()).done(function() { 621 | console.log("success"); //fn1和fn2都成功才会成功 622 | }).fail(function(){ 623 | console.log("fail"); //fail 624 | }); 625 | 626 | ``` 627 | 628 | `when`传参情况 629 | 630 | ``` javascript 631 | function fn1() { 632 | var dfd = $.Deferred(); 633 | dfd.resolve(); 634 | return dfd; 635 | } 636 | 637 | function fn2() { 638 | var dfd = $.Deferred(); 639 | dfd.resolve(); 640 | return dfd; 641 | } 642 | 643 | 644 | function fn() { 645 | var dfd = $.Deferred(); 646 | dfd.resolve(); 647 | } 648 | 649 | //1.无参情况 650 | //无参数的情况下在when中新建了一个deffered对象 651 | //并返回deffered.promise() 652 | //在when内部触发了新建deffered对象的fireWith函数 653 | //因此done对象可以执行 654 | $.when().done(function() { 655 | console.log("success"); 656 | }); 657 | 658 | //2.只有一个参数,不返回延迟对象 659 | //和第一种情况类似 660 | $.when(fn()).done(function() { 661 | console.log("success"); 662 | }); 663 | 664 | //3.只有一个参数的情况,返回延迟对象 665 | //没有在when中新建deffered对象,而是使用fn1传入的deffered对象进行了处理 666 | //done函数也是和fn1返回的dfd对象对应 667 | $.when(fn1()).done(function() { 668 | console.log("success"); 669 | }); 670 | 671 | //4.多个参数的情况 672 | //使用计数器进行处理 673 | $.when(fn1(),fn2()).done(function() { 674 | console.log("success"); 675 | }); 676 | ``` 677 | -------------------------------------------------------------------------------- /总体架构.md: -------------------------------------------------------------------------------- 1 | ## 1. 总体架构 2 | 3 | ``` javascript 4 | (function(window, undefined) { 5 | [21~91] : $自执行匿名函数的私有属性 6 | [96~283] : $jQuery对象的属性和方法 7 | [285~347] : $拷贝继承 8 | [349~817] : $工具方法 9 | [877~2856] : $复杂选择器Sizzle 10 | [2880~3042] : $回调对象 11 | [3043~3183] : $延迟对象 12 | [3484~3295] : $功能检测 13 | [3308~3652] : $数据缓存 14 | [3653~3797] : $队列管理 15 | [3803~4299] : $元素属性 16 | [4300~5182] : $事件操作 17 | [5140~6057] : $DOM操作 18 | [6058~6620] : $样式操作 19 | [6621~7854] : $ajax操作 20 | [7855~8584] : $运动方法 21 | [8585~8792] : $屏幕位置 22 | [8804~8821] : $模块化 23 | [8826] : window.jQuery = window.$ = jQuery 24 | })(window); 25 | ``` 26 | 27 | ### 1. 1 自执行匿名函数 28 | 29 | - 代码压缩 30 | - 模块化 31 | - 缩短作用域链 32 | 33 | >内容解析 34 | 35 | (一)、自执行匿名函数创建了特殊的函数作用域,该作用域的代码不会和匿名函数外部的同名函数冲突 36 | ``` javascript 37 | (function(){ 38 | //局部函数 39 | function a() { 40 | alert('inner a'); 41 | } 42 | })(); 43 | 44 | //全局函数 45 | function a() { 46 | alert('out a'); 47 | } 48 | a(); //out a 49 | ``` 50 | 51 | (二)、缩短作用域链 52 | 53 | ``` javascript 54 | //访问局部变量window,不需要向上遍历作用域链,缩短查找时间,同时在压缩代码时局部变量window可被压缩 55 | (function(window){ 56 | window.a = 1; 57 | alert(a); 58 | })(window); 59 | 60 | //向上遍历到顶层作用域,访问速度变慢,全局变量window不能被压缩 61 | (function(){ 62 | window.a = 1; 63 | alert(a); 64 | })(); 65 | ``` 66 | 67 | (三)、`undefined`保证不被修改,可以被压缩,也可以缩短查找`undefined`的作用域链 68 | ``` javascript 69 | //自执行内部的undefined变量不会被外部的情况修改,低版本IE浏览器可以修改undefined的值 70 | (function(window,undefined){ 71 | alert(undefined); //undefined 72 | })(window); 73 | ``` -------------------------------------------------------------------------------- /拷贝继承.md: -------------------------------------------------------------------------------- 1 | ## 4. 拷贝继承 2 | 3 | >源码 4 | 5 | ``` javascript 6 | //[285] 7 | //详见(一)、(二)、(三) 8 | jQuery.extend = jQuery.fn.extend = function() { 9 | var options, name, src, copy, copyIsArray, clone, 10 | target = arguments[0] || {}, 11 | i = 1, 12 | length = arguments.length, 13 | deep = false; 14 | 15 | // Handle a deep copy situation 16 | //布尔值true则是深拷贝 17 | if ( typeof target === "boolean" ) { 18 | deep = target; 19 | //目标对象变成了第二项 20 | target = arguments[1] || {}; 21 | // skip the boolean and the target 22 | i = 2; 23 | } 24 | 25 | // Handle case when target is a string or something (possible in deep copy) 26 | //目标元素不是对象则变成空对象 27 | if ( typeof target !== "object" && !jQuery.isFunction(target) ) { 28 | target = {}; 29 | } 30 | 31 | // extend jQuery itself if only one argument is passed 32 | //如果参数只有1个,则是扩展jQuery自身的方法,详见(一) 33 | if ( length === i ) { 34 | target = this; 35 | --i; 36 | } 37 | 38 | //多个对象参数 39 | for ( ; i < length; i++ ) { 40 | // Only deal with non-null/undefined values 41 | //第二个开始的参数是不是空 42 | if ( (options = arguments[ i ]) != null ) { 43 | // Extend the base object 44 | for ( name in options ) { 45 | src = target[ name ]; 46 | copy = options[ name ]; 47 | 48 | // Prevent never-ending loop 49 | //防止循环引用 例如 var a = {}; $.extend(a,{name:a}) 50 | if ( target === copy ) { 51 | continue; 52 | } 53 | 54 | // Recurse if we're merging plain objects or arrays 55 | //深拷贝,被拷贝的值必须是对象或数组,利用递归,详见(三) 56 | if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { 57 | if ( copyIsArray ) { 58 | copyIsArray = false; 59 | //如果目标自带的值有,则选择目标自带的值 60 | clone = src && jQuery.isArray(src) ? src : []; 61 | } else { 62 | //如果目标自带的值有,则选择目标自带的值,详见(三) 63 | clone = src && jQuery.isPlainObject(src) ? src : {}; 64 | } 65 | 66 | // Never move original objects, clone them 67 | target[ name ] = jQuery.extend( deep, clone, copy ); 68 | 69 | // Don't bring in undefined values 70 | //浅拷贝 71 | } else if ( copy !== undefined ) { 72 | target[ name ] = copy; 73 | } 74 | } 75 | } 76 | } 77 | 78 | // Return the modified object 79 | return target; 80 | }; 81 | ``` 82 | 83 | >内容解析 84 | 85 | - `jQuery`允许扩展新的静态方法(只有一个对象参数) 86 | - `jQuery`允许扩展新的实例方法(只有一个对象参数) 87 | - 扩展自定义对象的属性和方法(多个对象参数) 88 | - 深浅拷贝 89 | 90 | (一)、扩展构造函数的静态方法和实例方法 91 | 92 | 93 | ``` javascript 94 | //[96] 95 | jQuery.fn = jQuery.prototype; 96 | //[285] 97 | jQuery.extend = jQuery.fn.extend; //jQuery.prototype.extend; 98 | 99 | //jQuery.extend 是静态方法 100 | //jQuery.prototype.extend 实例方法,原型对象的方法会被实例化对象继承 101 | 102 | 103 | $.extend({ 104 | f1: function(){ 105 | alert(1); 106 | }, 107 | f2: function(){ 108 | alert(2); 109 | } 110 | }); 111 | 112 | //静态方法 113 | $.f1(); //1 114 | $.f2(); //2 115 | 116 | $.fn.extend({ 117 | f1: function(){ 118 | alert(1); 119 | }, 120 | f2: function(){ 121 | alert(2); 122 | } 123 | }); 124 | 125 | //实例方法,jQuery允许我们在构造函数上扩展新的实例方法 126 | $().f1(); //1 127 | $().f2(); //2 128 | 129 | ``` 130 | 131 | (二)、扩展自定义对象的属性和方法 132 | 133 | ``` javascript 134 | //多个参数时,后面的参数的对象属性和方法扩展到第一个参数对象 135 | var obj = {}; 136 | 137 | $.extend( 138 | obj, 139 | { 140 | f1: function(){ 141 | alert(1); 142 | }, 143 | f2: function(){ 144 | alert(2); 145 | } 146 | }, 147 | { 148 | name: 'obj_extend' 149 | } 150 | ); 151 | 152 | console.log(obj); //{f1,f2,name}; 153 | ``` 154 | 155 | (三)、深浅拷贝 156 | 157 | ``` javascript 158 | var a = {name:'ziy2',age:23}; 159 | var b = a; //浅拷贝 160 | console.log(a===b); //true b是a的副本,引用了同一个内存块 161 | b.name = 'ziyi3'; 162 | console.log(a.name); //ziyi3 163 | 164 | var d = {name:'ziyi5',age:{age:22}}; 165 | var f = {}; 166 | 167 | for(var key in d) { //仍然是浅拷贝 168 | f[key] = d[key]; 169 | } 170 | 171 | f.age.age = 25; 172 | f.name = 'ziyi6'; 173 | console.log(d.age.age); //25 说明age属性仍然是浅拷贝 174 | console.log(d.name); //ziyi5 175 | 176 | 177 | //以下可能是一个深拷贝的函数,其实简单理解就是深拷贝就是两个不同的内存块,浅拷贝就是两个都有引用同一内存块 178 | var deepCopy= function(source) { 179 | var result={}; 180 | for (var key in source) { 181 | result[key] = typeof source[key]===’object’? deepCoyp(source[key]): source[key]; 182 | } 183 | return result; 184 | } 185 | 186 | ``` 187 | 188 | `jQuery`中默认是浅拷贝 189 | 190 | 191 | ``` javascript 192 | var 193 | a = {}, 194 | b = { 195 | name: 'ziyi2', 196 | age: { 197 | age: 23 198 | } 199 | }; 200 | 201 | $.extend(a,b); 202 | 203 | b.age.age = 24; 204 | console.log(a.age.age); //24 浅拷贝 205 | 206 | b.name = 'ziyi3'; 207 | console.log(a.name); //ziyi2 基本类型的值不受影响 208 | ``` 209 | 210 | 设置为深拷贝 211 | 212 | ``` javascript 213 | var 214 | a = {}, 215 | b = { 216 | name: 'ziyi2', 217 | age: { 218 | age: 23 219 | } 220 | }; 221 | 222 | $.extend(true,a,b); //添加参数true 223 | 224 | b.age.age = 24; 225 | console.log(a.age.age); //23 深拷贝 226 | 227 | b.name = 'ziyi3'; 228 | console.log(a.name); //ziyi2 229 | 230 | 231 | //保留原有的属性 232 | var c = {name: {familyName: 'zhu'}}, 233 | d = {name: {familyName: 'zhang'}}; 234 | 235 | $.extend(true,c,d); 236 | console.log(c); //{name: {familyName: 'zhang'}} 237 | 238 | var e = {name: {age: 23}}; 239 | $.extend(true,d,e); 240 | console.log(d); //{name: {familyName: 'zhang',age:23}} 保留d所有的属性familyName 241 | 242 | ``` 243 | -------------------------------------------------------------------------------- /数据缓存.md: -------------------------------------------------------------------------------- 1 | ## 10. 数据缓存 2 | 3 | - 在DOM下挂载大量的数据 4 | - 注意和`$().attr`和`$().prop`的区别 5 | 6 | ``` javascript 7 | 8 | //内部Date构造函数 9 | function Data() { 10 | } 11 | 12 | //内部Date实例对象的方法 13 | Data.prototype = { 14 | key: 15 | set: 16 | get: 17 | access: 18 | remove: 19 | hasData: 20 | discard: 21 | }; 22 | 23 | 24 | //扩展工具方法(调用了实例Date对象的方法) 25 | jQuery.extend({ 26 | acceptData: 27 | hasData: 28 | data: 29 | removeData: 30 | //带_的其实是内部私有方法 31 | _data: 32 | _removeData: 33 | }); 34 | 35 | //扩展实例方法(调用了实例Date对象的方法) 36 | jQuery.fn.extend({ 37 | data: 38 | removeData: 39 | }); 40 | ``` 41 | 42 | >内容解析 43 | 44 | DOM元素与对象之间互相引用会出现内存泄漏,使用数据缓存可以解决这个问题 45 | 46 | ``` javascript 47 | var oDiv = document.getElementById('div1'); 48 | var obj = {}; 49 | 50 | //互相引用导致内存泄漏 51 | //$("#div1").attr('name',obj) 52 | //$("#div1").prop('name',obj) 53 | oDiv.name = obj; 54 | obj.age = oDiv; 55 | ``` 56 | 57 | 使用案例 58 | 59 | ``` javascript 60 | //实例对象的方法 61 | $("#div1").data("name","ziyi2"); 62 | console.log($("#div1").data("name")); //ziyi2 63 | $("#div1").removeData("name"); 64 | console.log($("#div1").data("name")); //undefined 65 | 66 | //工具方法 67 | $.data(document.body,"name","ziyi2"); 68 | console.log($.data(document.body,"name")); //ziyi2 69 | console.log($.hasData(document.body)); //true 70 | $.removeData(document.body,"name"); 71 | console.log($.data(document.body,"name")); //undefined 72 | console.log($.hasData(document.body)); //false 73 | ``` 74 | 75 | 76 | 77 | ### 10.1 `Date`构造函数 78 | 79 | 80 | 为了防止DOM元素与对象之间互相引用会出现内存泄漏,自动给DOM元素加上一个属性,这个属性[`this.expando`]的值随着数字`1`开始递增,正好对应`Date`构造函数内部的`this.cache`对象,这个对象就是从`0`开始(`0`不对应任何DOM元素,而是对应不能使用`data`的对象类型)的一个对象,每一个数字对应一个DOM元素绑定的`data`,这样由于DOM元素的属性没有直接引用对象(而是使用数字和`this.cache`对象一一对应起来),所以不会造成内存泄漏. 81 | 82 | >源码 83 | 84 | ``` javascript 85 | function Data() { 86 | // Support: Android < 4, 87 | // Old WebKit does not have Object.preventExtensions/freeze method, 88 | // return new empty object instead with no [[set]] accessor 89 | // 详见(一) 90 | // 属性0不能被修改 91 | // 属性1,2,3,4...可以被修改 92 | Object.defineProperty( this.cache = {}, 0, { 93 | get: function() { 94 | return {}; 95 | } 96 | }); 97 | 98 | // 用于给所有需要增加data的DOM元素对象生成一个唯一的属性 99 | this.expando = jQuery.expando + Math.random(); 100 | } 101 | 102 | 103 | // DOM元素的this.expando属性的模式起始值是1 104 | Data.uid = 1; 105 | 106 | Data.accepts = function( owner ) { 107 | // Accepts only: 108 | // - Node 109 | // - Node.ELEMENT_NODE 110 | // - Node.DOCUMENT_NODE 111 | // - Object 112 | // - Any 113 | // 如果是节点的话只有element对象和document对象则可以存储数据 114 | return owner.nodeType ? 115 | owner.nodeType === 1 || owner.nodeType === 9 : true; 116 | }; 117 | 118 | 119 | 120 | Data.prototype = { 121 | key: function( owner ) { 122 | // We can accept data for non-element nodes in modern browsers, 123 | // but we should not, see #8335. 124 | // Always return the key for a frozen object. 125 | // 如果是不能存储的对象,则返回0 126 | if ( !Data.accepts( owner ) ) { 127 | return 0; 128 | } 129 | 130 | var descriptor = {}, 131 | // Check if the owner object already has a cache key 132 | // DOM元素第一次设置data值时,unlock = undefined 133 | // DOM元素第二次获取data值, unlocak = (dom元素的this.expando属性对应的Data.uid值) 134 | unlock = owner[ this.expando ]; 135 | 136 | // If not, create one 137 | // DOM第一次设置时可以进入 138 | // 获取值时不会进入 139 | if ( !unlock ) { 140 | //给DOM第一次加data值时需要给对应的标识符+1 141 | unlock = Data.uid++; 142 | 143 | // Secure it in a non-enumerable, non-writable property 144 | try { 145 | //descriptor = {this.expando : Data.uid} 146 | descriptor[ this.expando ] = { value: unlock }; 147 | //DOM元素对象多了一个属性this.expando,值是Data.uid 148 | //只设置一次,后面增加data值时不会变 149 | Object.defineProperties( owner, descriptor ); 150 | 151 | // Support: Android < 4 152 | // Fallback to a less secure definition 153 | } catch ( e ) { 154 | //兼容老版本写法 155 | descriptor[ this.expando ] = unlock; 156 | jQuery.extend( owner, descriptor ); 157 | } 158 | } 159 | 160 | // Ensure the cache object 161 | // 第一次时给cache设置属性 162 | // 例如 cache[1] = {} 163 | // 因为DOM own[this.expando] = 1 164 | // 所以cache的一个属性和一个dom对应 165 | // 通过的就是数字1 166 | // 获取值时不会进入 167 | if ( !this.cache[ unlock ] ) { 168 | this.cache[ unlock ] = {}; 169 | } 170 | 171 | // 返回DOM元素的标识符1 172 | return unlock; 173 | }, 174 | set: function( owner, data, value ) { 175 | var prop, 176 | // There may be an unlock assigned to this node, 177 | // if there is no entry for this "owner", create one inline 178 | // and set the unlock as though an owner entry had always existed 179 | // 同样先获取owner所对应的cache的属性 180 | unlock = this.key( owner ), 181 | // 获取当前owner所对应的data缓存 182 | // 需要注意cache和this.cache都是同一个引用 183 | cache = this.cache[ unlock ]; 184 | 185 | // Handle: [ owner, key, value ] args 186 | // data可能是{} 187 | if ( typeof data === "string" ) { 188 | cache[ data ] = value; 189 | 190 | // Handle: [ owner, { properties } ] args 191 | // 否则如果不是$.data(owner,data,value)的形式 192 | // 而是$.data(owner,{data,value})的形式 193 | } else { 194 | // Fresh assignments by object are shallow copied 195 | // 如果cache是空的,那么只要浅复制就行了 196 | if ( jQuery.isEmptyObject( cache ) ) { 197 | jQuery.extend( this.cache[ unlock ], data ); 198 | // Otherwise, copy the properties one-by-one to the cache object 199 | } else { 200 | // 否则就遍历data的所有属性 201 | // 然后进行赋值 202 | for ( prop in data ) { 203 | cache[ prop ] = data[ prop ]; 204 | } 205 | } 206 | } 207 | // 返回当前dom对应的data缓存 208 | return cache; 209 | }, 210 | get: function( owner, key ) { 211 | // Either a valid cache is found, or will be created. 212 | // New caches will be created and the unlock returned, 213 | // allowing direct access to the newly created 214 | // empty data object. A valid owner object must be provided. 215 | 216 | // this.key(owner)获取和owner对应的cache的属性 217 | // 例如this.key(owner) = 1 218 | // 由于cache的属性和dom元素owner的this.expando属性所对应的值相同 219 | // 因此也就获取了dom元素所对应的所有data缓存 220 | var cache = this.cache[ this.key( owner ) ]; 221 | 222 | // 如果指定了需要获取的属性值,则获取属性对应的值 223 | // 否则返回整个data缓存 224 | return key === undefined ? 225 | cache : cache[ key ]; 226 | }, 227 | 228 | //获取或设置data都通过这个函数实现 229 | access: function( owner, key, value ) { 230 | var stored; 231 | // In cases where either: 232 | // 233 | // 1. No key was specified 234 | // 2. A string key was specified, but no value provided 235 | // 236 | // Take the "read" path and allow the get method to determine 237 | // which value to return, respectively either: 238 | // 239 | // 1. The entire cache object 240 | // 2. The data stored at the key 241 | // 如果没有第三个参数value 242 | // 或者key也没有 243 | if ( key === undefined || 244 | ((key && typeof key === "string") && value === undefined) ) { 245 | 246 | // 获取值 247 | stored = this.get( owner, key ); 248 | 249 | // 返回值 250 | return stored !== undefined ? 251 | stored : this.get( owner, jQuery.camelCase(key) ); 252 | } 253 | 254 | // [*]When the key is not a string, or both a key and value 255 | // are specified, set or extend (existing objects) with either: 256 | // 257 | // 1. An object of properties 258 | // 2. A key and value 259 | // 如果有第三个参数,则是设置值 260 | // 或者第二参数是{},没有第三参数 261 | this.set( owner, key, value ); 262 | 263 | // Since the "set" path can have two possible entry points 264 | // return the expected data based on which path was taken[*] 265 | // 第二参数是{} 第三参数没有的情况,返回{} 266 | return value !== undefined ? value : key; 267 | }, 268 | remove: function( owner, key ) { 269 | var i, name, camel, 270 | // 获取owner的data值 271 | unlock = this.key( owner ), 272 | cache = this.cache[ unlock ]; 273 | 274 | //如果没有第二参数就是删除所有的data 275 | if ( key === undefined ) { 276 | this.cache[ unlock ] = {}; 277 | 278 | } else { 279 | // Support array or space separated string of keys 280 | if ( jQuery.isArray( key ) ) { 281 | // If "name" is an array of keys... 282 | // When data is initially created, via ("key", "val") signature, 283 | // keys will be converted to camelCase. 284 | // Since there is no way to tell _how_ a key was added, remove 285 | // both plain key and camelCase key. #12786 286 | // This will only penalize the array argument path. 287 | name = key.concat( key.map( jQuery.camelCase ) ); 288 | } else { 289 | camel = jQuery.camelCase( key ); 290 | // Try the string as a key before any manipulation 291 | // 需要注意驼峰法 292 | if ( key in cache ) { 293 | name = [ key, camel ]; 294 | } else { 295 | // If a key with the spaces exists, use it. 296 | // Otherwise, create an array by matching non-whitespace 297 | name = camel; 298 | name = name in cache ? 299 | [ name ] : ( name.match( core_rnotwhite ) || [] ); 300 | } 301 | } 302 | 303 | i = name.length; 304 | while ( i-- ) { 305 | delete cache[ name[ i ] ]; 306 | } 307 | } 308 | }, 309 | hasData: function( owner ) { 310 | return !jQuery.isEmptyObject( 311 | this.cache[ owner[ this.expando ] ] || {} 312 | ); 313 | }, 314 | discard: function( owner ) { 315 | if ( owner[ this.expando ] ) { 316 | delete this.cache[ owner[ this.expando ] ]; 317 | } 318 | } 319 | }; 320 | 321 | 322 | 323 | 324 | ``` 325 | 326 | >内容解析 327 | 328 | (一) `Object.defineProperty`和 `Object.preventExtensions/freeze`方法类似,可以读取设置的对象属性,不能对属性进行设置操作,但是老的版本不支持后两个方法,第三个参数还可以接收四个属性(可以详细介绍) 329 | 330 | ``` javascript 331 | var obj = {}; 332 | 333 | Object.defineProperty(obj,0,{ 334 | //只能读取,不能写入,写入会被忽略 335 | get: function() { 336 | return {name:"ziyi2"} 337 | } 338 | }); 339 | 340 | console.log(obj[0].name); //ziyi2 341 | 342 | obj[0].name = "ziyi3"; 343 | console.log(obj[0].name); //ziyi2 并不能被修改 344 | ``` 345 | 346 | 347 | `defineProperty()`传递三个参数:属性所在的对象,属性的名字,一个描述符对象(描述符对象的属性必须是 `configurable、enumerable、writable和value`,可以设置一个或多个属性值)可以设置一个或多个属性值 348 | - `configurable:`能否通过`delete`删除属性从而重新定义属性,能否修改属性的特性,能否把属性修改成访问器属性 349 | - `enumerable`:能否通过`for-in`循环返回属性 350 | - `writable`:能否修改属性的值 351 | - `value`:读取属性值得时候从这个位置读,写入属性值得时候把新值保存在这个位置 352 | 353 | 354 | ``` javascript 355 | var Person = {}; 356 | 357 | Object.defineProperty(Person,"name", { 358 | configurable:false, 359 | writable: false,//不可写 360 | value: "zhuxiankang" 361 | }); 362 | 363 | alert(Person.name); //zhuxiankang 364 | Person.name = "ziyi2"; 365 | alert(Person.name); //zhuxiankang 只读的,不能写,所以值不会变 366 | 367 | delete Person.name; 368 | alert(Person.name);//zhuxiankang 不能从对象中删除属性 369 | ``` 370 | 371 | 一旦把属性定义为不可配置,就不能在把它变为可配置的,此时如果修改除`writable`之外的特性都会导致错误 372 | 373 | ``` javascript 374 | Object.defineProperty(Person,"name", { //Uncaught TypeError: Cannot redefine property: name 375 | configurable:true, 376 | writable: false,//不可写 377 | value: "zhuxiankang" 378 | }); 379 | 380 | //可以多次修改同一个属性,但是把configurable设置为false以后就会有限制了 381 | ``` 382 | 383 | - `get`:在读取属性时调用的函数,默认为`undefined` 384 | - `set`:在写入属性时调用的函数,默认为`undefined` 385 | 386 | ``` javascript 387 | var book = { 388 | _year: 2004, 389 | version: 1 390 | }; 391 | 392 | Object.defineProperty(book,"year",{ 393 | get:function(){ 394 | return this._year; 395 | }, 396 | set:function(newValue){ 397 | if(newValue > 2004){ 398 | this._year = newValue; 399 | this.version = newValue - 2004; 400 | } 401 | } 402 | }); 403 | 404 | //读取访问其属性时会调用get函数,而这里是写入访问器属性的值,调用了setter函数并写入了新值 405 | book.year = 2005;//访问器属性year的值修改了以后导致了其他属性也修改了 406 | alert(book._year); //2005 407 | alert(book.version); //1 408 | ``` 409 | 410 | >注意: 只指定`getter`意味着属性是不能写,尝试写入属性会被忽略,只指定`setter`函数的属性也不能读 411 | 412 | (二) 案例调试 413 | 414 | ``` javascript 415 | var div = document.getElementById("div1"); 416 | $.data(div,"name","ziyi2"); //返回值是ziyi2 417 | console.log($.data(div,"name")); //ziyi2 418 | $.data(div,"age","27"); 419 | console.log($.data(div,"age")); 420 | $.data(div,{school: 'zjut'}); //返回值是{school:'zjut} 421 | console.log($.data(div)); //{name:ziyi2,age:27,school:zjut} 422 | $.removeData(div,"name"); 423 | $.removeData(div); //删除所有data 424 | console.log($.data(div)); //{} 425 | console.log($.hasData(div)); //false 426 | ``` 427 | 428 | (三) 数据缓存 429 | 430 | ``` javascript 431 | (function(window,undefined){ 432 | var ziyi2 = {}; 433 | function Date() { 434 | this.data = {}; 435 | } 436 | Date.prototype = { 437 | get:function() { 438 | return this.data; 439 | }, 440 | set: function(data) { 441 | this.data = data; 442 | } 443 | }; 444 | var data = new Date(); 445 | ziyi2.set = function(d) { 446 | data.set(d); 447 | }; 448 | ziyi2.get = function() { 449 | return data.get(); 450 | }; 451 | window.ziyi2 = ziyi2; 452 | })(window); 453 | 454 | ziyi2.set("ziyi2"); 455 | console.log(ziyi2.get()); //ziyi2 为什么data变量在局部函数(自执行函数中)中没有被释放? 这个和this.cache为什么没有被释放是一个道理 456 | ``` 457 | 458 | 类似于以下模块化写法 459 | 460 | 461 | ``` javascript 462 | var collections; 463 | 464 | if(!collections) { 465 | collections = {}; 466 | } 467 | 468 | collections.family = {}; 469 | 470 | (function namespace(){ 471 | //这里定义多种’集合‘类,使用局部变量和函数 472 | //例如Person类 473 | function Person(name,age){ 474 | this.name = name; 475 | this.age = age; 476 | } 477 | 478 | //Person类的子类Father类 479 | //使用Function.prototype.extend()方法来定义子类 480 | var Father = Person.extend( 481 | function Father(job) { 482 | this.job = job; 483 | }//constructor 子类的构造函数 484 | ); 485 | 486 | 487 | //Mother类 488 | //var Mother = 489 | 490 | //省略很多其他类 491 | //以及这些类的原型对象方法以及辅助函数和变量 492 | 493 | //这样就不需要return了,外部直接引用,保持引用也不会释放内部相关的局部变量,这也是闭包,不一定要返回函数 494 | collections.family.Father = Father; 495 | collections.family.Person = Person; 496 | 497 | }()); //立即执行 498 | ``` 499 | 500 | (四) 模块化写法 501 | 502 | 模块化: 例如CommonJS使用的`require()`函数,不同的模块必须避免修改全局执行上下文,所以模块应当尽可能少的定义全局标识,理想状况是所有的模块都不应当定义超过一个全局标识,例如(三)中的`ziyi2`和`collections.family`就是一个全局标识,在模块创建过程中避免污染全局变量的一种方法是使用一个对象作为命名空间,它将函数和值作为命名空间对象属性存储起来,而不是定义全局函数和变量 503 | 504 | ``` javascript 505 | var father = {}; //命名空间 506 | 507 | father.Father = function(name,age){ //构造函数 508 | this.name = name; 509 | this.age = age; 510 | }; 511 | 512 | var F = father.Father; //导入到另外一个文件的全局命名空间中 513 | var f = new F('victor',23); 514 | write(f.name); //victor 515 | write(f.age); //23 516 | //模块对外导出一些共用API,这些API是提供给其他程序员使用的,包括函数,类,属性和方法 517 | //但是模块的实现往往需要一些辅助函数和方法 518 | //这些函数和方法并不需要在函数外部可见 519 | 520 | //可以将模块定义在某个函数的内部来实现 521 | //函数的作用域 522 | //在函数中声明的变量在整个函数体内都是可见的,包括嵌套的函数中 523 | //在函数的外部不可见 524 | 525 | //块级作用域 526 | (function(){ 527 | //模块代码 528 | })(); 529 | ``` 530 | 531 | 模块化写法一 532 | 533 | ``` javascript 534 | //声明全局变量Person,使用一个函数的返回值给它赋值 535 | //函数定义后立即执行 536 | //返回值赋值给Person 537 | //注意它是一个函数表达式,因此函数'invocation'并没有创建全局变量 538 | 539 | var Person = (function invocation(){//第一行代码 540 | 541 | function Person(name,age){ //这个构造函数是一个局部变量 542 | this.name = name; 543 | this.age = age; 544 | } 545 | 546 | //原型方法 547 | Person.prototype.sayInfo = function(){ 548 | Info(); //调用了这个辅助函数 549 | write(this.name + '-' + this.age); 550 | }; 551 | 552 | //辅助函数和变量 553 | //不属于模块的共有API,隐藏在这个函数的作用域内 554 | //因此我们不必将它们定义为Person的属性 555 | function Info(){ 556 | write(str); 557 | } 558 | var str = '这是一个辅助函数'; 559 | 560 | //这个模块的共有API是Person构造函数 561 | //我们需要把这个函数从私有命名空间中导出来 562 | //以便在外部可以使用它,我们通过返回构造函数来导出它 563 | //它变成第一行代码所指的表达式的值 564 | return Person; 565 | }()); //立即执行 566 | 567 | var p = new Person('victor',25); //类似于闭包,一直保持对内部Person构造函数的引用? 568 | write(p.name); //victor 569 | write(p.age); //25 570 | p.sayInfo(); //这是一个辅助函数 victor-25 571 | 572 | //一旦将模块代码封装进一个函数,就需要一些方法导出其共用API 573 | //以便在模块函数的外部调用它们 574 | //上面的例子中模块函数返回构造函数 575 | //这个构造函数随后赋值给一个全局变量 576 | 577 | //将值返回表明API已经导出在函数作用域之外 578 | 579 | ``` 580 | 581 | 模块化写法二 582 | 583 | ``` javascript 584 | //上面只是一个类,如果包含多个类等,则可以返回命名空间对象 585 | 586 | 587 | //创建一个全局变量用来存放集合相关的模块 588 | var collections; 589 | 590 | if(!collections) { 591 | collections = {}; 592 | } 593 | 594 | //定义Family模块 595 | collections.family = (function namespace(){ 596 | //这里定义多种’集合‘类,使用局部变量和函数 597 | //例如Person类 598 | function Person(name,age){ 599 | this.name = name; 600 | this.age = age; 601 | } 602 | 603 | //Person类的子类Father类 604 | //使用Function.prototype.extend()方法来定义子类 605 | var Father = Person.extend( 606 | function Father(job) { 607 | this.job = job; 608 | }//constructor 子类的构造函数 609 | ); 610 | 611 | 612 | //Mother类 613 | //var Mother = 614 | 615 | //省略很多其他类 616 | //以及这些类的原型对象方法以及辅助函数和变量 617 | 618 | 619 | 620 | //返回的是一个对象 621 | //这个对象叫做命名空间对象 622 | //这个对象的属性都是以上定义的类 623 | return { 624 | Perosn: Person, 625 | Father: Father 626 | //后面还有许多类似的类 627 | }; 628 | }()); //立即执行 629 | ``` 630 | 631 | 模块化写法三 632 | 633 | ``` javascript 634 | //另外一种类似的技术是将模块函数当做构造函数,通过new来调用 635 | var collections; 636 | 637 | if(!collections) { 638 | collections = {}; 639 | } 640 | 641 | //定义Family模块 642 | 643 | var a = (new function Person(name){ //先是一个立即执行的构造函数,然后使用new 644 | this.name = name; 645 | }('victor')); 646 | 647 | write(a.name); //victor 648 | 649 | 650 | /** 651 | * new function namespance(){}() 652 | */ 653 | 654 | collections.family = (new function namespace(){ 655 | //这里定义多种’集合‘类,使用局部变量和函数 656 | //例如Person类 657 | function Person(name,age){ 658 | this.name = name; 659 | this.age = age; 660 | } 661 | 662 | //Person类的子类Father类 663 | //使用Function.prototype.extend()方法来定义子类 664 | var Father = Person.extend( 665 | function Father(job) { 666 | this.job = job; 667 | }//constructor 子类的构造函数 668 | ); 669 | 670 | 671 | //Mother类 672 | //var Mother = 673 | 674 | //省略很多其他类 675 | //以及这些类的原型对象方法以及辅助函数和变量 676 | 677 | 678 | 679 | /**返回的是一个对象 680 | //这个对象叫做命名空间对象 681 | //这个对象的属性都是以上定义的类 682 | return { 683 | Person: Person, 684 | Father: Father 685 | //后面还有许多类似的类 686 | };*/ 687 | 688 | //不要return 689 | this.Person = Person; //this.Person就成了new function namespace()构造函数的一个属性 690 | this.Father = Father; //所以就不需要返回了,因为namespace就是构造函数了,相当于返回了一个立即new出来的namespace构造函数实例 691 | 692 | }()); //立即执行 693 | 694 | 695 | ``` 696 | 697 | 那前面几种无非就是内部有一个立即执行的匿名函数,构建了一个作用域,然后把内部的某个对象返回供给外部的window对象的属性使用,这样的话就保持了外部对内部的引用,也可以直接这么干,其实也就是类似了(三)的写法,需要注意的是有闭包的思想 698 | 699 | ``` javascript 700 | //另外一种替代的方法 701 | 702 | var collections; 703 | 704 | if(!collections) { 705 | collections = {}; 706 | } 707 | 708 | collections.family = {}; 709 | 710 | (function namespace(){ 711 | //这里定义多种’集合‘类,使用局部变量和函数 712 | //例如Person类 713 | function Person(name,age){ 714 | this.name = name; 715 | this.age = age; 716 | } 717 | 718 | //Person类的子类Father类 719 | //使用Function.prototype.extend()方法来定义子类 720 | var Father = Person.extend( 721 | function Father(job) { 722 | this.job = job; 723 | }//constructor 子类的构造函数 724 | ); 725 | 726 | 727 | //Mother类 728 | //var Mother = 729 | 730 | //省略很多其他类 731 | //以及这些类的原型对象方法以及辅助函数和变量 732 | 733 | //这样就不需要return了 734 | collections.family.Father = Father; 735 | collections.family.Person = Person; 736 | 737 | }()); //立即执行 738 | ``` 739 | 740 | 补充说明闭包在块级作用域中的使用 741 | 742 | ``` javascript 743 | //JS将function关键字当做一个函数声明的开始,函数声明后面不能跟圆括号 744 | //函数表达式的后面可以跟圆括号 745 | //要将函数声明转换成函数表达式只要给它加上一对圆括号即可 746 | (function(){ 747 | //这里是块级作用域 748 | })(); 749 | //如果在某些地方只是临时需要一些变量,就可以私有作用域 750 | function outputNumbers(count){ 751 | (function(){ 752 | //块级作用域 753 | for(var i=0;i源码 910 | 911 | ``` javascript 912 | // These may be used throughout the jQuery core codebase 913 | data_user = new Data(); 914 | data_priv = new Data(); 915 | 916 | jQuery.extend({ 917 | acceptData: Data.accepts, 918 | 919 | hasData: function( elem ) { 920 | return data_user.hasData( elem ) || data_priv.hasData( elem ); 921 | }, 922 | 923 | data: function( elem, name, data ) { 924 | return data_user.access( elem, name, data ); 925 | }, 926 | 927 | removeData: function( elem, name ) { 928 | data_user.remove( elem, name ); 929 | }, 930 | 931 | // TODO: Now that all calls to _data and _removeData have been replaced 932 | // with direct calls to data_priv methods, these can be deprecated. 933 | _data: function( elem, name, data ) { 934 | return data_priv.access( elem, name, data ); 935 | }, 936 | 937 | _removeData: function( elem, name ) { 938 | data_priv.remove( elem, name ); 939 | } 940 | }); 941 | ``` 942 | 943 | ### 10.3 `data`实例方法 944 | 945 | 946 | >源码 947 | 948 | ``` javascript 949 | jQuery.fn.extend({ 950 | data: function( key, value ) { 951 | var attrs, name, 952 | // 获取$()[0]元素 953 | elem = this[ 0 ], 954 | i = 0, 955 | data = null; 956 | 957 | // Gets all values 958 | // 如果一个参数都没有 $().data() 则获取所有缓存 959 | if ( key === undefined ) { 960 | //dom元素如果存在 961 | if ( this.length ) { 962 | //获取数据,这里获取的是构造函数Date的this.cache中的值 963 | data = data_user.get( elem ); 964 | 965 | //这里获取HTML5中的dom属性data-的值 966 | if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { 967 | //获取元素的attributes值 968 | attrs = elem.attributes; 969 | //遍历所有的属性(可能有多个data-) 970 | for ( ; i < attrs.length; i++ ) { 971 | //详见(一),获取id/data-set/style 972 | name = attrs[ i ].name; 973 | 974 | //找到data-set值 975 | if ( name.indexOf( "data-" ) === 0 ) { 976 | 977 | //获取set 978 | name = jQuery.camelCase( name.slice(5) ); 979 | //获取html元素中的data-的属性值 980 | dataAttr( elem, name, data[ name ] ); 981 | } 982 | } 983 | data_priv.set( elem, "hasDataAttrs", true ); 984 | } 985 | } 986 | 987 | //返回this.cache和dom元素的data-的组合对象值 988 | return data; 989 | } 990 | 991 | // Sets multiple values 992 | // 设置多个值 $().data({}) 993 | if ( typeof key === "object" ) { 994 | return this.each(function() { 995 | data_user.set( this, key ); 996 | }); 997 | } 998 | 999 | // 1000 | return jQuery.access( this, function( value ) { 1001 | var data, 1002 | camelKey = jQuery.camelCase( key ); 1003 | 1004 | // The calling jQuery object (element matches) is not empty 1005 | // (and therefore has an element appears at this[ 0 ]) and the 1006 | // `value` parameter was not undefined. An empty jQuery object 1007 | // will result in `undefined` for elem = this[ 0 ] which will 1008 | // throw an exception if an attempt to read a data cache is made. 1009 | // 如果elem存在并且value==undefined 1010 | // 则是获取数据 1011 | if ( elem && value === undefined ) { 1012 | // Attempt to get data from the cache 1013 | // with the key as-is 1014 | // 先获取原始数据例如family-name 1015 | // 详见(一) 1016 | data = data_user.get( elem, key ); 1017 | // 由于family-name是转驼峰存储 即familyName 因此data = undefined 1018 | // 其他形式则可以返回值 1019 | if ( data !== undefined ) { 1020 | return data; 1021 | } 1022 | 1023 | // Attempt to get data from the cache 1024 | // with the key camelized 1025 | // 尝试获取驼峰数据 1026 | data = data_user.get( elem, camelKey ); 1027 | if ( data !== undefined ) { 1028 | return data; 1029 | } 1030 | 1031 | // Attempt to "discover" the data in 1032 | // HTML5 custom data-* attrs 1033 | // 如果this.cache中没有该属性值,则获取html5中的该值试试 1034 | data = dataAttr( elem, camelKey, undefined ); 1035 | if ( data !== undefined ) { 1036 | return data; 1037 | } 1038 | 1039 | // We tried really hard, but the data doesn't exist. 1040 | return; 1041 | } 1042 | 1043 | // Set the data... 1044 | // 设置数据 例如$().data({name:'zhuxianakang'}) 1045 | // this.each 对所有符合条件的元素进行设置 1046 | // 详见(一) 1047 | this.each(function() { 1048 | // First, attempt to store a copy or reference of any 1049 | // data that might've been store with a camelCased key. 1050 | //详见(一)最后 1051 | var data = data_user.get( this, camelKey ); 1052 | 1053 | // For HTML5 data-* attribute interop, we have to 1054 | // store property names with dashes in a camelCase form. 1055 | // This might not apply to all properties...* 1056 | data_user.set( this, camelKey, value ); 1057 | 1058 | // *... In the case of properties that might _actually_ 1059 | // have dashes, we need to also store a copy of that 1060 | // unchanged property. 1061 | if ( key.indexOf("-") !== -1 && data !== undefined ) { 1062 | data_user.set( this, key, value ); 1063 | } 1064 | }); 1065 | 1066 | // arguments.length如果>1则说设置数据 1067 | // 否则是获取数据 1068 | 1069 | }, null, value, arguments.length > 1, null, true ); 1070 | }, 1071 | 1072 | removeData: function( key ) { 1073 | return this.each(function() { 1074 | data_user.remove( this, key ); 1075 | }); 1076 | } 1077 | }); 1078 | 1079 | 1080 | function dataAttr( elem, key, data ) { 1081 | var name; 1082 | 1083 | // If nothing was found internally, try to fetch any 1084 | // data from the HTML5 data-* attribute 1085 | if ( data === undefined && elem.nodeType === 1 ) { 1086 | //data-set set只是一种情况,如果是data-set-name, 1087 | name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); 1088 | //获取data-set的值zhuxiankang 1089 | data = elem.getAttribute( name ); 1090 | 1091 | if ( typeof data === "string" ) { 1092 | try { 1093 | data = data === "true" ? true : 1094 | data === "false" ? false : 1095 | data === "null" ? null : 1096 | // Only convert to a number if it doesn't change the string 1097 | +data + "" === data ? +data : 1098 | rbrace.test( data ) ? JSON.parse( data ) : 1099 | data; 1100 | } catch( e ) {} 1101 | 1102 | // Make sure we set the data so it isn't changed later 1103 | // 设置this.cache的值,增加在html的dom元素的data-的属性值到this.cache 1104 | data_user.set( elem, key, data ); 1105 | } else { 1106 | data = undefined; 1107 | } 1108 | } 1109 | return data; 1110 | } 1111 | 1112 | 1113 | 1114 | 1115 | ``` 1116 | 1117 | >内容解析 1118 | 1119 | 设置和获取数据 1120 | 1121 | ``` javascript 1122 | 1123 |
    1124 | 1125 | $('#div1').data('name','zhuxiankang'); 1126 | $('#div1').data('family-name','zhuxiankang'); 1127 | console.log($('#div1').data('family-name')); //zhuxiankang 1128 | console.log($('#div1').data()); 1129 | //{familyName:"zhuxiankang",name:"zhuxiankang",set:"zhuxiankang"} 1130 | $("#div1").data('set'); //zhuxiankang 1131 | $("#div1").data({1:'1',2:'2'}); 1132 | console.log($("#div1").data()); //{1:'1',2:'2',set:'zhuxiankang'} 1133 | 1134 | $("#div1").data('nameAge','ziyi3'); 1135 | $("#div1").data('name-age','ziyi2'); 1136 | console.log($("#div1").data()); //nameAge:ziyi2 name-age:ziyi2 1137 | ``` -------------------------------------------------------------------------------- /私有属性.md: -------------------------------------------------------------------------------- 1 | ## 2.私有属性 2 | 3 | ### 2.1 rootjQuery 4 | 5 | - 压缩 6 | - 查找局部变量`rootjQuery`而不是执行`jQuery(document)`,提高代码性能 7 | 8 | >源码 9 | 10 | ``` javascript 11 | //[21~23] 12 | var 13 | // A central reference to the root jQuery(document) 14 | rootjQuery, 15 | 16 | //[865~866] 17 | // All jQuery objects should point back to these 18 | rootjQuery = jQuery(document); 19 | ``` 20 | 21 | >提示: `rootjQuery`可以压缩,`jQuery(document)`不能被压缩. 22 | 23 | ### 2.2 readyList 24 | 25 | - 用于加载DOM 26 | - 延迟对象 27 | 28 | 详见`5.3 $.ready()` 29 | 30 | 31 | 32 | ### 2.3 core_strundefined 33 | 34 | - 兼容性 35 | 36 | >源码 37 | ``` javascript 38 | //[28~30] 39 | // Support: IE9 40 | // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined` 41 | core_strundefined = typeof undefined, //'undefined'字符串 42 | ``` 43 | 44 | >内容解析 45 | ``` javascript 46 | window.a == undefined //并不是所有情况都兼容,xml节点不能判断 xmlNode 47 | typeof window.a == 'undefined' //所有情况兼容 48 | ``` 49 | 50 | ### 2.4 window属性 51 | 52 | - 压缩 53 | - 缩短查找作用域链 54 | 55 | >源码 56 | ``` javascript 57 | //[32~35] 58 | // Use the correct document accordingly with window argument (sandbox) 59 | location = window.location, 60 | document = window.document, 61 | docElem = document.documentElement, 62 | ``` 63 | 64 | ### 2.5 _变量 65 | 66 | - 防冲突 67 | 68 | >源码 69 | ``` javascript 70 | [37~41] 71 | // Map over jQuery in case of overwrite 72 | _jQuery = window.jQuery, 73 | 74 | // Map over the $ in case of overwrite 75 | _$ = window.$, 76 | ``` 77 | 78 | >内容解析 79 | 80 | ``` javascript 81 | 84 | 85 | 86 | //执行了_$ = window.$, 将用户或第三方的$变量内容存储下来,防止引用jQuery之前的变量冲突 87 | 90 | ``` 91 | 92 | ### 2.6 class2type 93 | 94 | - 空对象 95 | - 类型 96 | 97 | >源码 98 | ``` javascript 99 | //[43~44] 100 | // [[Class]] -> type pairs 101 | class2type = {}, 102 | ``` 103 | 104 | 详见`5.7 $.type()` 105 | 106 | ### 2.7 core_deletedIds 107 | - 空数组 108 | 109 | >源码 110 | ``` javascript 111 | //[46~47] 112 | // List of deleted data cache ids, so we can reuse them 113 | core_deletedIds = [], 114 | ``` 115 | 116 | ### 2.8 core_version 117 | - 字符串 118 | - 版本号 119 | 120 | >源码 121 | ``` javascript 122 | //[49] 123 | core_version = "2.0.3", 124 | ``` 125 | ### 2.9 数组、对象、字符串方法 126 | - 压缩 127 | - 缩短查找时间 128 | 129 | >源码 130 | 131 | ```javascript 132 | //[51~58] 133 | // Save a reference to some core methods 134 | core_concat = core_deletedIds.concat, 135 | core_push = core_deletedIds.push, 136 | core_slice = core_deletedIds.slice, 137 | core_indexOf = core_deletedIds.indexOf, 138 | core_toString = class2type.toString, 139 | core_hasOwn = class2type.hasOwnProperty, 140 | core_trim = core_version.trim, //去除字符串的空格 141 | ``` 142 | 143 | ### 2.10 jQuery(重点) 144 | 145 | - 构造函数 146 | - 原型 147 | - 面向对象 148 | 149 | >源码 150 | ``` javascript 151 | //[60] 152 | // Define a local copy of jQuery 153 | jQuery = function( selector, context ) { 154 | // The jQuery object is actually just the init constructor 'enhanced' 155 | return new jQuery.fn.init( selector, context, rootjQuery ); 156 | }, 157 | //[96] 158 | jQuery.fn = jQuery.prototype = { 159 | // The current version of jQuery being used 160 | jquery: core_version, 161 | constructor: jQuery, 162 | init: function( selector, context, rootjQuery ) {} 163 | ... 164 | } 165 | //[282] 166 | // Give the init function the jQuery prototype for later instantiation 167 | jQuery.fn.init.prototype = jQuery.fn; 168 | ``` 169 | 170 | >内容解析 171 | 172 | (一)、普通面向对象的编程方法 173 | ``` javascript 174 | function Obj() {} 175 | Obj.prototype.init = function(){ 176 | }; 177 | 178 | Obj.prototype.extend = function(){ 179 | }; 180 | var o = new Obj(); 181 | o.init(); //首先需要初始化 182 | o.css(); //然后才去做其他方法的工作,那么jQuery是这么做的? 183 | ``` 184 | 185 | (二)、`jQuery`的面向对象的编程方法 186 | 187 | ``` javascript 188 | function jQuery() { 189 | return new jQuery.prototype.init(); //jQuery.prototype = jQuery.fn 190 | //类似于return new jQuery(); 191 | //同时return new A()的形式让我们在创建实例时可以省略new,例如$('div'),而不是new $('div') 192 | } 193 | jQuery.prototype.init = function() { 194 | alert('init'); 195 | } 196 | jQuery.prototype.css = function() { 197 | alert('css'); 198 | } 199 | jQuery.prototype.init.prototype = jQuery.prototype; //jQuery.prototype.init = jQuery 200 | jQuery().css(); //init css 201 | //jQuery()返回jQuery实例的同时进行初始化工作 202 | //jQuery()类似于var a = new A(); a.init(); 两步操作 203 | ``` 204 | 205 | ### 2.11 正则变量 206 | 207 | >源码 208 | ``` javascript 209 | //[66] 210 | // Used for matching numbers 211 | core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, 212 | 213 | // Used for splitting on whitespace 214 | core_rnotwhite = /\S+/g, 215 | 216 | // A simple way to check for HTML strings 217 | // Prioritize #id over to avoid XSS via location.hash (#9521) 218 | // Strict HTML recognition (#11290: must start with <) 219 | rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, 220 | 221 | // Match a standalone tag 222 | rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, 223 | 224 | // Matches dashed string for camelizing 225 | rmsPrefix = /^-ms-/, 226 | rdashAlpha = /-([\da-z])/gi, 227 | ``` 228 | 229 | ### 2.12 fcamelCase 230 | - 回调函数 231 | ### 2.13 completed 232 | - 回调函数 233 | 234 | 详见`5.3 $.ready()` -------------------------------------------------------------------------------- /队列管理.md: -------------------------------------------------------------------------------- 1 | ## 11. 队列管理 2 | 3 | - 队列管理主要的功能使异步操作按顺序执行,从而可以防止地狱回调 4 | - 队列管理和`$.Callback`以及`$.Deffered`最大的区别在于队列管理可以对更多的异步函数进行管理,从而功能更强大,后两者一般只能对单个异步函数进行管理. 5 | 6 | 7 | > 内容解析 8 | 9 | ``` javascript 10 | function fn1() { 11 | console.log('a'); 12 | } 13 | 14 | function fn2() { 15 | console.log('b'); 16 | } 17 | 18 | $.queue(document,'fn', [fn1,fn2]); //也可以一个个增加 19 | $.dequeue(document,'fn'); //a, 执行了fn1 20 | $.dequeue(document,'fn'); //b, 执行了fn2 21 | ``` 22 | 23 | `$().animate`可以利用队列功能让多个异步函数可以按顺序执行 24 | 25 | ``` 26 | $('#div').animate({left:'200px'}); //需要注意设置元素的定位为relative/fixed/absolute 27 | $('#div').animate({top:'200px'}); 28 | $('#div').animate({left:'0'}); 29 | $('#div').animate({top:'0'}); 30 | ``` 31 | 32 | ### 11.1 `queue`工具方法 33 | 34 | >源码 35 | 36 | ``` javascript 37 | 38 | // [3654] 39 | jQuery.extend({ 40 | //入队方法 41 | queue: function( elem, type, data ) { 42 | var queue; 43 | 44 | if ( elem ) { 45 | //详见(一) 46 | //type = fn + queue 47 | type = ( type || "fx" ) + "queue"; 48 | //先从data的this.cache中获取一下fnqueue是否存在 49 | //需要注意如果第二次设置同一个elem对象的同一个type属性,则这里先获取this.cache.uid[type+'queue'] 50 | //例如(一)的 $.queue(document,'fn', fn2); 51 | //此时queue = this.cache.uid[type+'queue'] 52 | queue = data_priv.get( elem, type ); 53 | 54 | // Speed up dequeue by getting out quickly if this is just a lookup 55 | // 如果data存在,则是要queue入队处理 56 | if ( data ) { 57 | //如果data_priv这个data缓存中不存在fnqueue这个属性,并且需要入队的参数是数组 58 | //如果传入的data是一个数组,则覆盖之前的fnqueue属性 59 | //例如$.queue(document,'fn', [fn3]),此时data是数组 60 | if ( !queue || jQuery.isArray( data ) ) { 61 | //将需要入队的函数参数放入data_priv.cache.1.fnqueue = [data] 62 | //需要注意fnqueue这个属性是一个数组 63 | queue = data_priv.access( elem, type, jQuery.makeArray(data) ); 64 | //如果queue已经存在,则说明queue = this.cache.uid[type+'queue'] 65 | //此时再次this.cache.uid[type+'queue'].push(data) 66 | //所以data_priv中该elem对应的uid下的[type + 'queue']属性就改变了 67 | } else { 68 | queue.push( data ); 69 | } 70 | } 71 | //返回data_priv中该elem对应的uid下的[type + 'queue']属性 72 | //这个值是一个数组,这个数组的元素是所有queue的函数 73 | //需要注意,如果没有data参数,例如$.queue(document,'fn'),则是获取queue队列中的函数操作 74 | return queue || []; 75 | } 76 | }, 77 | 78 | //出队方法 79 | dequeue: function( elem, type ) { 80 | //type默认是fx 81 | type = type || "fx"; 82 | 83 | //利用$.queue没有第三参数获取queue 84 | var queue = jQuery.queue( elem, type ), 85 | startLength = queue.length, 86 | //推出最前面的一个数组元素 87 | fn = queue.shift(), 88 | //这个钩子函数只有当empty.fire()时候才会触发add()函数 89 | //如果queue还有值,则返回queue 90 | hooks = jQuery._queueHooks( elem, type ), 91 | //next只要用于回调函数的第二参数 92 | //详见(二) 93 | next = function() { 94 | jQuery.dequeue( elem, type ); 95 | }; 96 | 97 | // If the fx queue is dequeued, always remove the progress sentinel 98 | if ( fn === "inprogress" ) { 99 | fn = queue.shift(); 100 | startLength--; 101 | } 102 | 103 | //如果有需要执行的fn 104 | if ( fn ) { 105 | 106 | // Add a progress sentinel to prevent the fx queue from being 107 | // automatically dequeued 108 | if ( type === "fx" ) { 109 | queue.unshift( "inprogress" ); 110 | } 111 | 112 | // clear up the last queue stop function 113 | delete hooks.stop; 114 | //执行回调函数,需要注意执行next就是执行dequeue 115 | fn.call( elem, next, hooks ); 116 | } 117 | 118 | //如果startLength等于0 119 | //没有需要执行的fn时删除fnqueue和fnqueueHooks属性 120 | if ( !startLength && hooks ) { 121 | hooks.empty.fire(); 122 | } 123 | }, 124 | 125 | // not intended for public consumption - generates a queueHooks object, or returns the current one 126 | // 私有方法 127 | _queueHooks: function( elem, type ) { 128 | var key = type + "queueHooks"; 129 | return data_priv.get( elem, key ) || data_priv.access( elem, key, { 130 | empty: jQuery.Callbacks("once memory").add(function() { 131 | data_priv.remove( elem, [ type + "queue", key ] ); 132 | }) 133 | }); 134 | } 135 | }); 136 | ``` 137 | 138 | 139 | 140 | 141 | 142 | >内容解析 143 | 144 | 145 | (一) `$.queue`方法解析 146 | 147 | ``` javascript 148 | console.log($.queue(document,'fn', fn1)); //[fn1] 149 | console.log($.queue(document,'fn', fn2)); //[fn1,fn2] 150 | console.log($.queue(document,'fn', [fn3])); //[fn3] 之前的fn1和fn2都没了 151 | console.log($.queue(document,'fn')); //没有第三个参数,则是获取queue队列中的函数 152 | $.dequeue(document,'fn'); //this is fn3... 153 | $.dequeue(document,'fn'); //此时没有任何可以执行的回调函数,并销毁fnqueue 154 | ``` 155 | 156 | (二) `next`参数(第二参数) 157 | 158 | ``` javascript 159 | function fn1(next) { 160 | console.log("this is fn1..."); 161 | console.log(this === document); //this指向了document 内部使用fn1.call(document,next,hooks) 162 | next(); 163 | } 164 | 165 | function fn2() { 166 | console.log("this is fn2..."); 167 | } 168 | 169 | console.log($.queue(document,'fn', fn1)); //[fn1] 170 | console.log($.queue(document,'fn', fn2)); //[fn1,fn2] 171 | 172 | $.dequeue(document,'fn'); 173 | //this is fn1 174 | //true 175 | //this if fn2 176 | ``` 177 | 178 | (三) `hooks`参数(第三参数) 179 | 180 | ``` javascript 181 | function fn1(next,hooks) { 182 | console.log("this is fn1..."); 183 | console.log(this === document); 184 | hooks.empty.fire(); //清空了queue 185 | } 186 | 187 | function fn2() { 188 | console.log("this is fn2..."); 189 | } 190 | 191 | console.log($.queue(document,'fn', fn1)); //[fn1] 192 | console.log($.queue(document,'fn', fn2)); //[fn1,fn2] 193 | 194 | $.dequeue(document,'fn'); 195 | //this is fn1 196 | //true 197 | 198 | $.dequeue(document,'fn'); 199 | //因为queue为空,相当于又执行了一次hooks.empty.fire() 200 | ``` 201 | 202 | 203 | (四) 私有方法`_queueHooks` 204 | 205 | - 尽管私有,但是对外可见 206 | 207 | ``` javascript 208 | console.log($._queueHooks); 209 | ``` 210 | 211 | 212 | 213 | ### 11.2 `queue`实例方法 214 | 215 | 216 | 217 | >源码 218 | 219 | ``` javascript 220 | jQuery.fn.extend({ 221 | queue: function( type, data ) { 222 | var setter = 2; 223 | 224 | //如果type没有,即省略第一参数,则只是传入data 225 | //因此data = type 226 | if ( typeof type !== "string" ) { 227 | data = type; 228 | type = "fx"; 229 | setter--; 230 | } 231 | 232 | //如果一个参数都没有 233 | if ( arguments.length < setter ) { 234 | return jQuery.queue( this[0], type ); 235 | } 236 | 237 | return data === undefined ? 238 | this : 239 | //this.each说明this匹配了多个dom对象,因此要对每一个对象进行queue 240 | this.each(function() { 241 | var queue = jQuery.queue( this, type, data ); 242 | 243 | // ensure a hooks for this queue 244 | jQuery._queueHooks( this, type ); 245 | 246 | 247 | //$().animate()第一次需要自执行时会满足条件进来 248 | if ( type === "fx" && queue[0] !== "inprogress" ) { 249 | jQuery.dequeue( this, type ); 250 | } 251 | }); 252 | }, 253 | dequeue: function( type ) { 254 | return this.each(function() { 255 | jQuery.dequeue( this, type ); 256 | }); 257 | }, 258 | // Based off of the plugin by Clint Helfers, with permission. 259 | // http://blindsignals.com/index.php/2009/07/jquery-delay/ 260 | 261 | 262 | 263 | /* 264 | [8569] 265 | jQuery.fx.speeds = { 266 | slow: 600, 267 | fast: 200, 268 | // Default speed 269 | _default: 400 270 | }; 271 | */ 272 | delay: function( time, type ) { 273 | time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; 274 | type = type || "fx"; 275 | 276 | //需要注意data传入的参数就是函数function(next,hoos)类似于fn1 fn2... 277 | return this.queue( type, function( next, hooks ) { 278 | var timeout = setTimeout( next, time ); 279 | hooks.stop = function() { 280 | clearTimeout( timeout ); 281 | }; 282 | }); 283 | }, 284 | 285 | //清空队列 286 | clearQueue: function( type ) { 287 | return this.queue( type || "fx", [] ); 288 | }, 289 | // Get a promise resolved when queues of a certain type 290 | // are emptied (fx is the type by default) 291 | promise: function( type, obj ) { 292 | var tmp, 293 | count = 1, 294 | defer = jQuery.Deferred(), 295 | elements = this, 296 | i = this.length, 297 | resolve = function() { 298 | //一个队列执行完毕就count-- 299 | if ( !( --count ) ) { 300 | //如果队列执行完毕就可以resoveWith触发外部的done函数 301 | //详见(四) 302 | defer.resolveWith( elements, [ elements ] ); 303 | } 304 | }; 305 | 306 | if ( typeof type !== "string" ) { 307 | obj = type; 308 | type = undefined; 309 | } 310 | type = type || "fx"; 311 | 312 | while( i-- ) { 313 | //获取所有所有element的queue队列 314 | tmp = data_priv.get( elements[ i ], type + "queueHooks" ); 315 | 316 | if ( tmp && tmp.empty ) { 317 | //count++表明所有的队列++ 318 | count++; 319 | //添加resolve函数 320 | //在dequeue中如果一个queue执行完毕会fire 321 | tmp.empty.add( resolve ); 322 | } 323 | } 324 | resolve(); 325 | return defer.promise( obj ); 326 | } 327 | }); 328 | ``` 329 | 330 | 331 | >内容解析 332 | 333 | (一) `$().queue()` 334 | 335 | 336 | ``` 337 | function fn1() { 338 | console.log("this is fn1..."); 339 | } 340 | 341 | function fn2() { 342 | console.log("this is fn2..."); 343 | } 344 | 345 | $(document).queue('fn',fn1); 346 | $(document).queue('fn',fn1); 347 | $(document).queue('fn',fn2); 348 | 349 | $(document).queue(fn2); //this is fn2 直接duqueue了! 保证第一次自执行? 350 | //animate方法的自执行操作? 351 | ``` 352 | 353 | 354 | (二) `$().dequeue()` 355 | 356 | ``` javascript 357 | function fn1(next,hooks) { 358 | console.log("this is fn1..."); 359 | next(); //相当于fn1执行完了才可以执行next(), 如果fn1是异步函数则控制了异步的行为按顺序执行了 360 | } 361 | 362 | function fn2() { 363 | console.log("this is fn2..."); 364 | } 365 | 366 | $(document).queue('fn',fn1); 367 | $(document).queue('fn',fn1); 368 | $(document).queue('fn',fn2); 369 | 370 | $(document).dequeue('fn'); //fn1 -> next() -> fn1 -> next() -> fn2 371 | //this is fn1 372 | //this is fn1 373 | //this is fn2 374 | ``` 375 | 376 | 377 | (三) `$().delay()` 378 | 379 | ``` javascript 380 | $('#div1').animate({left:'200px'}).delay(2000).animate({left:'0'}) 381 | ``` 382 | 383 | (四) `$().promise()` 384 | 385 | ``` javascript 386 | $('#div1').animate({left:'200px'}).delay(2000).animate({left:'0'}) 387 | 388 | $('#div1').promise().done(function() { 389 | alert("运动执行完毕!"); 390 | }) 391 | ``` --------------------------------------------------------------------------------