├── 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 | 
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 | 
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 新建一个 `