├── 05-Sizzle-select ├── jQuerySource.js └── README.md ├── 06-Sizzle-compile ├── jQuerySource.js └── README.md ├── 09-prevObject ├── p1.png └── README.md ├── 11-event-main ├── event.jpg └── README.md ├── 03-Sizzle ├── jQuerySource.js └── README.md ├── 04-Sizzle-Tokens ├── jQuerySource.js └── README.md ├── README.md ├── 01-总体架构 ├── jQuerySource.js └── README.md ├── 02-init构造器 ├── jQuerySource.js └── README.md ├── 16-dom-html └── README.md ├── 14-event-trigger └── README.md ├── 12-event-extend └── README.md ├── 18-class └── README.md ├── 10-hooks └── README.md ├── 17-css └── README.md ├── 07-Callbacks └── README.md ├── 15-dom-domManip └── README.md ├── 13-event-on └── README.md ├── 08-Data └── README.md └── 19-ajax └── README.md /05-Sizzle-select/jQuerySource.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /06-Sizzle-compile/jQuerySource.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /09-prevObject/p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/songjinzhong/JQuerySource/HEAD/09-prevObject/p1.png -------------------------------------------------------------------------------- /11-event-main/event.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/songjinzhong/JQuerySource/HEAD/11-event-main/event.jpg -------------------------------------------------------------------------------- /03-Sizzle/jQuerySource.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery.fn.pushStack 3 | * 4 | */ 5 | jQuery.fn.pushStack = function (elems) { 6 | 7 | // Build a new jQuery matched element set 8 | var ret = jQuery.merge(this.constructor(), elems); 9 | 10 | // Add the old object onto the stack (as a reference) 11 | ret.prevObject = this; 12 | 13 | // Return the newly-formed element set 14 | return ret; 15 | } 16 | 17 | /* 18 | * jQuery.contains 19 | */ 20 | jQuery.contains = function (context, elem) { 21 | // Set document vars if needed 22 | if ((context.ownerDocument || context) !== document) { 23 | setDocument(context); 24 | } 25 | return contains(context, elem); 26 | } 27 | 28 | /* 29 | * contains 30 | */ 31 | var contains = function (a, b) { 32 | var adown = a.nodeType === 9 ? a.documentElement : a, 33 | bup = b && b.parentNode; 34 | return a === bup || !!(bup && bup.nodeType === 1 && ( 35 | adown.contains ? adown.contains(bup) : a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16)); 36 | } 37 | 38 | /* 39 | * jQuery.uniqueSort 40 | */ 41 | jQuery.uniqueSort = function (results) { 42 | var elem, duplicates = [], 43 | j = 0, 44 | i = 0; 45 | 46 | // Unless we *know* we can detect duplicates, assume their presence 47 | hasDuplicate = !support.detectDuplicates; 48 | sortInput = !support.sortStable && results.slice(0); 49 | results.sort(sortOrder); 50 | 51 | if (hasDuplicate) { 52 | while ((elem = results[i++])) { 53 | if (elem === results[i]) { 54 | j = duplicates.push(i); 55 | } 56 | } 57 | while (j--) { 58 | results.splice(duplicates[j], 1); 59 | } 60 | } 61 | 62 | // Clear input after sorting to release objects 63 | // See https://github.com/jquery/sizzle/pull/225 64 | sortInput = null; 65 | 66 | return results; 67 | } 68 | 69 | /* 70 | * push 71 | */ 72 | var push = arr.push; 73 | -------------------------------------------------------------------------------- /04-Sizzle-Tokens/jQuerySource.js: -------------------------------------------------------------------------------- 1 | /* 2 | * some regex 3 | */ 4 | var 5 | booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", 6 | 7 | // Regular expressions 8 | 9 | // http://www.w3.org/TR/css3-selectors/#whitespace 10 | whitespace = "[\\x20\\t\\r\\n\\f]", 11 | 12 | // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier 13 | identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", 14 | 15 | // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors 16 | attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + 17 | // Operator (capture 2) 18 | "*([*^$|!~]?=)" + whitespace + 19 | // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" 20 | "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + 21 | "*\\]", 22 | 23 | pseudos = ":(" + identifier + ")(?:\\((" + 24 | // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: 25 | // 1. quoted (capture 3; capture 4 or capture 5) 26 | "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + 27 | // 2. simple (capture 6) 28 | "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + 29 | // 3. anything else (capture 2) 30 | ".*" + 31 | ")\\)|)", 32 | 33 | // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter 34 | rwhitespace = new RegExp( whitespace + "+", "g" ), 35 | rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), 36 | 37 | rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), 38 | rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), 39 | 40 | rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), 41 | 42 | rpseudo = new RegExp( pseudos ), 43 | ridentifier = new RegExp( "^" + identifier + "$" ), 44 | 45 | matchExpr = { 46 | "ID": new RegExp( "^#(" + identifier + ")" ), 47 | "CLASS": new RegExp( "^\\.(" + identifier + ")" ), 48 | "TAG": new RegExp( "^(" + identifier + "|[*])" ), 49 | "ATTR": new RegExp( "^" + attributes ), 50 | "PSEUDO": new RegExp( "^" + pseudos ), 51 | "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + 52 | "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + 53 | "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), 54 | "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), 55 | // For use in libraries implementing .is() 56 | // We use this for POS matching in `select` 57 | "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + 58 | whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) 59 | }, 60 | 61 | rinputs = /^(?:input|select|textarea|button)$/i, 62 | rheader = /^h\d$/i, 63 | 64 | rnative = /^[^{]+\{\s*\[native \w/, 65 | 66 | // Easily-parseable/retrievable ID or TAG or CLASS selectors 67 | rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, 68 | 69 | rsibling = /[+~]/, 70 | 71 | // CSS escapes 72 | // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters 73 | runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuerySource 2 | 决定你走多远的是基础,jQuery 源码分析,向长者膜拜! 3 | 4 | 关于本源码的博客介绍:[标签 jQuery](http://yuren.space/blog/tags/jQuery/)。 5 | 6 | ## 目录 7 | 8 | - Directory 9 | + [x] [01-总体架构](https://github.com/songjinzhong/JQuerySource/tree/master/01-%E6%80%BB%E4%BD%93%E6%9E%B6%E6%9E%84)- 只有弄懂整体架构,后面的学习才好办 10 | + [x] [02-init 构造器](https://github.com/songjinzhong/JQuerySource/tree/master/02-init%E6%9E%84%E9%80%A0%E5%99%A8)- 介绍 jQuery 的入口函数 init 的构造 11 | + [x] [03-Sizzle](https://github.com/songjinzhong/JQuerySource/tree/master/03-Sizzle)- Sizzle 再 jQuery 中的应用 12 | + [x] [04-Sizzle-Tokens](https://github.com/songjinzhong/JQuerySource/tree/master/04-Sizzle-Tokens)- 介绍 Sizzle 函数中的词法分析,即 tokensize 函数 13 | + [x] [05-Sizzle-select](https://github.com/songjinzhong/JQuerySource/tree/master/05-Sizzle-select)- 介绍 Sizzle 中的 select 函数,对词法分析的结果进行处理,生成 seed 14 | + [x] [06-Sizzle-compile](https://github.com/songjinzhong/JQuerySource/tree/master/06-Sizzle-compile)- 介绍 Sizzle 中的 compile 函数,用于生成 superMatcher,并通过 superMatcher,匹配 seed 15 | + [x] [07-Callbacks](https://github.com/songjinzhong/JQuerySource/tree/master/07-Callbacks)- 这里介绍 jQuery 中的回调函数 Callbacks 16 | + [x] [08-Data](https://github.com/songjinzhong/JQuerySource/tree/master/08-Data)- jQuery 中的缓存机制是什么样的,为什么不回造成内存泄漏,一起来见识见识 17 | + [x] [09-prevObject](https://github.com/songjinzhong/JQuerySource/tree/master/09-prevObject)- 你知道吗,jQuery 内部维护着一个对象栈 18 | + [x] [10-hooks](https://github.com/songjinzhong/JQuerySource/tree/master/10-hooks)- jQuery 里的一些原理,你不了解,就会觉得恐惧,当你熟悉了之后,就会发现非常有意思,比如 Hooks 19 | + [x] [11-event-main](https://github.com/songjinzhong/JQuerySource/tree/master/11-event-main)- 事件委托算是 jQuery 中被广泛使用一个性能较好的辅助函数,那么,它是一个什么样的架构 20 | + [x] [12-event-extend](https://github.com/songjinzhong/JQuerySource/tree/master/12-event-extend)- 还是回头去把一些基础扎实一点,才去解读源码吧 21 | + [x] [13-event-on](https://github.com/songjinzhong/JQuerySource/tree/master/13-event-on)- 分析一下 event 对象中两个重要的函数,add 和 dispatch 22 | + [x] [14-event-trigger](https://github.com/songjinzhong/JQuerySource/tree/master/14-event-trigger)- jQuery 也提供自定义事件 trigger 和 triggerHandler 23 | + [x] [15-dom-domManip](https://github.com/songjinzhong/JQuerySource/tree/master/15-dom-domManip)- 长久以来,jQuery 中 dom 操作就被人喜爱 24 | + [x] [16-dom-html](https://github.com/songjinzhong/JQuerySource/tree/master/16-dom-html)- 介绍一下 html 和 text 操作 25 | + [x] [17-css](https://github.com/songjinzhong/JQuerySource/tree/master/17-css)- css 在 jQuery 中的使用频率很高的 26 | + [x] [18-class](https://github.com/songjinzhong/JQuerySource/tree/master/18-class)- jQuery 中的 class 操作,包括 add,remove,has 和 toggle 27 | + [x] [19-ajax](https://github.com/songjinzhong/JQuerySource/tree/master/19-ajax)- jQuery 中的 ajax 方法介绍 28 | 29 | 我虽然接触 jQuery 很久了,但也只是局限于表面使用的层次,碰到一些问题,找到 jQuery 的解决办法,然后使用。**显然**,这种做法的弊端就是,无论你怎么学,都只能是个小白。 30 | 31 | 当我建立这个项目的时候,就表示,我要改变这一切了,做一些人想做,憧憬去做,但从没踏入第一步的事情,学习 jQuery 源码。 32 | 33 | 到目前为止,jQuery 的[贡献者团队](https://github.com/jquery/jquery)共 256 名成员,6000 多条 commits,可想而知,jQuery 是一个多么庞大的项目。jQuery [官方](https://jquery.com/)的版本目前是 v3.1.1,已经衍生出 jQueryUI、jQueryMobile 等多个项目。 34 | 35 | 虽然我在前端爬摸打滚一年多,自认基础不是很好,在没有外界帮助的情况下,直接阅读项目源码太难了,所以在边参考遍实践的过程中写下来这个项目。 36 | 37 | **首先**,先推荐一个 jQuery 的[源码查询](http://james.padolsey.com/jquery/)网站,这个网站给初学者非常大的帮助,不仅能查找不同版本的 jQuery 源码,还能索引函数,功能简直吊炸天。 38 | 39 | 另外,推荐两个分析 jQuery 的博客: 40 | 41 | >[jQuery源码分析系列](http://www.cnblogs.com/aaronjs/p/3279314.html) 42 | 43 | >[[原创] jQuery1.6.1源码分析系列(停止更新)](http://www.cnblogs.com/nuysoft/archive/2011/11/14/2248023.html) 44 | 45 | 这两个博客给我了很大的帮助,谢谢。 46 | 47 | 另外还有下面的网址,让我在如何使用 jQuery 上得心应手: 48 | 49 | >[jQuery API 中文文档](http://www.css88.com/jqapi-1.9/) -------------------------------------------------------------------------------- /09-prevObject/README.md: -------------------------------------------------------------------------------- 1 | 学习了 prevObject 之后发现,我之前写的一篇博客介绍 pushStack 函数那个内容是有问题的。本来我以为这个 pushStack 函数就是一个普通的函数,它接受一个 DOM (数组)参数,把该参数合并到一个 jQuery 对象中并返回该 jQuery 对象。 2 | 3 | 后来我也疑惑过一段时间,为什么看不到这个函数的使用,而且为什么要把它放到 jQuery.fn 上,直到今天,才恍然大悟。 4 | 5 | ## jQuery 的 prevObject 对象 6 | 7 | 当我们新建一个 jQuery 对象的时候,都会在其属性中发现一个 `prevObject` 的属性,如果单单从名字来看的,“前一个对象”,那么它到底是怎么用的呢。 8 | 9 | ![p1](p1.png) 10 | 11 | 在 jQuery 对象的内部,有着一个 jQuery 对象栈,用来维护所有已经操作过的 jQuery 对象。这样子的话可以写出很流畅的 jQuery 代码,比如操作父元素,操作子元素,再回到父元素的时候就很方便。 12 | 13 | 但实际上,这个栈是不存在的,我们知道数组可以当作栈或队列来使用,是不是说 jQuery 内部有这么个数组来存放这个对象栈呢。答案是 no,为什么,因为没必要这么麻烦。**这样来想一想,如果每一个 jQuery 对象都有一个指针指向上一个对象的话,也就间接组成了一个 jQuery 对象栈。**如果栈只有一个元素,prevObject 就默认指向 `document`。prevObject 是干什么用的,就是来实现对象栈的。比如: 14 | 15 | ```html 16 |
17 |

标题

18 |

container

19 | 重点 20 |
21 | ``` 22 | 23 | 对于上面的 html: 24 | 25 | ```javascript 26 | var $view = $("#view"); 27 | // $header 是由 $view 操作得到的 28 | var $header = $view.find('.header'); 29 | $header.prevObject === $view; // true 30 | ``` 31 | 32 | 不过在使用的时候,都是忽略对象栈而定义不同的 jQuery 对象来指向父元素和子元素,就像上面的例子那样,既然定义了 $view 和 $header,就不需要 prevObject 了。 33 | 34 | ## jQuery.fn.end 和 jQuery.fn.addBack 35 | 36 | 这两个函数其实就是 prevObject 的应用,举个例子就能弄明白了,仍然是上面的那个 html: 37 | 38 | ```javascript 39 | $('#view').find('.header').css({'color': 'red'}) 40 | .end() 41 | .find(.espe).css({'color': 'white'}) 42 | ``` 43 | 44 | 加了 `end`之后,当前执行的 jQuery 对象就变成 `$('#view')`了,所以可以继续执行 find 操作等等,如果不加的话,这段话是不能执行成功的。可见,如果这样子写 jQuery 不仅写法优雅,而且还很高效。 45 | 46 | end 和 addBack 是有区别的,`end()` 函数只是单纯的进行出栈操作,并返回出栈的这个 jQuery 对象,而 `addBack()` 函数不会执行出栈,而是把栈顶对象和当前的对象组成一个新对象,入栈: 47 | 48 | ```javascript 49 | $('#view').find('.header').nextAll() 50 | .addBack() 51 | .css({'color': 'red'}) // 颜色全红 52 | ``` 53 | 54 | 上面的代码,会使得 #view 的三个子元素的颜色都设置为红色。 55 | 56 | ## 相关函数源码 57 | 58 | 重新来看下 pushStack 函数吧,这个函数不仅在 `fn.find()` 函数中出现,好多涉及 jQuery dom 操作的原型函数都出现了,而且[之前介绍](https://github.com/songjinzhong/JQuerySource/tree/master/03-Sizzle#jqueryfnpushstack)的时候,忽略了一个重要的部分 prevObject 对象: 59 | 60 | ```javascript 61 | jQuery.fn.pushStack = function (elems) { 62 | 63 | // 将 elems 合并到 jQuery 对象中 64 | var ret = jQuery.merge(this.constructor(), elems); 65 | 66 | // 实现对象栈 67 | ret.prevObject = this; 68 | 69 | // 返回 70 | return ret; 71 | } 72 | ``` 73 | 74 | `pushStack` 生成了一个新 jQuery 对象 `ret`,`ret` 的 prevObject 属性是指向调用 pushStack 函数的那个 jQuery 对象的,这样就形成了一个栈链,其它原型方法如 find、nextAll、filter 等都可以调用 pushStack 函数,返回一个新的 jQuery 对象并维持对象栈。 75 | 76 | 我们知道了所有 jQuery 方法都有一个 prevObject 属性的情况下,来看看 end 方法: 77 | 78 | ```javascript 79 | jQuery.fn.end = function () { 80 | return this.prevObject || this.constructor(); 81 | } 82 | ``` 83 | 84 | 还有 addBack 方法: 85 | 86 | ```javascript 87 | jQuery.fn.addBack = function (selector) { 88 | // 可以看出有参数的 addBack 会对 prevObject 进行过滤 89 | return this.add(selector == null ? this.prevObject : this.prevObject.filter(selector)); 90 | } 91 | ``` 92 | 93 | 里面穿插了一个 `fn.add` 方法: 94 | 95 | ```javascript 96 | jQuery.fn.add = function (selector, context) { 97 | return this.pushStack( 98 | jQuery.uniqueSort( 99 | jQuery.merge(this.get(), jQuery(selector, context)))); 100 | } 101 | ``` 102 | 103 | 应该不需要解释吧,源码一清二楚。 104 | 105 | ## 总结 106 | 107 | 我之前就已经说过,很少会使用 end 或 pushStack 方法,而在 jQuery 内部的原型方法中,比如 find、nextAll、filter 等,被频繁使用,这种封装一个 jQuery 的方法很棒,会把对象栈给维护得很好。无论如何,这就是 jQuery 的魅力吧! 108 | 109 | ## 参考 110 | 111 | >[jQuery 2.0.3 源码分析 回溯魔法 end()和pushStack()](http://www.cnblogs.com/aaronjs/p/3387278.html) 112 | 113 | >[jQuery API .end()](http://www.css88.com/jqapi-1.9/end/) 114 | 115 | >[jQuery API .addBack()](http://www.css88.com/jqapi-1.9/addBack/) -------------------------------------------------------------------------------- /01-总体架构/jQuerySource.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery 建立方法 3 | * 4 | */ 5 | 6 | var jQuery = function (selector, context) { 7 | 8 | // The jQuery object is actually just the init constructor 'enhanced' 9 | // Need init if jQuery is called (just allow error to be thrown if not included) 10 | return new jQuery.fn.init(selector, context); 11 | } 12 | 13 | /* 14 | * jQuery.extend 15 | * jQuery.fn.extend 16 | * 17 | */ 18 | 19 | function () { 20 | var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, 21 | i = 1, 22 | length = arguments.length, 23 | deep = false; 24 | 25 | // Handle a deep copy situation 26 | if (typeof target === "boolean") { 27 | deep = target; 28 | 29 | // Skip the boolean and the target 30 | target = arguments[i] || {}; 31 | i++; 32 | } 33 | 34 | // Handle case when target is a string or something (possible in deep copy) 35 | if (typeof target !== "object" && !jQuery.isFunction(target)) { 36 | target = {}; 37 | } 38 | 39 | // Extend jQuery itself if only one argument is passed 40 | if (i === length) { 41 | target = this; 42 | i--; 43 | } 44 | 45 | for (; i < length; i++) { 46 | 47 | // Only deal with non-null/undefined values 48 | if ((options = arguments[i]) != null) { 49 | 50 | // Extend the base object 51 | for (name in options) { 52 | src = target[name]; 53 | copy = options[name]; 54 | 55 | // Prevent never-ending loop 56 | if (target === copy) { 57 | continue; 58 | } 59 | 60 | // Recurse if we're merging plain objects or arrays 61 | if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) { 62 | 63 | if (copyIsArray) { 64 | copyIsArray = false; 65 | clone = src && Array.isArray(src) ? src : []; 66 | 67 | } else { 68 | clone = src && jQuery.isPlainObject(src) ? src : {}; 69 | } 70 | 71 | // Never move original objects, clone them 72 | target[name] = jQuery.extend(deep, clone, copy); 73 | 74 | // Don't bring in undefined values 75 | } else if (copy !== undefined) { 76 | target[name] = copy; 77 | } 78 | } 79 | } 80 | } 81 | 82 | // Return the modified object 83 | return target; 84 | } 85 | 86 | /* 87 | * jQuery.isFunction 88 | * 89 | */ 90 | 91 | function (obj) { 92 | return jQuery.type(obj) === "function"; 93 | } 94 | 95 | /* 96 | * jQuery.type 97 | * 98 | */ 99 | 100 | var class2type = { 101 | "[object Boolean]": "boolean", 102 | "[object Number]": "number", 103 | "[object String]": "string", 104 | "[object Function]": "function", 105 | "[object Array]": "array", 106 | "[object Date]": "date", 107 | "[object RegExp]": "regexp", 108 | "[object Object]": "object", 109 | "[object Error]": "error", 110 | "[object Symbol]": "symbol" 111 | } 112 | 113 | function (obj) { 114 | if (obj == null) { 115 | return obj + ""; 116 | } 117 | 118 | // Support: Android <=2.3 only (functionish RegExp) 119 | return typeof obj === "object" || typeof obj === "function" ? class2type[toString.call(obj)] || "object" : typeof obj; 120 | } 121 | 122 | /* 123 | * jQuery.isPlainObject 124 | * 125 | */ 126 | 127 | // Object.getPrototypeOf 用来返回元素继承的父元素 128 | var getProto = Object.getPrototypeOf; 129 | 130 | var hasOwn = class2type.hasOwnProperty; 131 | 132 | var fnToString = hasOwn.toString; 133 | 134 | var ObjectFunctionString = fnToString.call( Object ); 135 | 136 | function (obj) { 137 | var proto, Ctor; 138 | 139 | // Detect obvious negatives 140 | // Use toString instead of jQuery.type to catch host objects 141 | if (!obj || toString.call(obj) !== "[object Object]") { 142 | return false; 143 | } 144 | 145 | proto = getProto(obj); 146 | 147 | // Objects with no prototype (e.g., `Object.create( null )`) are plain 148 | if (!proto) { 149 | return true; 150 | } 151 | 152 | // Objects with prototype are plain iff they were constructed by a global Object function 153 | Ctor = hasOwn.call(proto, "constructor") && proto.constructor; 154 | return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString; 155 | } -------------------------------------------------------------------------------- /02-init构造器/jQuerySource.js: -------------------------------------------------------------------------------- 1 | /* 2 | * rootjQuery 3 | */ 4 | var rootjQuery = jQuery( document ); 5 | 6 | /* 7 | * rquickExpr 8 | */ 9 | var rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/; 10 | 11 | /* 12 | * jQuery.merge 13 | * 14 | */ 15 | 16 | jQuery.merge = function (first, second) { 17 | var len = +second.length, 18 | j = 0, 19 | i = first.length; 20 | 21 | for (; j < len; j++) { 22 | first[i++] = second[j]; 23 | } 24 | 25 | first.length = i; 26 | 27 | return first; 28 | } 29 | 30 | /* 31 | * jQuery.parseHTML 32 | * 33 | */ 34 | jQuery.parseHTML = function (data, context, keepScripts) { 35 | if (typeof data !== "string") { 36 | return []; 37 | } 38 | if (typeof context === "boolean") { 39 | keepScripts = context; 40 | context = false; 41 | } 42 | 43 | var base, parsed, scripts; 44 | 45 | if (!context) { 46 | 47 | // Stop scripts or inline event handlers from being executed immediately 48 | // by using document.implementation 49 | if (support.createHTMLDocument) { 50 | context = document.implementation.createHTMLDocument(""); 51 | 52 | // Set the base href for the created document 53 | // so any parsed elements with URLs 54 | // are based on the document's URL (gh-2965) 55 | base = context.createElement("base"); 56 | base.href = document.location.href; 57 | context.head.appendChild(base); 58 | } else { 59 | context = document; 60 | } 61 | } 62 | 63 | parsed = rsingleTag.exec(data); 64 | scripts = !keepScripts && []; 65 | 66 | // Single tag 67 | if (parsed) { 68 | return [context.createElement(parsed[1])]; 69 | } 70 | 71 | parsed = buildFragment([data], context, scripts); 72 | 73 | if (scripts && scripts.length) { 74 | jQuery(scripts).remove(); 75 | } 76 | 77 | return jQuery.merge([], parsed.childNodes); 78 | } 79 | 80 | /* 81 | * rsingleTag 82 | */ 83 | 84 | var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); 85 | 86 | /* 87 | * jQuery.attr 88 | * 89 | */ 90 | jQuery.attr = function (elem, name, value) { 91 | var ret, hooks, nType = elem.nodeType; 92 | 93 | // Don't get/set attributes on text, comment and attribute nodes 94 | if (nType === 3 || nType === 8 || nType === 2) { 95 | return; 96 | } 97 | 98 | // Fallback to prop when attributes are not supported 99 | if (typeof elem.getAttribute === "undefined") { 100 | return jQuery.prop(elem, name, value); 101 | } 102 | 103 | // Attribute hooks are determined by the lowercase version 104 | // Grab necessary hook if one is defined 105 | if (nType !== 1 || !jQuery.isXMLDoc(elem)) { 106 | hooks = jQuery.attrHooks[name.toLowerCase()] || (jQuery.expr.match.bool.test(name) ? boolHook : undefined); 107 | } 108 | 109 | if (value !== undefined) { 110 | if (value === null) { 111 | jQuery.removeAttr(elem, name); 112 | return; 113 | } 114 | 115 | if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) { 116 | return ret; 117 | } 118 | 119 | elem.setAttribute(name, value + ""); 120 | return value; 121 | } 122 | 123 | if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) { 124 | return ret; 125 | } 126 | 127 | ret = jQuery.find.attr(elem, name); 128 | 129 | // Non-existent attributes return null, we normalize to undefined 130 | return ret == null ? undefined : ret; 131 | } 132 | 133 | /* 134 | * jQueyr.makeArray 135 | * 136 | */ 137 | jQuery.makeArray = function (arr, results) { 138 | var ret = results || []; 139 | 140 | if (arr != null) { 141 | if (isArrayLike(Object(arr))) { 142 | jQuery.merge(ret, typeof arr === "string" ? [arr] : arr); 143 | } else { 144 | push.call(ret, arr); 145 | } 146 | } 147 | 148 | return ret; 149 | } 150 | 151 | /* 152 | * _isAyyayLike 153 | */ 154 | function isArrayLike(obj) { 155 | 156 | // Support: real iOS 8.2 only (not reproducible in simulator) 157 | // `in` check used to prevent JIT error (gh-2145) 158 | // hasOwn isn't used here due to false negatives 159 | // regarding Nodelist length in IE 160 | var length = !!obj && "length" in obj && obj.length, 161 | type = jQuery.type(obj); 162 | 163 | if (type === "function" || jQuery.isWindow(obj)) { 164 | return false; 165 | } 166 | 167 | return type === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj; 168 | } -------------------------------------------------------------------------------- /11-event-main/README.md: -------------------------------------------------------------------------------- 1 | 这次的内容是来介绍关于 jQuery 的事件委托。不过在之前呢有必要先来了解一下 JS 中的事件委托与冒泡,我之前也写过类似的博客,[事件冒泡与捕获](http://yuren.space/blog/2016/10/16/%E4%BA%8B%E4%BB%B6%E5%86%92%E6%B3%A1%E4%B8%8E%E6%8D%95%E8%8E%B7/) 2 | 3 | ## 由 JS 事件引入 4 | 5 | 事件是 JS DOM 中极具活力的内容,你可以随时监听 DOM 的变化,并对它们及时的做出反应,如果你不是太懂 JS 中的事件,建议你先去看一些相关介绍的文章,直接看 jQuery 中的事件委托头会头大的。 6 | 7 | 事件的处理顺序由两个环节,一个是捕获环节,一个是冒泡环节,借用别人的一张图: 8 | 9 | ![事件冒泡与捕获](event.jpg) 10 | 11 | 如果把处理也算进的话,整个事件分为三个阶段,分别是捕获阶段,目标处理阶段和冒泡阶段。捕获阶段由外向内寻找 target,冒泡阶段由内向外直到根结点。这只是一个事件,当这三个阶段中又穿插着更多的事件时,还需要将事件的执行顺序考虑进去。 12 | 13 | 而 jQuery 事件委托的概念:事件目标自身不处理事件,而是将其委托给父元素或祖先元素或根元素,而借助事件的**冒泡性质**(由内向外)来达到最终处理事件。 14 | 15 | ## jQuery 中的事件优化 16 | 17 | 首先必须要知道,绑定事件越多,浏览器内存占用越大,就会间接的影响性能。而且一旦出现 ajax,局部刷新导致重新绑定事件。 18 | 19 | 使用事件委托可以解决以上带来的问题,借助事件的冒泡,尤其当一个父元素的子元素过多,而且子元素绑定的事件非常多时,委托事件的作用就体现出来了。 20 | 21 | 我本人不善于比较 JS 中的性能问题,感兴趣的可以去看看这篇文章关于事件委托性能的设计和比较。[深入理解-事件委托](https://gold.xitu.io/entry/5896d04d61ff4b006b0e337a)。 22 | 23 | 在早期的 jQuery 版本,使用的是 `.delegate()`、`.bind()`、`.live()`等方法来实现事件监听,当然也包括`.click()`方法,随着 jQuery 的发展,像 live 方法已经明确从 jQuery 中删除,而其余的方法,比如 bind 方法也将在 3.0 之后的版本陆续删除,取而代之的是 `.on()`方法。而且剩下的其它方法都是通过 on 方法来间接实现的,如果介绍,只需要看 on 的源码即可。 24 | 25 | on 函数在 jQuery 中的用法也很简单,`.on( events [, selector ] [, data ], handler(eventObject) )`events 表示绑定的事件,比如 "click" 或 "click mouseleave",selector 和 data 是可选的,分别表示要绑定事件的元素和要执行的数据,handler 表示事件执行函数。 26 | 27 | off 函数的用法 `.off( events [, selector ] [, handler ] )`,events 代表要移除的事件,selector 表示选择的 dom,handler 表示事件处理函数。还有更残暴的比如 `.off()`不接受任何参数,表示着移除所有 on 绑定的函数。 28 | 29 | ## on off 函数源码 30 | 31 | 虽然我分析的源码时 jQuery 3.1.1,但这个时候 bind 和 delegate 函数并没有从源码中移除呢,先来看看它们怎么调用 on: 32 | 33 | ```javascript 34 | jQuery.fn.extend( { 35 | bind: function( types, data, fn ) { 36 | return this.on( types, null, data, fn ); 37 | }, 38 | unbind: function( types, fn ) { 39 | return this.off( types, null, fn ); 40 | }, 41 | delegate: function( selector, types, data, fn ) { 42 | return this.on( types, selector, data, fn ); 43 | }, 44 | undelegate: function( selector, types, fn ) { 45 | // ( namespace ) or ( selector, types [, fn] ) 46 | return arguments.length === 1 ? 47 | this.off( selector, "**" ) : 48 | this.off( types, selector || "**", fn ); 49 | } 50 | } ); 51 | ``` 52 | 53 | 可以看得出来,全都被 on 和 off 这两个函数来处理了。 54 | 55 | ```javascript 56 | jQuery.fn.extend( { 57 | on: function (types, selector, data, fn) { 58 | // on 又依托于全局的 on 函数 59 | return on(this, types, selector, data, fn); 60 | } 61 | } ); 62 | function on( elem, types, selector, data, fn, one ) { 63 | var origFn, type; 64 | 65 | // 支持 object 的情况 66 | if ( typeof types === "object" ) { 67 | 68 | // ( types-Object, selector, data ) 69 | if ( typeof selector !== "string" ) { 70 | 71 | // ( types-Object, data ) 72 | data = data || selector; 73 | selector = undefined; 74 | } 75 | // 一次执行 object 的每一个 76 | for ( type in types ) { 77 | on( elem, type, selector, data, types[ type ], one ); 78 | } 79 | return elem; 80 | } 81 | // 参数为两个的情况 82 | if ( data == null && fn == null ) { 83 | 84 | // ( types, fn ) 85 | fn = selector; 86 | data = selector = undefined; 87 | } else if ( fn == null ) { 88 | if ( typeof selector === "string" ) { 89 | 90 | // ( types, selector, fn ) 91 | fn = data; 92 | data = undefined; 93 | } else { 94 | 95 | // ( types, data, fn ) 96 | fn = data; 97 | data = selector; 98 | selector = undefined; 99 | } 100 | } 101 | if ( fn === false ) { 102 | // returnFalse 是一个返回 false 的函数 103 | fn = returnFalse; 104 | } else if ( !fn ) { 105 | return elem; 106 | } 107 | 108 | if ( one === 1 ) { 109 | origFn = fn; 110 | fn = function( event ) { 111 | 112 | // Can use an empty set, since event contains the info 113 | jQuery().off( event ); 114 | return origFn.apply( this, arguments ); 115 | }; 116 | 117 | // Use same guid so caller can remove using origFn 118 | fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); 119 | } 120 | return elem.each( function() { 121 | // 关键 122 | jQuery.event.add( this, types, fn, data, selector ); 123 | } ); 124 | } 125 | ``` 126 | 127 | 是的,你没有看错,这个全局的 on 函数,其实只是起到了校正参数的作用,而真正的大头是: 128 | 129 | ```javascript 130 | jQuery.event = { 131 | global = {}, 132 | add: function(){...}, 133 | remove: function(){...}, 134 | dispatch: function(){...}, 135 | handlers: function(){...}, 136 | addProp: function(){...}, 137 | fix: function(){...}, 138 | special: function(){...} 139 | } 140 | ``` 141 | 142 | off 函数: 143 | 144 | ```javascript 145 | jQuery.fn.off = function (types, selector, fn) { 146 | var handleObj, type; 147 | if (types && types.preventDefault && types.handleObj) { 148 | // ( event ) dispatched jQuery.Event 149 | handleObj = types.handleObj; 150 | jQuery(types.delegateTarget).off( 151 | handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, 152 | handleObj.selector, 153 | handleObj.handler 154 | ); 155 | return this; 156 | } 157 | if (typeof types === "object") { 158 | // ( types-object [, selector] ) 159 | for (type in types) { 160 | this.off(type, selector, types[type]); 161 | } 162 | return this; 163 | } 164 | if (selector === false || typeof selector === "function") { 165 | // ( types [, fn] ) 166 | fn = selector; 167 | selector = undefined; 168 | } 169 | if (fn === false) { 170 | fn = returnFalse; 171 | } 172 | return this.each(function() { 173 | // 关键 174 | jQuery.event.remove(this, types, fn, selector); 175 | }); 176 | } 177 | ``` 178 | 179 | ## 总结 180 | 181 | 可见 jQuery 对于参数的放纵导致其处理起来非常复杂,不过对于使用者来说,却非常大便利。 182 | 183 | 委托事件也带来了一些不足,比如一些事件无法冒泡,load、submit 等,会加大管理等复杂,不好模拟用户触发事件等。 184 | 185 | ## 参考 186 | 187 | >[jQuery 2.0.3 源码分析 事件绑定 - bind/live/delegate/on](http://www.cnblogs.com/aaronjs/p/3440647.html) 188 | 189 | >[深入理解-事件委托](https://gold.xitu.io/entry/5896d04d61ff4b006b0e337a) 190 | 191 | >[.on()](http://www.css88.com/jqapi-1.9/on/) -------------------------------------------------------------------------------- /16-dom-html/README.md: -------------------------------------------------------------------------------- 1 | 上一章谈到了 dom 的几个插入操作,虽然插入的方式多种多样,但只要在懂了原生方法等基础上,代码看起来都不是很复杂。比较有意思的一个函数就是 buildFragment 方法,用来将 html 字符串转换成 dom 碎片。本章来看一下 dom 的其它方法。 2 | 3 | ## html、text 方法 4 | 5 | 说到 elem 的操作,就必然要提一下 elem 的类型。[NodeType](https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType),一个节点的 `nodeType` 为 1 表示元素节点,为 3 表示 text 文字节点,为 9 表示 document,11 表示 documentFragment,就是上一章所说的文档碎片。大概知道这几个就可以了。 6 | 7 | 原生的 elem 方法包括 innerHTML,outerHTML,innerText,outerText,然而,在开始本章之前,一定要对这几个方法很常熟练才行。[解密jQuery内核 DOM操作方法(二)html,text,val](http://www.cnblogs.com/aaronjs/p/3520383.html)。 8 | 9 | innerHTML 和 outerHTML 一个显著的差异性就是 outer 会把当前 elem 也一起算进去并获得 html 字符串,inner 不会。 10 | 11 | innerText 和 outerText 获取时候没有显著差异,但是 set 情况下(设置)的时候,outer 会把当前 elem 也给删掉,使用还是要谨慎。 12 | 13 | 有时候因为浏览器的兼容问题,可以用 textContent 替代 innerText。 14 | 15 | ## access 函数源码 16 | 17 | 下面是`jQuery.fn.html` 和 text 的源码,看了之后肯定有话要说: 18 | 19 | ```javascript 20 | jQuery.fn.extends( { 21 | html: function( value ) { 22 | return access( this, function( value ) { 23 | ... // callback 函数 24 | }, null, value, arguments.length ) 25 | }), 26 | text: function( value ) { 27 | return access( this, function( value ) { 28 | ...// 回调函数 29 | }, null, value, arguments.length) 30 | }) 31 | } ); 32 | ``` 33 | 34 | 好吧,我承认,又是同样的套路,先交给 access 函数来处理,然后 callback 函数,我猜这个时候 callback 函数肯定是采用 call 方式使 this 绑定当前 elem。这个套路似曾相识,对,就是 domManip 函数。 35 | 36 | 其实 access 前面已经介绍了过了,不过还是值得来重现介绍一下。 37 | 38 | 像 html、text、css 这些函数的功能,都有一个特点,就是可以带参数,也可以不带参数,先用 access 函数对参数校正,执行回调。 39 | 40 | ```javascript 41 | var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { 42 | var i = 0, 43 | len = elems.length, 44 | bulk = key == null; 45 | 46 | // key values 多种情况 47 | if ( jQuery.type( key ) === "object" ) { 48 | chainable = true; 49 | for ( i in key ) { 50 | access( elems, fn, i, key[ i ], true, emptyGet, raw ); 51 | } 52 | 53 | // Sets 情况 54 | } else if ( value !== undefined ) { 55 | chainable = true; 56 | // value 为函数,不知道这是一种什么情况 57 | if ( !jQuery.isFunction( value ) ) { 58 | raw = true; 59 | } 60 | 61 | if ( bulk ) { 62 | 63 | // 执行回调 64 | if ( raw ) { 65 | fn.call( elems, value ); 66 | fn = null; 67 | 68 | // ...except when executing function values 69 | } else { 70 | bulk = fn; 71 | fn = function( elem, key, value ) { 72 | return bulk.call( jQuery( elem ), value ); 73 | }; 74 | } 75 | } 76 | 77 | // css 走这一步 78 | if ( fn ) { 79 | for ( ; i < len; i++ ) { 80 | fn( 81 | elems[ i ], key, raw ? 82 | value : 83 | value.call( elems[ i ], i, fn( elems[ i ], key ) ) 84 | ); 85 | } 86 | } 87 | } 88 | // chainable 表示参数长度 0 或 1 89 | if ( chainable ) { 90 | return elems; 91 | } 92 | 93 | // Gets 94 | if ( bulk ) { 95 | return fn.call( elems ); 96 | } 97 | 98 | return len ? fn( elems[ 0 ], key ) : emptyGet; 99 | }; 100 | ``` 101 | 102 | access 中出现了一种 value 为函数的情况,没有碰到过,暂不知道什么意思。access 函数基本没有做太大的变化处理看,看起来也不是很难。(哈哈,找到了,后面 css 操作的时候,key 可以为 object) 103 | 104 | ## fn.html 源码 105 | 106 | 现在就是主要来看这个回调函数了,当前的 this 使指向 jQuery 对象的,并没有指向单独的 elem 元素,html 肯定要进行判断: 107 | 108 | ```javascript 109 | jQuery.fn.extends( { 110 | html: function( value ) { 111 | return access( this, function( value ) { 112 | var elem = this[ 0 ] || {}, 113 | i = 0, 114 | l = this.length; 115 | // 参数为空的情况,get 116 | if ( value === undefined && elem.nodeType === 1 ) { 117 | return elem.innerHTML; 118 | } 119 | 120 | // set 操作 121 | if ( typeof value === "string" && !rnoInnerhtml.test( value ) && 122 | !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { 123 | 124 | value = jQuery.htmlPrefilter( value ); 125 | 126 | try { 127 | for ( ; i < l; i++ ) { 128 | elem = this[ i ] || {}; 129 | 130 | // Remove element nodes and prevent memory leaks 131 | if ( elem.nodeType === 1 ) { 132 | 133 | // cleanData 是清除 dom 绑定 cache 的数据 134 | jQuery.cleanData( getAll( elem, false ) ); 135 | elem.innerHTML = value; 136 | } 137 | } 138 | 139 | elem = 0; 140 | 141 | // If using innerHTML throws an exception, use the fallback method 142 | } catch ( e ) {} 143 | } 144 | 145 | if ( elem ) { 146 | this.empty().append( value ); 147 | } 148 | }, null, value, arguments.length ); 149 | } 150 | } ); 151 | ``` 152 | 153 | ## fn.text 源码 154 | 155 | 下面是 text 源码,关于 html 和 text 在开头已经介绍了,算是比较基础的 dom 操作吧,直接来看源码吧: 156 | 157 | ```javascript 158 | jQuery.fn.extends( { 159 | text: function( value ) { 160 | return access( this, function( value ) { 161 | // 前面已经说了,回调函数里的 this 指向 jQuery 对象 162 | return value === undefined ? 163 | // get 164 | jQuery.text( this ) : 165 | // set 166 | this.empty().each( function() { 167 | if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { 168 | this.textContent = value; 169 | } 170 | } ); 171 | }, null, value, arguments.length ); 172 | } 173 | } ); 174 | ``` 175 | 176 | 先来看看 set 的情况,这里有个 empty 函数,源码在下面,两个功能,先清空 dom 内容,在删除 data cache 中保存的数据。这里没有用 innerText 方法,而是使用 textContent 方法,貌似就 set 来说,textContent 兼容性更好。 177 | 178 | ```javascript 179 | jQuery.fn.extends( { 180 | empty: function() { 181 | var elem, 182 | i = 0; 183 | 184 | for ( ; ( elem = this[ i ] ) != null; i++ ) { 185 | if ( elem.nodeType === 1 ) { 186 | 187 | // Prevent memory leaks 188 | jQuery.cleanData( getAll( elem, false ) ); 189 | 190 | // 清空 191 | elem.textContent = ""; 192 | } 193 | } 194 | 195 | return this; 196 | }, 197 | } ); 198 | ``` 199 | 200 | set 的方法知道了,那么 get 呢?get 首先调用了 `jQuery.text` 方法,找了半天才找到它在哪里,原来调用的是 Sizzle 中的方法: 201 | 202 | ```javascript 203 | jQuery.text = Sizzle.getText; 204 | 205 | var getText = Sizzle.getText = function( elem ) { 206 | var node, 207 | ret = "", 208 | i = 0, 209 | nodeType = elem.nodeType; 210 | 211 | if ( !nodeType ) { 212 | // elem 是一个 dom 数组 213 | while ( (node = elem[i++]) ) { 214 | // 分步来搞 215 | ret += getText( node ); 216 | } 217 | } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { 218 | // innerText usage removed for consistency of new lines (jQuery #11153) 219 | // 依然使用 textContent 方法 220 | if ( typeof elem.textContent === "string" ) { 221 | return elem.textContent; 222 | } else { 223 | // Traverse its children 224 | for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { 225 | ret += getText( elem ); 226 | } 227 | } 228 | // 3 (text)或 4 (4 貌似被移除)直接返回 nodeValue 229 | } else if ( nodeType === 3 || nodeType === 4 ) { 230 | return elem.nodeValue; 231 | } 232 | 233 | return ret; 234 | }; 235 | ``` 236 | 237 | ## 总结 238 | 239 | 我自己在浏览器上面测试,发现 textContent 方法并不会把空白符给删了,而且 jQuery 的 text 方法也没有做过滤,每个浏览器的解析也不一样,就可能导致浏览器带来的差异,实际使用的时候,还是要小心点好,多长个心眼。 240 | 241 | ## 参考 242 | 243 | >[Node.NodeType](https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType) 244 | 245 | >[解密jQuery内核 DOM操作方法(二)html,text,val](http://www.cnblogs.com/aaronjs/p/3520383.html) -------------------------------------------------------------------------------- /05-Sizzle-select/README.md: -------------------------------------------------------------------------------- 1 | ## select 函数 2 | 3 | 前面已经介绍了 tokensize 函数的功能,已经生成了一个 tokens 数组,而且对它的组成我们也做了介绍,下面就是介绍对这个 tokens 数组如何处理。 4 | 5 | DOM 元素之间的连接关系大概有 ` > + ~` 几种,包括空格,而 tokens 数组中是 type 是有 tag、attr 和连接符之分的,区分它们 Sizzle 也是有一套规则的,比如上一章我们所讲的 Expr 对象,它真的非常重要: 6 | 7 | ```javascript 8 | Expr.relative = { 9 | ">": { dir: "parentNode", first: true }, 10 | " ": { dir: "parentNode" }, 11 | "+": { dir: "previousSibling", first: true }, 12 | "~": { dir: "previousSibling" } 13 | }; 14 | ``` 15 | 16 | `Expr.relative` 标记用来将连接符区分,对其种类又根据目录进行划分。 17 | 18 | 现在我们再来理一理 tokens 数组,这个数组目前是一个多重数组,现在不考虑逗号的情况,暂定只有一个分支。如果我们使用从右向左的匹配方式的话,`div > div.seq h2 ~ p`,会先得到 type 为 TAG 的 token,而对于 type 为 ~ 的 token 我们已经可以用 relative 对象来判断,现在来介绍 Expr.find 对象: 19 | 20 | ```javascript 21 | Expr.find = {}; 22 | Expr.find['ID'] = function( id, context ) { 23 | if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { 24 | var elem = context.getElementById( id ); 25 | return elem ? [ elem ] : []; 26 | } 27 | }; 28 | Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { 29 | if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { 30 | return context.getElementsByClassName( className ); 31 | } 32 | }; 33 | Expr.find["TAG"] = function(){...}; 34 | ``` 35 | 36 | 实际上 jQuery 的源码还考虑到了兼容性,这里以 find["ID"] 介绍: 37 | 38 | ```javascript 39 | if(support.getById){ 40 | Expr.find['ID'] = function(){...}; // 上面 41 | }else{ 42 | // 兼容 IE 6、7 43 | Expr.find["ID"] = function( id, context ) { 44 | if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { 45 | var node, i, elems, 46 | elem = context.getElementById( id ); 47 | 48 | if ( elem ) { 49 | 50 | // Verify the id attribute 51 | node = elem.getAttributeNode("id"); 52 | if ( node && node.value === id ) { 53 | return [ elem ]; 54 | } 55 | 56 | // Fall back on getElementsByName 57 | elems = context.getElementsByName( id ); 58 | i = 0; 59 | while ( (elem = elems[i++]) ) { 60 | node = elem.getAttributeNode("id"); 61 | if ( node && node.value === id ) { 62 | return [ elem ]; 63 | } 64 | } 65 | } 66 | 67 | return []; 68 | } 69 | }; 70 | } 71 | ``` 72 | 73 | 可以对 find 对象进行简化: 74 | 75 | ```javascript 76 | Expr.find = { 77 | "ID": document.getElementById, 78 | "CLASS": document.getElementsByClassName, 79 | "TAG": document.getElementsByTagName 80 | } 81 | ``` 82 | 83 | 以后还会介绍 `Expr.filter`。 84 | 85 | ## select 源码 86 | 87 | 源码之前,来看几个正则表达式。 88 | 89 | ```javascript 90 | var runescape = /\\([\da-f]{1,6}[\x20\t\r\n\f]?|([\x20\t\r\n\f])|.)/gi 91 | //这个正则是用来对转义字符特殊处理,带个反斜杠的 token 92 | runescape.exec('\\ab'); //["\ab", "ab", undefined] 93 | var rsibling = /[+~]/; //匹配 +、~ 94 | 95 | matchExpr['needsContext'] = /^[\x20\t\r\n\f]*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\([\x20\t\r\n\f]*((?:-\d)?\d*)[\x20\t\r\n\f]*\)|)(?=[^-]|$)/i 96 | //needsContext 用来匹配不完整的 selector 97 | matchExpr['needsContext'].test(' + p')//true 98 | matchExpr['needsContext'].test(':first-child p')//true 99 | //这个不完整,可能是由于抽调 #ID 导致的 100 | ``` 101 | 102 | 而对于 runescape 正则,往往都是配合 replace 来使用: 103 | 104 | ```javascript 105 | var str = '\\ab'; 106 | str.replace(runescape, funescape); 107 | var funescape = function (_, escaped, escapedWhitespace) { 108 | var high = "0x" + escaped - 0x10000; 109 | // NaN means non-codepoint 110 | // Support: Firefox<24 111 | // Workaround erroneous numeric interpretation of +"0x" 112 | return high !== high || escapedWhitespace ? escaped : high < 0 ? 113 | // BMP codepoint 114 | String.fromCharCode(high + 0x10000) : 115 | // Supplemental Plane codepoint (surrogate pair) 116 | String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00); 117 | } 118 | ``` 119 | 120 | 我完全看不懂啦,你们自己意会去吧,O(∩_∩)O哈哈~ 121 | 122 | ```javascript 123 | var select = Sizzle.select = function (selector, context, results, seed) { 124 | var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, 125 | match = !seed && tokenize((selector = compiled.selector || selector)); 126 | 127 | results = results || []; 128 | 129 | // 长度为 1,即表示没有逗号,Sizzle 尝试对此情况优化 130 | if (match.length === 1) { 131 | tokens = match[0] = match[0].slice(0); 132 | // 第一个 TAG 为一个 ID 选择器,设置快速查找 133 | if (tokens.length > 2 && (token = tokens[0]).type === "ID" && context.nodeType === 9 && documentIsHTML && Expr.relative[tokens[1].type]) { 134 | //将新 context 设置成那个 ID 135 | context = (Expr.find["ID"](token.matches[0].replace(runescape, funescape), context) || [])[0]; 136 | if (!context) { 137 | // 第一个 ID 都找不到就直接返回 138 | return results; 139 | 140 | // 此时 selector 为 function,应该有特殊用途 141 | } else if (compiled) { 142 | context = context.parentNode; 143 | } 144 | 145 | selector = selector.slice(tokens.shift().value.length); 146 | } 147 | 148 | // 在没有 CHILD 的情况,从右向左,仍然是对性能的优化 149 | i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length; 150 | while (i--) { 151 | token = tokens[i]; 152 | 153 | // 碰到 +~ 等符号先停止 154 | if (Expr.relative[(type = token.type)]) { 155 | break; 156 | } 157 | if ((find = Expr.find[type])) { 158 | // Search, expanding context for leading sibling combinators 159 | if ((seed = find( 160 | token.matches[0].replace(runescape, funescape), rsibling.test(tokens[0].type) && testContext(context.parentNode) || context))) { 161 | // testContext 是判断 getElementsByTagName 是否存在 162 | // If seed is empty or no tokens remain, we can return early 163 | tokens.splice(i, 1); 164 | selector = seed.length && toSelector(tokens); 165 | //selector 为空,表示到头,直接返回 166 | if (!selector) { 167 | push.apply(results, seed); 168 | return results; 169 | } 170 | break; 171 | } 172 | } 173 | } 174 | } 175 | 176 | // Compile and execute a filtering function if one is not provided 177 | // Provide `match` to avoid retokenization if we modified the selector above 178 | (compiled || compile(selector, match))( 179 | seed, context, !documentIsHTML, results, !context || rsibling.test(selector) && testContext(context.parentNode) || context); 180 | return results; 181 | } 182 | ``` 183 | 184 | toSelector 函数是将 tokens 除去已经选择的将剩下的拼接成字符串: 185 | 186 | ```javascript 187 | function toSelector(tokens) { 188 | var i = 0, 189 | len = tokens.length, 190 | selector = ""; 191 | for (; i < len; i++) { 192 | selector += tokens[i].value; 193 | } 194 | return selector; 195 | } 196 | ``` 197 | 198 | 在最后又多出一个 compile 函数,是 Sizzle 的编译函数,下章讲。 199 | 200 | 到目前为止,该优化的都已经优化了,selector 和 context,还有 seed,而且如果执行到 compile 函数,这几个变量的状态: 201 | 202 | 1. selector 可能已经不上最初那个,经过各种去头去尾; 203 | 2. match 没变,仍是 tokensize 的结果; 204 | 3. seed 事种子集合,所有等待匹配 DOM 的集合; 205 | 4. context 可能已经是头(#ID); 206 | 5. results 没变。 207 | 208 | 可能,你也发现了,其实 compile 是一个异步函数 `compile()()`。 209 | 210 | ## 总结 211 | 212 | select 大概干了几件事, 213 | 214 | 1. 将 tokenize 处理 selector 的结果赋给 match,所以 match 实为 tokens 数组; 215 | 2. 在长度为 1,且第一个 token 为 ID 的情况下,对 context 进行优化,把 ID 匹配到的元素赋给 context; 216 | 3. 若不含 needsContext 正则,则生成一个 seed 集合,为所有的最右 DOM 集合; 217 | 4. 最后事 compile 函数,参数真多... 218 | 219 | ## 参考 220 | 221 | >[jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理](http://www.cnblogs.com/aaronjs/p/3310937.html) -------------------------------------------------------------------------------- /14-event-trigger/README.md: -------------------------------------------------------------------------------- 1 | 以前,我只知道,只有当对浏览器中的元素进行点击的时候,才会出发 click 事件,其它的事件也一样,需要人为的鼠标操作。 2 | 3 | 后来随着学习的不断深入,才知道原来 JS 可以写函数来控制事件的执行,这样子写代码才有意思。记得很久很久以前一些恶意网站,明明鼠标没有点击,却被网站强行的点击了某个链接,大概实现的方式就是这样的吧。 4 | 5 | ## 原生事件 6 | 7 | 其实 JS 的原生事件已经做得挺好了,只是 jQuery 将其进行封装,做的更好。 8 | 9 | 关于 [document.createEvent](https://developer.mozilla.org/en-US/docs/Web/API/Document/createEvent),下面是一个简单的事件点击的例子: 10 | 11 | ```javascript 12 | var fn = function(){ 13 | console.log('click'); 14 | } 15 | 16 | var button = document.getElementById('#id'); 17 | button.addEventListener('click', fn); 18 | 19 | // 点击事件 MouseEvent 20 | var myClick = document.createEvent('MouseEvent'); 21 | myClick.initMouseEvent('click', false, false, null); 22 | 23 | // 执行 24 | button.dispatchEvent(myClick); // 'click' 25 | ``` 26 | 27 | 除了鼠标事件,还可以自定义事件: 28 | 29 | ```javascript 30 | // 随便自定义一个事件 test.click 31 | button.addEventListener('test.click', fn); 32 | 33 | var testEvent = document.createEvent('CustomEvent'); 34 | // customEvent 也可以初始化为鼠标事件,不一定非要自定义事件 35 | testEvent.initCustomEvent('test.click', false, false, null); 36 | 37 | button.dispatchEvent(testEvent); // 'click' 38 | ``` 39 | 40 | JS 原生的模拟事件,使用起来还是很方便的,以上便是原生的。 41 | 42 | 不过 jQuery 也有自己的一套自定义事件方案。 43 | 44 | ## jQuery.trigger 45 | 46 | `jQuery.trigger` 可以和 `HTMLElement.dispatchEvent` 事件拿来对比,他们都是用来模拟和执行监听的事件。 47 | 48 | ### 如何使用 49 | 50 | 关于使用,则比较简单了[.trigger()](http://www.css88.com/jqapi-1.9/trigger/): 51 | 52 | ```javascript 53 | var $body = $(document.body); 54 | 55 | // 先绑定事件 56 | $body.on('click', function(){ 57 | console.log('click'); 58 | }) 59 | 60 | // 执行 61 | $body.trigger('click'); //'click' 62 | ``` 63 | 64 | trigger 还支持更多的参数,同样可以自定义事件: 65 | 66 | ```javascript 67 | $body.on('click.test', function(e, data1, data2){ 68 | console.log(data1 + '-' + data2); 69 | }) 70 | 71 | $body.trigger('click.test', ['hello', 'world']); 72 | ``` 73 | 74 | ### trigger 源码 75 | 76 | trigger 的源码有些简单,因为还是要借助于 `jQuery.event` 来处理: 77 | 78 | ```javascript 79 | jQuery.fn.extend( { 80 | trigger: function(type, data){ 81 | return this.each(function(){ 82 | jQuery.event.trigger(type, data, this); 83 | }) 84 | }, 85 | // triggerHandler 处理第一个且不触发默认事件 86 | triggerHandler: function( type, data ) { 87 | var elem = this[ 0 ]; 88 | if ( elem ) { 89 | return jQuery.event.trigger( type, data, elem, true ); 90 | } 91 | } 92 | } ); 93 | ``` 94 | 95 | 所以 trigger 事件的起点又回到了 `jQuery.event`。 96 | 97 | ## jQuery.event.trigger 98 | 99 | 其实 trigger 和 add + handler 函数很类似,大致都是从 data cache 中搜索缓存,执行回调函数。需要考虑要不要执行默认事件,即第四个参数为 true 的情况。 100 | 101 | ```javascript 102 | jQuery.extend(jQuery.event, { 103 | // onleyHandlers 表示不考虑冒泡事件 104 | trigger: function( event, data, elem, onlyHandlers ) { 105 | 106 | var i, cur, tmp, bubbleType, ontype, handle, special, 107 | eventPath = [ elem || document ], 108 | type = hasOwn.call( event, "type" ) ? event.type : event, 109 | namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; 110 | 111 | cur = tmp = elem = elem || document; 112 | 113 | // Don't do events on text and comment nodes 114 | if ( elem.nodeType === 3 || elem.nodeType === 8 ) { 115 | return; 116 | } 117 | 118 | // 异步不冲突 119 | if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { 120 | return; 121 | } 122 | 123 | if ( type.indexOf( "." ) > -1 ) { 124 | 125 | // Namespaced trigger; create a regexp to match event type in handle() 126 | namespaces = type.split( "." ); 127 | type = namespaces.shift(); 128 | namespaces.sort(); 129 | } 130 | ontype = type.indexOf( ":" ) < 0 && "on" + type; 131 | 132 | // 改装原生的 event 事件 133 | event = event[ jQuery.expando ] ? 134 | event : 135 | new jQuery.Event( type, typeof event === "object" && event ); 136 | 137 | // 判断是否只执行当前 trigger 事件,不冒泡 138 | event.isTrigger = onlyHandlers ? 2 : 3; 139 | event.namespace = namespaces.join( "." ); 140 | event.rnamespace = event.namespace ? 141 | new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : 142 | null; 143 | 144 | // Clean up the event in case it is being reused 145 | event.result = undefined; 146 | if ( !event.target ) { 147 | event.target = elem; 148 | } 149 | 150 | // Clone any incoming data and prepend the event, creating the handler arg list 151 | data = data == null ? 152 | [ event ] : 153 | jQuery.makeArray( data, [ event ] ); 154 | 155 | // Allow special events to draw outside the lines 156 | special = jQuery.event.special[ type ] || {}; 157 | if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { 158 | return; 159 | } 160 | 161 | // 向 document 冒泡并把冒泡结果存储到 eventPath 数组中 162 | if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { 163 | 164 | bubbleType = special.delegateType || type; 165 | if ( !rfocusMorph.test( bubbleType + type ) ) { 166 | cur = cur.parentNode; 167 | } 168 | for ( ; cur; cur = cur.parentNode ) { 169 | eventPath.push( cur ); 170 | tmp = cur; 171 | } 172 | 173 | // Only add window if we got to document (e.g., not plain obj or detached DOM) 174 | if ( tmp === ( elem.ownerDocument || document ) ) { 175 | eventPath.push( tmp.defaultView || tmp.parentWindow || window ); 176 | } 177 | } 178 | 179 | // 按需求来执行 180 | i = 0; 181 | while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { 182 | 183 | event.type = i > 1 ? 184 | bubbleType : 185 | special.bindType || type; 186 | 187 | // 从 data cache 中获得回调函数 188 | handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && 189 | dataPriv.get( cur, "handle" ); 190 | if ( handle ) { 191 | // 执行 192 | handle.apply( cur, data ); 193 | } 194 | 195 | // Native handler 196 | handle = ontype && cur[ ontype ]; 197 | if ( handle && handle.apply && acceptData( cur ) ) { 198 | event.result = handle.apply( cur, data ); 199 | if ( event.result === false ) { 200 | event.preventDefault(); 201 | } 202 | } 203 | } 204 | event.type = type; 205 | 206 | // If nobody prevented the default action, do it now 207 | if ( !onlyHandlers && !event.isDefaultPrevented() ) { 208 | 209 | if ( ( !special._default || 210 | special._default.apply( eventPath.pop(), data ) === false ) && 211 | acceptData( elem ) ) { 212 | 213 | // Call a native DOM method on the target with the same name as the event. 214 | // Don't do default actions on window, that's where global variables be (#6170) 215 | if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { 216 | 217 | // Don't re-trigger an onFOO event when we call its FOO() method 218 | tmp = elem[ ontype ]; 219 | 220 | if ( tmp ) { 221 | elem[ ontype ] = null; 222 | } 223 | 224 | // Prevent re-triggering of the same event, since we already bubbled it above 225 | jQuery.event.triggered = type; 226 | elem[ type ](); 227 | jQuery.event.triggered = undefined; 228 | 229 | if ( tmp ) { 230 | elem[ ontype ] = tmp; 231 | } 232 | } 233 | } 234 | } 235 | 236 | return event.result; 237 | }, 238 | }) 239 | ``` 240 | 241 | ## 总结 242 | 243 | 在 `jQuery.event.trigger` 中,比较有意思的是模拟冒泡机制,大致的思路就是: 244 | 245 | 1. 把当前 elem 存入数组; 246 | 2. 查找当前 elem 的父元素,如果符合,push 到数组中,重复第一步,否则下一步; 247 | 3. 遍历数组,从 data cache 中查看是否绑定 type 事件,然后依次执行。 248 | 249 | 冒泡事件就是就是由内向外冒泡的过程,这个过程不是很复杂。 250 | 251 | event 事件应该就这么多内容吧。 252 | 253 | ## 参考 254 | 255 | >[解密jQuery事件核心 - 自定义设计(三)](http://www.cnblogs.com/aaronjs/p/3452279.html) 256 | 257 | >[MDN createEvent](https://developer.mozilla.org/en-US/docs/Web/API/Document/createEvent) 258 | 259 | >[解密jQuery事件核心 - 模拟事件(四)](http://www.cnblogs.com/aaronjs/p/3481075.html) -------------------------------------------------------------------------------- /12-event-extend/README.md: -------------------------------------------------------------------------------- 1 | 前面一章,大概是一个总览,介绍了事件绑定的初衷和使用,通过了解,知道其内部是一个什么样的流程,从哪个函数到哪个函数。无论 jQuery 的源码简单或者复杂,有一点可以肯定,jQuery 致力于解决浏览器的兼容问题,最终是服务于使用者。 2 | 3 | ## 一些遗留问题 4 | 5 | 前面介绍 bind、delegate 和它们的 un 方法的时候,经提醒,忘记提到一些内容,却是我们经常使用的。比如 `$('body').click`,`$('body').mouseleave`等,它们是直接定义在原型上的函数,不知道怎么,就把它们给忽略了。 6 | 7 | ```javascript 8 | jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + 9 | "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + 10 | "change select submit keydown keypress keyup contextmenu" ).split( " " ), 11 | function( i, name ) { 12 | 13 | // Handle event binding 14 | jQuery.fn[ name ] = function( data, fn ) { 15 | return arguments.length > 0 ? 16 | this.on( name, null, data, fn ) : 17 | this.trigger( name ); 18 | }; 19 | } ); 20 | ``` 21 | 22 | 这个构造也是十分巧妙的,这些方法组成的字符串通过 `split(" ")` 变成数组,而后又通过 each 方法,在原型上对应每个名称,定义函数,这里可以看到,依旧是 on,还有 targger: 23 | 24 | ```javascript 25 | jQuery.fn.extend( { 26 | trigger: function(type, data){ 27 | return this.each(function (){ 28 | // // 依旧是 event 对象上的方法 29 | jQuery.event.trigger(type, data, this); 30 | }) 31 | } 32 | } ) 33 | ``` 34 | 35 | 还缺少一个 one 方法,这个方法表示绑定的事件同类型只执行一次,[.one()](http://www.css88.com/jqapi-1.9/one/): 36 | 37 | ```javascript 38 | jQuery.fn.extend( { 39 | one: function( types, selector, data, fn ) { 40 | // 全局 on 函数 41 | return on( this, types, selector, data, fn, 1 ); 42 | }, 43 | } ); 44 | ``` 45 | 46 | ## DOM 事件知识点 47 | 48 | 发现随着 event 源码的不断的深入,我自己出现越来越多的问题,比如没有看到我所熟悉的 `addEventListener`,还有一些看得很迷糊的 events 事件,所以我决定还是先来看懂 JS 中的 DOM 事件吧。 49 | 50 | ### 早期 DOM 事件 51 | 52 | 在 HTML 的 DOM 对象中,有一些以 `on` 开头的熟悉,比如 onclick、onmouseout 等,这些就是早期的 DOM 事件,它的最简单的用法,就是支持直接在对象上以名称来写函数: 53 | 54 | ```javascript 55 | document.getElementsByTagName('body')[0].onclick = function(){ 56 | console.log('click!'); 57 | } 58 | document.getElementsByTagName('body')[0].onmouseout = function(){ 59 | console.log('mouse out!'); 60 | } 61 | ``` 62 | 63 | onclick 函数会默认传入一个 event 参数,表示触发事件时的状态,包括触发对象,坐标等等。 64 | 65 | 这种方式有一个非常大的弊端,就是相同名称的事件,会前后覆盖,后一个 click 函数会把前一个 click 函数覆盖掉: 66 | 67 | ```javascript 68 | var body = document.getElementsByTagName('body')[0]; 69 | body.onclick = function(){ 70 | console.log('click1'); 71 | } 72 | body.onclick = function(){ 73 | console.log('click2'); 74 | } 75 | // "click2" 76 | body.onclick = null; 77 | // 没有效果 78 | ``` 79 | 80 | ### DOM 2.0 81 | 82 | 随着 DOM 的发展,已经来到 2.0 时代,也就是我所熟悉的 addEventListener 和 attachEvent(IE),[JS 中的事件冒泡与捕获](http://yuren.space/blog/2016/10/16/%E4%BA%8B%E4%BB%B6%E5%86%92%E6%B3%A1%E4%B8%8E%E6%8D%95%E8%8E%B7/)。这个时候和之前相比,变化真的是太大了,[MDN addEventListener()](https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener)。 83 | 84 | 变化虽然是变化了,但是浏览器的兼容却成了一个大问题,比如下面就可以实现不支持 `addEventListener` 浏览器: 85 | 86 | ```javascript 87 | (function() { 88 | // 不支持 preventDefault 89 | if (!Event.prototype.preventDefault) { 90 | Event.prototype.preventDefault=function() { 91 | this.returnValue=false; 92 | }; 93 | } 94 | // 不支持 stopPropagation 95 | if (!Event.prototype.stopPropagation) { 96 | Event.prototype.stopPropagation=function() { 97 | this.cancelBubble=true; 98 | }; 99 | } 100 | // 不支持 addEventListener 时候 101 | if (!Element.prototype.addEventListener) { 102 | var eventListeners=[]; 103 | 104 | var addEventListener=function(type,listener /*, useCapture (will be ignored) */) { 105 | var self=this; 106 | var wrapper=function(e) { 107 | e.target=e.srcElement; 108 | e.currentTarget=self; 109 | if (typeof listener.handleEvent != 'undefined') { 110 | listener.handleEvent(e); 111 | } else { 112 | listener.call(self,e); 113 | } 114 | }; 115 | if (type=="DOMContentLoaded") { 116 | var wrapper2=function(e) { 117 | if (document.readyState=="complete") { 118 | wrapper(e); 119 | } 120 | }; 121 | document.attachEvent("onreadystatechange",wrapper2); 122 | eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper2}); 123 | 124 | if (document.readyState=="complete") { 125 | var e=new Event(); 126 | e.srcElement=window; 127 | wrapper2(e); 128 | } 129 | } else { 130 | this.attachEvent("on"+type,wrapper); 131 | eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper}); 132 | } 133 | }; 134 | var removeEventListener=function(type,listener /*, useCapture (will be ignored) */) { 135 | var counter=0; 136 | while (counter[jQuery 2.0.3 源码分析 事件体系结构](http://www.cnblogs.com/aaronjs/p/3441320.html) 238 | 239 | >[addEvent库](http://dean.edwards.name/weblog/2005/10/add-event/) 240 | 241 | >[MDN EventTarget.addEventListener()](https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener) 242 | 243 | >[原生JavaScript事件详解](http://www.cnblogs.com/iyangyuan/p/4190773.html) -------------------------------------------------------------------------------- /04-Sizzle-Tokens/README.md: -------------------------------------------------------------------------------- 1 | ## Tokens 词法分析 2 | 3 | 其实词法分析是汇编里面提到的词汇,把它用到这里感觉略有不合适,但 Sizzle 中的 `tokensize`函数干的就是词法分析的活。 4 | 5 | 上一章我们已经讲到了 Sizzle 的用法,实际上就是 jQuery.find 函数,只不过还涉及到 jQuery.fn.find。jQuery.find 函数考虑的很周到,对于处理 #id、.class 和 TagName 的情况,都比较简单,通过一个正则表达式 `rquickExpr` 将内容给分开,如果浏览器支持 querySelectorAll,那更是最好的。 6 | 7 | 比较难的要数这种类似于 css 选择器的 selector,`div > div.seq h2 ~ p , #id p`,如果使用从左向右的查找规则,效率很低,而从右向左,可以提高效率。 8 | 9 | 本章就来介绍 tokensize 函数,看看它是如何将复杂的 selector 处理成 tokens 的。 10 | 11 | 我们以 `div > div.seq h2 ~ p , #id p` 为例,这是一个很简单的 css,逗号 , 将表达式分成两部分。css 中有一些基本的符号,这里有必要强调一下,比如 `,、space、>、+、~`: 12 | 13 | 1. `div,p` , 表示并列关系,所有 div 元素和 p 元素; 14 | 2. `div p` 空格表示后代元素,div 元素内所有的 p 元素; 15 | 3. `div>p` > 子元素且相差只能是一代,父元素为 div 的所有 p 元素; 16 | 4. `div+p` + 表示紧邻的兄弟元素,前一个兄弟节点为 div 的所有 p 元素; 17 | 5. `div~p` ~ 表示兄弟元素,所有前面有兄弟元素 div 的所有 p 元素。 18 | 19 | 除此之外,还有一些 a、input 比较特殊的: 20 | 21 | 1. `a[target=_blank]` 选择所有 target 为 _blank 的所有 a 元素; 22 | 2. `a[title=search]` 选择所有 title 为 search 的所有 a 元素; 23 | 3. `input[type=text]` 选择 type 为 text 的所有 input 元素; 24 | 4. `p:nth-child(2)` 选择其为父元素第二个元素的所有 p 元素; 25 | 26 | Sizzle 都是支持这些语法的,如果我们把这一步叫做词法分析,那么词法分析的结果是一个什么东西呢? 27 | 28 | `div > div.seq h2 ~ p , #id p` 经过 tokensize(selector) 会返回一个数组,改数组在函数中称为 groups,该数组有两个元素,分别是 tokens0 和 tokens1,代表选择器的两部分。tokens 也是数组,它的每一个元素都是一个 token 对象。 29 | 30 | token 对象结构如下所说: 31 | 32 | ```javascript 33 | token: { 34 | value: matched, // 匹配到的字符串 35 | type: type, //token 类型 36 | matches: match //去除 value 的正则结果数组 37 | } 38 | ``` 39 | 40 | Sizzle 中 type 的种类有下面几种:ID、CLASS、TAG、ATTR、PSEUDO、CHILD、bool、needsContext,这几种有几种我也不知道啥意思,child 表示 nth-child、even、odd 这种子选择器。这是针对于 matches 存在的情况,对于 matches 不存在的情况,其 type 就是 value 的 trim() 操作,后面会谈到。 41 | 42 | tokensize 函数对 selector 的处理,连空格都不放过,因为空格也属于 type 的一种,而且还很重要,`div > div.seq h2 ~ p` 的处理结果: 43 | 44 | ```javascript 45 | tokens: [ 46 | [value:'div', type:'TAG', matches:Array[1]], 47 | [value:' > ', type:'>'], 48 | [value:'div', type:'TAG', matches:Array[1]], 49 | [value:'.seq', type:'CLASS', matches:Array[1]], 50 | [value:' ', type:' '], 51 | [value:'h2', type:'TAG', matches:Array[1]], 52 | [value:' ~ ', type:'~'], 53 | [value:'p', type:'TAG', matches:Array[1]], 54 | ] 55 | ``` 56 | 57 | 这个数组会交给 Sizzle 的下一个流程来处理,今天暂不讨论。 58 | 59 | ## tokensize 源码 60 | 61 | 照旧,先来看一下几个正则表达式。 62 | 63 | ```javascript 64 | var rcomma = /^[\x20\t\r\n\f]*,[\x20\t\r\n\f]*/; 65 | rcomma.exec('div > div.seq h2 ~ p');//null 66 | rcomma.exec(' ,#id p');//[" ,"] 67 | ``` 68 | 69 | rcomma 这个正则,主要是用来区分 selector 是否到下一个规则,如果到下一个规则,就把之前处理好的 push 到 groups 中。这个正则中 `[\x20\t\r\n\f]` 是用来匹配类似于 whitespace 的,主体就一个逗号。 70 | 71 | ```javascript 72 | var rcombinators = /^[\x20\t\r\n\f]*([>+~]|[\x20\t\r\n\f])[\x20\t\r\n\f]*/; 73 | rcombinators.exec(' > div.seq h2 ~ p'); //[" > ", ">"] 74 | rcombinators.exec(' ~ p'); //[" ~ ", "~"] 75 | rcombinators.exec(' h2 ~ p'); //[" ", " "] 76 | ``` 77 | 78 | 是不是看来 rcombinators 这个正则表达式,上面 tokens 那个数组的内容就完全可以看得懂了。 79 | 80 | 其实,如果看 jQuery 的源码,rcomma 和 rcombinators 并不是这样来定义的,而是用下面的方式来定义: 81 | 82 | ```javascript 83 | var whitespace = "[\\x20\\t\\r\\n\\f]"; 84 | var rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), 85 | rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), 86 | rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), 87 | ``` 88 | 89 | 有的时候必须得要佩服 jQuery 中的做法,该简则简,该省则省,每一处代码都是极完美的。 90 | 91 | 还有两个对象,Expr 和 matchExpr,Expr 是一个非常关键的对象,它涵盖了几乎所有的可能的参数,比较重要的参数比如有: 92 | 93 | ```javascript 94 | Expr.filter = { 95 | "TAG": function(){...}, 96 | "CLASS": function(){...}, 97 | "ATTR": function(){...}, 98 | "CHILD": function(){...}, 99 | "ID": function(){...}, 100 | "PSEUDO": function(){...} 101 | } 102 | Expr.preFilter = { 103 | "ATTR": function(){...}, 104 | "CHILD": function(){...}, 105 | "PSEUDO": function(){...} 106 | } 107 | ``` 108 | 109 | 这个 filter 和 preFilter 是处理 type=TAG 的关键步骤,包括一些类似于 input[type=text] 也是这几个函数处理,也比较复杂,我本人是看迷糊了。还有 matchExpr 正则表达式: 110 | 111 | ```javascript 112 | var identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", 113 | attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + 114 | // Operator (capture 2) 115 | "*([*^$|!~]?=)" + whitespace + 116 | // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" 117 | "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + 118 | "*\\]", 119 | pseudos = ":(" + identifier + ")(?:\\((" + 120 | // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: 121 | // 1. quoted (capture 3; capture 4 or capture 5) 122 | "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + 123 | // 2. simple (capture 6) 124 | "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + 125 | // 3. anything else (capture 2) 126 | ".*" + 127 | ")\\)|)", 128 | booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped"; 129 | var matchExpr = { 130 | "ID": new RegExp( "^#(" + identifier + ")" ), 131 | "CLASS": new RegExp( "^\\.(" + identifier + ")" ), 132 | "TAG": new RegExp( "^(" + identifier + "|[*])" ), 133 | "ATTR": new RegExp( "^" + attributes ), 134 | "PSEUDO": new RegExp( "^" + pseudos ), 135 | "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + 136 | "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + 137 | "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), 138 | "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), 139 | // For use in libraries implementing .is() 140 | // We use this for POS matching in `select` 141 | "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + 142 | whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) 143 | } 144 | ``` 145 | 146 | matchExpr 作为正则表达式对象,其 key 的每一项都是一个 type 类型,将 type 匹配到,交给后续函数处理。 147 | 148 | tokensize 源码如下: 149 | 150 | ```javascript 151 | var tokensize = function (selector, parseOnly) { 152 | var matched, match, tokens, type, soFar, groups, preFilters, cached = tokenCache[selector + " "]; 153 | // tokenCache 表示 token 缓冲,保持已经处理过的 token 154 | if (cached) { 155 | return parseOnly ? 0 : cached.slice(0); 156 | } 157 | 158 | soFar = selector; 159 | groups = []; 160 | preFilters = Expr.preFilter; 161 | 162 | while (soFar) { 163 | 164 | // 判断一个分组是否结束 165 | if (!matched || (match = rcomma.exec(soFar))) { 166 | if (match) { 167 | // 从字符串中删除匹配到的 match 168 | soFar = soFar.slice(match[0].length) || soFar; 169 | } 170 | groups.push((tokens = [])); 171 | } 172 | 173 | matched = false; 174 | 175 | // 连接符 rcombinators 176 | if ((match = rcombinators.exec(soFar))) { 177 | matched = match.shift(); 178 | tokens.push({ 179 | value: matched, 180 | type: match[0].replace(rtrim, " ") 181 | }); 182 | soFar = soFar.slice(matched.length); 183 | } 184 | 185 | // 过滤,Expr.filter 和 matchExpr 都已经介绍过了 186 | for (type in Expr.filter) { 187 | if ((match = matchExpr[type].exec(soFar)) && (!preFilters[type] || (match = preFilters[type](match)))) { 188 | matched = match.shift(); 189 | // 此时的 match 实际上是 shift() 后的剩余数组 190 | tokens.push({ 191 | value: matched, 192 | type: type, 193 | matches: match 194 | }); 195 | soFar = soFar.slice(matched.length); 196 | } 197 | } 198 | 199 | if (!matched) { 200 | break; 201 | } 202 | } 203 | 204 | // parseOnly 这个参数应该以后会用到 205 | return parseOnly ? 206 | soFar.length : 207 | soFar ? 208 | Sizzle.error(selector) : 209 | // 存入缓存 210 | tokenCache(selector, groups).slice(0); 211 | } 212 | ``` 213 | 214 | 不仅数组,字符串也有 slice 操作,而且看源码的话,jQuery 中对字符串的截取,使用的都是 slice 方法。 215 | 216 | 如果此时 parseOnly 不成立,则返回结果需从 tokenCache 这个函数中来查找: 217 | 218 | ```javascript 219 | var tokenCache = createCache(); 220 | function createCache() { 221 | var keys = []; 222 | 223 | function cache( key, value ) { 224 | // Expr.cacheLength = 50 225 | if ( keys.push( key + " " ) > Expr.cacheLength ) { 226 | // 删,最不经常使用 227 | delete cache[ keys.shift() ]; 228 | } 229 | // 整个结果返回的是 value 230 | return (cache[ key + " " ] = value); 231 | } 232 | return cache; 233 | } 234 | ``` 235 | 236 | 可知,返回的结果是 groups,tokensize 就学完了,下章将介绍 tokensize 的后续。 237 | 238 | ## 总结 239 | 240 | 对于一个复杂的 selector,其 tokensize 的过程远比今天介绍的要复杂,今天的例子有点简单(其实也比较复杂了),后面的内容更精彩。 241 | 242 | ## 参考 243 | 244 | >[jQuery 2.0.3 源码分析Sizzle引擎 - 词法解析](http://www.cnblogs.com/aaronjs/p/3300797.html) 245 | 246 | >[CSS 选择器参考手册](http://www.w3school.com.cn/cssref/css_selectors.asp) -------------------------------------------------------------------------------- /18-class/README.md: -------------------------------------------------------------------------------- 1 | 眼看 jQuery 的源码就快到头了,后面还有几个重要的内容,包括 ajax 和动画操作,加油把它们看完,百度前端学院的新一批课程也开始了。[百度前端学院](http://ife.baidu.com/course/all)。 2 | 3 | class 的的操作应该算是比较愉快的,因为内容不是很多,或者说,内容涉及到的原生操作不是很大,就一个 className 或 getAttribute,主要还是来看下它涉及到的一些兼容性操作。 4 | 5 | ## class 操作 6 | 7 | 先来说一个比较有趣的 class 操作,先把[链接](http://stackoverflow.com/questions/5041494/selecting-and-manipulating-css-pseudo-elements-such-as-before-and-after-usin)贴上。 8 | 9 | js 有一个非常大的缺陷,就是无法控制伪元素的样式,比如 after 和 before,这样子会失去很多乐趣(同样也带来了很多乐趣)。上面的链接是 stackoverflow 的解答。 10 | 11 | **1. class 方式** 12 | 13 | 通过事先定义 class 的方式来解决: 14 | 15 | ``` 16 | p:before { 17 | content: "c1" 18 | } 19 | p.click:before { 20 | content: "click" 21 | } 22 | 23 | // js 24 | $("p").on("click", function(){ 25 | $(this).toggleClass('click'); 26 | }) 27 | ``` 28 | 29 | **2. 內联 style 方式** 30 | 31 | 这种方式不优雅,也是一种解决办法。 32 | 33 | ```javascript 34 | var str = "click"; 35 | $('').appendTo('head'); 36 | ``` 37 | 38 | **3. jQuery data-attr 来解决** 39 | 40 | 这种方式是依靠 content 的特性: 41 | 42 | ``` 43 | p:before { 44 | content: attr(data-click); 45 | } 46 | 47 | //js 48 | var str = 'click'; 49 | $("p").on("click", function(){ 50 | $(this).attr("data-click", str); 51 | }) 52 | ``` 53 | 54 | 这种方式应该是动态改变。 55 | 56 | jQuery 的应用还是挺广泛的。 57 | 58 | ## fn.hasClass 59 | 60 | jQuery 中的 class 操作还是很有意思,会用到很多正则表达式,我超喜欢正则表达式的。 61 | 62 | 如果让我用原生的 js 来实现 class 操作,我会想到两种方式,一种是 [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className),它的兼容性非常好,所有浏览器都支持,包括 mobile。第二个是 [getAttribute](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute),也是所有浏览器都支持(有版本限制)。 63 | 64 | 先从 hasClass 说起吧: 65 | 66 | ```javascript 67 | // 获取 class-name 68 | function getClass( elem ) { 69 | return elem.getAttribute && elem.getAttribute( "class" ) || ""; 70 | } 71 | 72 | // 将 class name 进行处理 73 | function stripAndCollapse( value ) { 74 | var tokens = value.match( /[^\x20\t\r\n\f]+/g ) || []; 75 | return tokens.join( " " ); 76 | } 77 | 78 | jQuery.fn.extend( { 79 | hasClass: function( selector ) { 80 | var className, elem, 81 | i = 0; 82 | 83 | className = " " + selector + " "; 84 | while ( ( elem = this[ i++ ] ) ) { 85 | if ( elem.nodeType === 1 && 86 | ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { 87 | return true; 88 | } 89 | } 90 | 91 | return false; 92 | } 93 | } ); 94 | ``` 95 | 96 | 可以看出 `getClass` 函数使用的是 `getAttribute` 方法。 97 | 98 | ## fn.addClass 99 | 100 | 接下来看一下添加 add: 101 | 102 | ```javascript 103 | jQuery.fn.extend( { 104 | addClass: function( value ) { 105 | var classes, elem, cur, curValue, clazz, j, finalValue, 106 | i = 0; 107 | // 参数为函数... 108 | if ( jQuery.isFunction( value ) ) { 109 | return this.each( function( j ) { 110 | jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); 111 | } ); 112 | } 113 | 114 | if ( typeof value === "string" && value ) { 115 | // 可以添加多个 class 116 | classes = value.match( /[^\x20\t\r\n\f]+/g ) || []; 117 | 118 | while ( ( elem = this[ i++ ] ) ) { 119 | curValue = getClass( elem ); 120 | cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); 121 | 122 | if ( cur ) { 123 | j = 0; 124 | while ( ( clazz = classes[ j++ ] ) ) { 125 | if ( cur.indexOf( " " + clazz + " " ) < 0 ) { 126 | cur += clazz + " "; 127 | } 128 | } 129 | 130 | // 在这里 set class,有个 diff 判断 131 | finalValue = stripAndCollapse( cur ); // 去除两侧空格 132 | if ( curValue !== finalValue ) { 133 | elem.setAttribute( "class", finalValue ); 134 | } 135 | } 136 | } 137 | } 138 | 139 | return this; 140 | } 141 | } ); 142 | ``` 143 | 144 | jQuery 大致处理的思路是这样的:先把当前 elem 中的 class 取出来 `cur`,要添加的 `value` 如果在 cur 中 `indexOf` 的值显示不存在,就在 cur 后面加上 value。 145 | 146 | ## fn.removeClass 147 | 148 | 删除可能要麻烦一点点: 149 | 150 | ```javascript 151 | jQuery.fn.extend( { 152 | removeClass: function( value ) { 153 | var classes, elem, cur, curValue, clazz, j, finalValue, 154 | i = 0; 155 | // 不知道在哪里用到 value 为 function 情况 156 | if ( jQuery.isFunction( value ) ) { 157 | return this.each( function( j ) { 158 | jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); 159 | } ); 160 | } 161 | // 无参数表示 移除所有的 class ... 162 | if ( !arguments.length ) { 163 | return this.attr( "class", "" ); 164 | } 165 | 166 | if ( typeof value === "string" && value ) { 167 | classes = value.match( rnothtmlwhite ) || []; 168 | 169 | while ( ( elem = this[ i++ ] ) ) { 170 | curValue = getClass( elem ); 171 | 172 | // This expression is here for better compressibility (see addClass) 173 | cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); 174 | 175 | if ( cur ) { 176 | j = 0; 177 | while ( ( clazz = classes[ j++ ] ) ) { 178 | 179 | // 移除所有需要移除的 class 180 | while ( cur.indexOf( " " + clazz + " " ) > -1 ) { 181 | cur = cur.replace( " " + clazz + " ", " " ); 182 | } 183 | } 184 | 185 | // Only assign if different to avoid unneeded rendering. 186 | finalValue = stripAndCollapse( cur ); 187 | if ( curValue !== finalValue ) { 188 | elem.setAttribute( "class", finalValue ); 189 | } 190 | } 191 | } 192 | } 193 | 194 | return this; 195 | } 196 | } ); 197 | ``` 198 | 199 | 可以看出 remove 的操作基本上和 add 一样,只不过处理 class 的时候略有不同: 200 | 201 | ```javascript 202 | // 这里用 while,是有技巧的 203 | while ( cur.indexOf( " " + clazz + " " ) > -1 ) { 204 | cur = cur.replace( " " + clazz + " ", " " ); 205 | } 206 | ``` 207 | 208 | 用 replace 替换匹配的 clazz 为空格。 209 | 210 | ## fn.toggleClass 211 | 212 | toggleClass 使用的频率也比较高。 213 | 214 | 先来看看大致用法,你肯定会忽略它的第二个参数的意思。[.toggleClass()](http://www.css88.com/jqapi-1.9/toggleClass/),当第二个参数为 true 的情况,就是 addClass,为 false 时,removeClass,从源码来看,就是直接调用的这两个函数。 215 | 216 | 除了两个参数,还有无参和只有 false 情况,下面也都有明确的处理办法。 217 | 218 | ```javascript 219 | jQuery.fn.extend( { 220 | toggleClass: function( value, stateVal ) { 221 | var type = typeof value; 222 | 223 | // 第二个参数为 boolean 224 | if ( typeof stateVal === "boolean" && type === "string" ) { 225 | return stateVal ? this.addClass( value ) : this.removeClass( value ); 226 | } 227 | 228 | if ( jQuery.isFunction( value ) ) { 229 | return this.each( function( i ) { 230 | jQuery( this ).toggleClass( 231 | value.call( this, i, getClass( this ), stateVal ), 232 | stateVal 233 | ); 234 | } ); 235 | } 236 | 237 | return this.each( function() { 238 | var className, i, self, classNames; 239 | 240 | if ( type === "string" ) { 241 | 242 | // Toggle individual class names 243 | i = 0; 244 | self = jQuery( this ); 245 | classNames = value.match( rnothtmlwhite ) || []; 246 | 247 | while ( ( className = classNames[ i++ ] ) ) { 248 | 249 | // 有则删,无则加,逻辑很简单 250 | if ( self.hasClass( className ) ) { 251 | self.removeClass( className ); 252 | } else { 253 | self.addClass( className ); 254 | } 255 | } 256 | 257 | // 当无参或只有一个 false 时,所有 class 都执行 258 | } else if ( value === undefined || type === "boolean" ) { 259 | className = getClass( this ); 260 | if ( className ) { 261 | 262 | // Store className if set 263 | dataPriv.set( this, "__className__", className ); 264 | } 265 | 266 | if ( this.setAttribute ) { 267 | this.setAttribute( "class", 268 | className || value === false ? 269 | "" : 270 | dataPriv.get( this, "__className__" ) || "" 271 | ); 272 | } 273 | } 274 | } ); 275 | } 276 | } ); 277 | ``` 278 | 279 | 看得出来,这个逻辑和前面两个很像,不过当无参或只有一个 boolean 且 false 时,先将当前的 className 保存到 data cache 中,然后实现 toggle 操作: 280 | 281 | ```javascript 282 | if ( this.setAttribute ) { 283 | this.setAttribute( "class", 284 | className || value === false ? // 判断条件 285 | "" : // 有则设空 286 | dataPriv.get( this, "__className__" ) || "" // 无则从 data cache 取 287 | ); 288 | } 289 | ``` 290 | 291 | ## 总结 292 | 293 | 感觉 jQuery 中的 class 操作不是很复杂,难道是我在进步吗,哈哈。 294 | 295 | ## 参考 296 | 297 | >[jQuery 2.0.3 源码分析 样式操作](http://www.cnblogs.com/aaronjs/p/3433358.html) 298 | 299 | >[Selecting and manipulating CSS ..](http://stackoverflow.com/questions/5041494/selecting-and-manipulating-css-pseudo-elements-such-as-before-and-after-usin) 300 | 301 | >[.toggleClass()](http://www.css88.com/jqapi-1.9/toggleClass/) -------------------------------------------------------------------------------- /10-hooks/README.md: -------------------------------------------------------------------------------- 1 | hooks 在英语中的意思表示钩子或挂钩,在 jQuery 中也有 hooks 这么一个概念,它的功能在考虑到一些兼容性和其它特殊情况的条件下,优先考虑这些特殊情况,而后才去用普通的方法处理,这种说法还是比较形象的。 2 | 3 | hooks 的使用非常用技术含量,可以支撑在原来的基础上扩展,而对于接口则无需改变,举个例子,像 `fn.css()` 这个函数我们都是非常熟悉的了,拿来就用,而不需要考虑浏览器的兼容性,这里的兼容性包括 border-radius 兼容,使用的时候不需要在前面加上 -webkit- 浏览器标识。而 css 函数的内部则是借助 `$.cssHooks()`来实现这种“钩子”的效果的,扩展的时候,也是在这个对象上进行扩展。 4 | 5 | ## 先来说说 attr 和 prop 6 | 7 | 不急着上来就谈 hooks,先来看看 hooks 涉及到的应用。一个典型的应用就是 `fn.attr` 和 `fn.prop`,这两个原型函数的作用是用来给 jQuery 对象绑定元素的,如果不了解,可以参考这两个链接,[attr](http://www.css88.com/jqapi-1.9/attr/),[prop](http://www.css88.com/jqapi-1.9/prop/)。 8 | 9 | 虽然它们都是添加属性,却是不同的方式,其中,attr 是把属性放到 html 中(实际上是 elem.attributes 属性),而 prop 是把属性添加到 dom 对象上,可以通过 [.] 来读取。 10 | 11 | 那么什么叫做 html 中?就是我们常说的 `data-` 数据: 12 | 13 | ```javascript 14 | var body = $('body'); 15 | body.attr('data-name','body'); 16 | // 17 | body.data('name'); //'body' 18 | ``` 19 | 20 | attr 方法是对应于 jQuery 中的方法,而内部是通过 setAttribute,getAttribute 这种低级 api 来实现的,而且在 dom 对象的 attributes 属性上是可以找到绑定值的,所以 attr 和 prop 是两种不同的方法。 21 | 22 | 这两个函数有四个功能,分别包括读取和设置,如果参数只有一个,表示读(如果参数是 Object 另外考虑),参数为两个,表示写。 23 | 24 | 当然,除此之外,还有 removeAttr 和 removeProp 方法,源码如下: 25 | 26 | ```javascript 27 | jQuery.fn.extend({ 28 | attr: function (name, value) { 29 | return access(this, jQuery.attr, name, value, arguments.length > 1); 30 | }, 31 | removeAttr: function (name) { 32 | return this.each(function () { 33 | jQuery.removeAttr(this, name); 34 | }); 35 | }, 36 | prop: function (name, value) { 37 | return access(this, jQuery.prop, name, value, arguments.length > 1); 38 | }, 39 | removeProp: function (name) { 40 | return this.each(function () { 41 | delete this[jQuery.propFix[name] || name]; 42 | }); 43 | } 44 | }) 45 | ``` 46 | 47 | ### access 方法 48 | 49 | 先看 attr 和 prop,都是通过 `access` 函数,至少传入的参数不同,一个是 jQuery.attr,一个是 jQuery.prop。来看看 access: 50 | 51 | ```javascript 52 | var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { 53 | var i = 0, 54 | len = elems.length, 55 | bulk = key == null; 56 | 57 | // 参数为对象,一次性设置多个值 58 | if ( jQuery.type( key ) === "object" ) { 59 | chainable = true; 60 | for ( i in key ) { 61 | access( elems, fn, i, key[ i ], true, emptyGet, raw ); 62 | } 63 | 64 | // 设置一个值 65 | } else if ( value !== undefined ) { 66 | chainable = true; 67 | 68 | if ( !jQuery.isFunction( value ) ) { 69 | raw = true; 70 | } 71 | 72 | // key 为 null 的情况 73 | if ( bulk ) { 74 | 75 | if ( raw ) { 76 | fn.call( elems, value ); 77 | fn = null; 78 | 79 | // value 为 function 80 | } else { 81 | bulk = fn; 82 | fn = function( elem, key, value ) { 83 | return bulk.call( jQuery( elem ), value ); 84 | }; 85 | } 86 | } 87 | 88 | // 函数执行在这里 89 | if ( fn ) { 90 | for ( ; i < len; i++ ) { 91 | fn( 92 | elems[ i ], key, raw ? 93 | value : 94 | value.call( elems[ i ], i, fn( elems[ i ], key ) ) 95 | ); 96 | } 97 | } 98 | } 99 | 100 | if ( chainable ) { 101 | // 写情况的返回值 102 | return elems; 103 | } 104 | 105 | // Gets 106 | if ( bulk ) { 107 | return fn.call( elems ); 108 | } 109 | // 这个返回值是比较熟悉的,即 get 110 | return len ? fn( elems[ 0 ], key ) : emptyGet; 111 | } 112 | ``` 113 | 114 | access 不是今天的重点,函数不是很难,源码读起来挺有意思。 115 | 116 | ### attr 和 prop 源码 117 | 118 | 来看看 jQuery.attr 和 jQuery.prop: 119 | 120 | ```javascript 121 | jQuery.attr = function (elem, name, value) { 122 | var ret, hooks, nType = elem.nodeType; 123 | 124 | // 对于 text, comment 和 attribute nodes 不处理 125 | if (nType === 3 || nType === 8 || nType === 2) { 126 | return; 127 | } 128 | 129 | // 如果连这个函数都不支持,还是用 prop 方法吧 130 | if (typeof elem.getAttribute === "undefined") { 131 | return jQuery.prop(elem, name, value); 132 | } 133 | 134 | // 先处理 hooks,优先考虑非正常情况 135 | if (nType !== 1 || !jQuery.isXMLDoc(elem)) { 136 | hooks = jQuery.attrHooks[name.toLowerCase()] || (jQuery.expr.match.bool.test(name) ? boolHook : undefined); 137 | } 138 | // value 为 underfined 的时候调用 remove 139 | if (value !== undefined) { 140 | if (value === null) { 141 | jQuery.removeAttr(elem, name); 142 | return; 143 | } 144 | // hooks.set 145 | if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) { 146 | return ret; 147 | } 148 | // 非 hooks 情况,正常 set 149 | elem.setAttribute(name, value + ""); 150 | return value; 151 | } 152 | // hooks.get 153 | if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) { 154 | return ret; 155 | } 156 | // 正常 get 方法 157 | ret = jQuery.find.attr(elem, name); 158 | 159 | // Non-existent attributes return null, we normalize to undefined 160 | return ret == null ? undefined : ret; 161 | } 162 | ``` 163 | 164 | ```javascript 165 | jQuery.prop = function (elem, name, value) { 166 | var ret, hooks, nType = elem.nodeType; 167 | 168 | // Don't get/set properties on text, comment and attribute nodes 169 | if (nType === 3 || nType === 8 || nType === 2) { 170 | return; 171 | } 172 | 173 | if (nType !== 1 || !jQuery.isXMLDoc(elem)) { 174 | // Fix name and attach hooks 175 | name = jQuery.propFix[name] || name; 176 | hooks = jQuery.propHooks[name]; 177 | } 178 | 179 | if (value !== undefined) { 180 | if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) { 181 | return ret; 182 | } 183 | 184 | return elem[name] = value; 185 | } 186 | 187 | if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) { 188 | return ret; 189 | } 190 | 191 | return elem[name]; 192 | } 193 | ``` 194 | 195 | 可以看得出来,`jQuery.attr` 和 `jQuery.prop` 方法是真的非常像,但是如果你不懂 hooks,可能会有很不疑问,这个不急。可以总结出大致的处理流程:先判断 dom 类型,然后根据一些特殊情况,复制 hooks 参数,这里的特殊条件为 `(nType !== 1 || !jQuery.isXMLDoc(elem))`,接着对于 set 和 get 方法判断,通过 value 值是否为 underfined,如果 hooks 中有,用 hooks 中提供的方法,没有,就走正常流程。 196 | 197 | ## 初识 hooks 198 | 199 | 已经知道在哪里使用 hooks,那么 hooks 长什么样呢: 200 | 201 | ```javascript 202 | jQuery.extend({ 203 | attrHooks: { 204 | // attrHooks 兼容 type 的低版本浏览器的情况 205 | type: { 206 | set: function( elem, value ) { 207 | if ( !support.radioValue && value === "radio" && 208 | jQuery.nodeName( elem, "input" ) ) { 209 | var val = elem.value; 210 | elem.setAttribute( "type", value ); 211 | if ( val ) { 212 | elem.value = val; 213 | } 214 | return value; 215 | } 216 | } 217 | } 218 | }, 219 | propHooks: { 220 | tabIndex: { 221 | get: function( elem ) { 222 | ... 223 | } 224 | } 225 | } 226 | }) 227 | // jQuery 内部扩展 228 | // 对于不支持 selected 的情况 229 | if(!support.optSelected){ 230 | jQuery.propHooks.selected = { 231 | get: function(){ 232 | ... 233 | }, 234 | set: function(){ 235 | 236 | } 237 | } 238 | } 239 | ``` 240 | 241 | 在 attr 的 attrHooks 中,用来处理的特殊情况是 `name=type` 的情况,或许是这种情况,type 绑定不到 html 中。在 prop 的 propHooks 中,处理的特殊情况是 tabIndex,下面还扩展了一个 selected 方法,如果浏览器不支持 select,就建一个 hooks。 242 | 243 | 所以一个基本的 Hooks(jQuery 内部的)应该长这样: 244 | 245 | ```javascript 246 | jQuery.extend({ 247 | nameHooks: { 248 | get: function(){ 249 | ... 250 | }, 251 | set: function(){ 252 | ... 253 | }, 254 | other: function(){ 255 | ... 256 | } 257 | } 258 | }) 259 | ``` 260 | 261 | get 和 set 是非必需的,这是因为 attr 和 prop 的特殊性造成的,在看一个例子 `jQuery.fn.val`,val 的介绍 [jQuery.val](http://www.css88.com/jqapi-1.9/val/),val 也有一个 valHooks 与之对应: 262 | 263 | ```javascript 264 | jQuery.extend({ 265 | valHooks: { 266 | option: { 267 | get: function(){...} 268 | }, 269 | select: { 270 | get: function(){...}, 271 | set: function(){...} 272 | } 273 | } 274 | }) 275 | ``` 276 | 277 | valHooks 和之前略有不同,又多了一层,但基本思路是一致的。 278 | 279 | ## 外部扩展 Hooks 280 | 281 | jQuery 内部的 hooks 功能是非常强大的,不过令人感觉欣慰的是可以在外部扩展。 282 | 283 | 比如有一个问题,我们之前解释 attr 的时候,知道它可以添加 html 的 attribute,但有一些固有的,比如 `class`,我们就是想在它上面添加,但又不能影响原有的 class 属性,可以这样来修改: 284 | 285 | ```javascript 286 | jQuery.attrHooks.class = { 287 | // 这里的参数顺序后面两个是相反的 288 | set: function(elem, value){ 289 | return $(elem).attr('class-sp', value); 290 | }, 291 | get: function(elem){ 292 | return $(elem).attr('class-sp'); 293 | } 294 | } 295 | 296 | //测试 297 | body.attr('class','test'); 298 | // 299 | body.attr('class'); // 'test' 300 | ``` 301 | 302 | perfect! 303 | 304 | ## 总结 305 | 306 | Hooks 讲这么多,应该就 ok 了。Hooks 算是 jQuery 中一个非常可以借鉴的用法,以前听到这个概念是非常恐惧的,当看了源码,弄懂原理之后,发现超级有意思。 307 | 308 | 仍然有不足,比如 jQuery 中一个非常有重量级的 `cssHooks` 就没有提到,还是脚踏实地吧。 309 | 310 | ## 参考 311 | 312 | >[jQuery 2.0.3 源码分析 钩子机制 - 属性操作](http://www.cnblogs.com/aaronjs/p/3387906.html) 313 | 314 | >[jQuery Hooks](https://blog.rodneyrehm.de/archives/11-jQuery-Hooks.html) 315 | 316 | >[jQuery.cssHooks](http://www.css88.com/jqapi-1.9/jQuery.cssHooks/) 317 | 318 | >[jQuery.val](http://www.css88.com/jqapi-1.9/val/) 319 | 320 | >[jQuery hooks源码学习](http://www.cnblogs.com/zzu-han/p/3164947.html) -------------------------------------------------------------------------------- /17-css/README.md: -------------------------------------------------------------------------------- 1 | 样式操作也是 jQuery 比较常用的一个操作,就我本人而言,这个 css 函数用多了,感觉自己有点傻乎乎的,主要还是自己不了解 js 中 css 的真正含义。 2 | 3 | 不过现在不怕了。 4 | 5 | 开始之前,先抛开 jQuery,我们来看看一个有趣的面试题(据说是一道微信面试题)。 6 | 7 | ## 一道很有深度的面试题 8 | 9 | 用原生的 js 获得一个 HTML 元素的 `background-color`,当然,这只是一个引入,为什么不是 color,为什么不是 font-size? 10 | 11 | ### css 渲染的优先级 12 | 13 | 写过 css 样式的同学都知道,css 是有优先级区分的,`!important`优先级最高,內联优先级其次,id,类和伪类,元素和伪元素。优先级会按照顺序依次比较,同一级别相同则看下一级别,优先级相同,后面会覆盖前面。 14 | 15 | 比如,就像理科生排成绩,规定 `总分 > 数学 > 语文 > 英语`,如果 A,B 两人总分一样,就看数学成绩谁高,数学成绩一样,就看语文成绩谁高,以此类推。 16 | 17 | 记得在一家公司实习的时候(初学者),那个时候修改网站的主页样式,由于找不到样式对应,就大量使用 `!important`,并把所有样式都写在样式标的最后,估计,后面接手的人要气炸吧。 18 | 19 | 问题来了,对于任意的一个 elem,DIV 也好,P 也好,都只会保留一个 style 样式表,一般可以通过 `getComputedStyle` 函数或 IE 的 currentStyle 参数活动(万恶的 IE,现在 jQuery 已经不支持低版本 IE,连淘宝都不支持 IE8 了)。**无论这个样式表是通过前面哪个优先级获得的,但它一定是唯一且只有一个。**而且它还是只读的属性,所以通过 JS 来改变样式,如果不能修改 css 文件的情况下,只能采用內联。 20 | 21 | 內联有两种,一种是在 elem 上添加一个 style 属性,还有一种是在 HTMl 新建一个 `