zoukankan      html  css  js  c++  java
  • jQuery1.11源码分析(3)-----Sizzle源码中的浏览器兼容性检测和处理[原创]

    上一章讲了正则表达式,这一章继续我们的前菜,浏览器兼容性处理。

    先介绍一个简单的沙盒测试函数。

    /**
     * Support testing using an element
     * @param {Function} fn Passed the created div and expects a boolean result
     */
    //特性检测的一个技巧,造一个div,最后将其删掉,提供一种简单的沙盒(如果该div不加入到DOM树上,则部分属性如currentStyle会没有)
    function assert( fn ) {
    	var div = document.createElement("div");
    
    	try {
    		return !!fn( div );
    	} catch (e) {
    		return false;
    	} finally {
    		// Remove from its parent by default
    		if ( div.parentNode ) {
                //为了不造成影响,最后如果该div还在DOM树上,就将其移除掉
    			div.parentNode.removeChild( div );
    		}
    		// release memory in IE
            // IE下对于不在DOM树里的DOM节点必须手动置为null,关于IE内存泄露的文章很多,不再赘述
    		div = null;
    	}
    }
    

    嗯,说明如注释,接下来我们先处理push函数,有些浏览器Nodelist是不能使用push的,我们先检测一下在当前浏览器下push能不能支持Nodelist,不能的话则把push换成自己的方法,使push能够支持Nodelist

    // Optimize for push.apply( _, NodeList )
    //居然还可以对push进行优化,看看push支不支持Nodelist
    //因为Nodelist不是数组,所以在某些浏览器下没有Push方法,需要人工造一个支持nodelist的push方法。
    try {
    	push.apply(
    		(arr = slice.call( preferredDoc.childNodes )),
    		preferredDoc.childNodes
    	);
    	// Support: Android < 4.0
    	// Detect silently failing push.apply
        //防止安卓4.0以下版本静默失败(失败了但不报错)。
    	arr[ preferredDoc.childNodes.length ].nodeType;
    } catch ( e ) {
        //如果arr有值,即arr.length > 0,说明上述push方法是有效的,使用原生API,否则换成自己的方法
    	push = { apply: arr.length ?
    		// Leverage slice if possible
    		function( target, els ) {
                //这里一定要用apply,否则会把整个els给Push进去而不是拆成一个个push
    			push_native.apply( target, slice.call(els) );
    		} :
    
    		// Support: IE<9
    		// Otherwise append directly
            //这里不明白为什么。。
            //因为在IE下对Nodelist执行slice会报错找不到JScript对象,所以arr.length为0
            //http://www.jb51.net/article/24182.htm
    		function( target, els ) {
    			var j = target.length,
    				i = 0;
    			// Can't trust NodeList.length
                //为什么IE8下不能相信长度?
    			while ( (target[j++] = els[i++]) ) {}
    			target.length = j - 1;
    		}
    	};
    }
    

    说明如注释,然后再来三发对于html元素向DOM元素转换时,部分属性会遇到的bug以及处理

    // Support: IE<8
    // Prevent attribute/property "interpolation"
    // http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
    //在IE<8下会自动串改部分属性,比如下面的href会改成当前地址+#,大家可以试试。
    //出现这种情况后,当我们要取某些属性时,交给代理函数进行处理,比如Expr.attrHandle
    if ( !assert(function( div ) {
    	div.innerHTML = "<a href='#'></a>";
    	return div.firstChild.getAttribute("href") === "#" ;
    }) ) {
    	addHandle( "type|href|height|width", function( elem, name, isXML ) {
    		if ( !isXML ) {
                //getAttribute我搜了一下。。没有第二个参数,还没翻标准。。暂且认为是错的。
    //我靠。。还真有第二个参数。。http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } }); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") if ( !support.attributes || !assert(function( div ) { div.innerHTML = "<input/>"; div.firstChild.setAttribute( "value", "" ); return div.firstChild.getAttribute( "value" ) === ""; }) ) { addHandle( "value", function( elem, name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } }); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies //使用getAttribute获取那些非attrName=attrValue形式的attr属性时,会出错,所以换成使用getAttributeNode,然后判断获得的Attr节点的specified属性,看看是否指定。 if ( !assert(function( div ) { return div.getAttribute("disabled") == null; }) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : (val = elem.getAttributeNode( name )) && val.specified ? val.value : null; } }); }

    说明如注释,接下来我们先声明一个对象,这个对象类似于配置文件(config)的作用,嗯。。配置文件通常很长

    Expr = Sizzle.selectors = {
    
    	// Can be adjusted by the user
        //缓存的长度
    	cacheLength: 50,
        //用来标识奇葩函数
    	createPseudo: markFunction,
        //存放用来匹配的表达式
    	match: matchExpr,
        //前面说过的,存放提取部分特殊属性的handle
    	attrHandle: {},
        //查找过程所用的函数就存在这,后面会进行声明
    	find: {},
        //前面说个token中有一种类型是表示两个元素的关系,这个关系处理函数用relative保存起来,first用来表示是否是查找到的第一个元素。
        //例如假设tokens[i]是{type:'>',value:'>'},调用body[Expr.relative[tokens[i].type][dir]],
    	relative: {
    		">": { dir: "parentNode", first: true },
    		" ": { dir: "parentNode" },
    		"+": { dir: "previousSibling", first: true },
    		"~": { dir: "previousSibling" }
    	},
    

    在Expr里还有三个部分:preFilter(用来对捕获组进行预处理),filter(返回一个个matcher,最后将多个matcher编译成一个),pseudo(其实就是filter的一种),后续可能留出篇幅来进行讲解。

    接下来我们休息一下,再一口气啃掉一个超长的初始化函数。这个初始化的主要作用是从健壮性的考虑出发,设置一下文档节点,检查一下是不是HTML,检查一下各个原生API是不是好用,检查一下querySelectorAll是不是好用,初始化查找过程用的函数(比如Expr.find['ID']);

    /**
     * Sets document-related variables once based on the current document
     * @param {Element|Object} [doc] An element or document object to use to set the document
     * @returns {Object} Returns the current document
     */
    //基于当前文档节点设置一些文档相关的内容,如支持性什么的。
    setDocument = Sizzle.setDocument = function( node ) {
        console.log('setDocument in');
    	var hasCompare,
    		doc = node ? node.ownerDocument || node : preferredDoc,         //获得文档节点的一种方式,这样写的好处是保持健壮性
    		parent = doc.defaultView;
    
    	// If no document and documentElement is available, return
        //nodeType不等于9说明不是文档节点
        //做这么多判断。。嗯。。还是为了健壮性
    	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
    		return document;
    	}
    
    	// Set our document
    	document = doc;
    	docElem = doc.documentElement;
    
    	// Support tests
    	documentIsHTML = !isXML( doc );
    
    	// Support: IE>8
    	// If iframe document is assigned to "document" variable and if iframe has been reloaded,
    	// IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
    	// IE6-8 do not support the defaultView property so parent will be undefined
        //这里是一个jQuery的bug修复
    	if ( parent && parent !== parent.top ) {
    		// IE11 does not have attachEvent, so all must suffer
    		if ( parent.addEventListener ) {
    			parent.addEventListener( "unload", function() {
    				setDocument();
    			}, false );
    		} else if ( parent.attachEvent ) {
    			parent.attachEvent( "onunload", function() {
    				setDocument();
    			});
    		}
    	}
    
    	/* Attributes
    	---------------------------------------------------------------------- */
    
    	// Support: IE<8
    	// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
        //如果这里检测通过的话,后面获取属性值用的就是getAttribute这个API,否则用的就是getAttributeNode
    	support.attributes = assert(function( div ) {
    		div.className = "i";
    		return !div.getAttribute("className");
    	});
    
    	/* getElement(s)By*
    	---------------------------------------------------------------------- */
    
    	// Check if getElementsByTagName("*") returns only elements
        //检查getElementsByTagName是否会包含注释节点,最好不要包含。
    	support.getElementsByTagName = assert(function( div ) {
    		div.appendChild( doc.createComment("") );
    		return !div.getElementsByTagName("*").length;
    	});
    
    	// Check if getElementsByClassName can be trusted
        //?????细节
    	support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) {
    		div.innerHTML = "<div class='a'></div><div class='a i'></div>";
    
    		// Support: Safari<4
    		// Catch class over-caching
    		div.firstChild.className = "i";
    		// Support: Opera<10
    		// Catch gEBCN failure to find non-leading classes
            //Opera<10 的时候当一个元素有多个class的时候,获得第二个会出错。
    		return div.getElementsByClassName("i").length === 2;
    	});
    	// Support: IE<10
    	// Check if getElementById returns elements by name
    	// The broken getElementById methods don't pick up programatically-set names,
    	// so use a roundabout getElementsByName test
        //????所以迂回使用getElementsByName来检测?不明原理
    	support.getById = assert(function( div ) {
    		docElem.appendChild( div ).id = expando;
    		return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
    	});
        //Expr里的函数分为查找和过滤两种功能类型。
        //接下来分别进行这两种函数功能性的检测和支持
    	// ID find and filter
    	if ( support.getById ) {
    
    		Expr.find["ID"] = function( id, context ) {
                //??????为什么要转换成字符串形式的undefined
    			if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
    				var m = context.getElementById( id );
    				// Check parentNode to catch when Blackberry 4.6 returns
    				// nodes that are no longer in the document #6963
                    //!!!!在黑莓4.6的浏览器中会返回那些不在DOM树中的节点,所以通过该节点是否有父节点来判断该节点是否在DOM树中,bug修正,并且转换为数组形式。
    				return m && m.parentNode ? [m] : [];
    			}
    		};
            //这里返回一个后面用来做matcher的函数
    		Expr.filter["ID"] = function( id ) {
                //对id做编码转换,这是一种闭包类型。
    			var attrId = id.replace( runescape, funescape );
    			return function( elem ) {
    				return elem.getAttribute("id") === attrId;
    			};
    		};
    	} else {
    		// Support: IE6/7
    		// getElementById is not reliable as a find shortcut
            //???getElementById不可靠??
            //!!!!删掉以后,Sizzle就不再通过ID来获取节点,获取属性的方式也由getAttribute变为getAttributeNode
    		delete Expr.find["ID"];
    
    		Expr.filter["ID"] =  function( id ) {
    			var attrId = id.replace( runescape, funescape );
    			return function( elem ) {
                    //每个DOM元素下有隐含的属性节点,通过查看其属性节点的方式来过滤
    				var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
    				return node && node.value === attrId;
    			};
    		};
    	}
    
    	// Tag
    	Expr.find["TAG"] = support.getElementsByTagName ?
    		function( tag, context ) {
                console.log('find tag begin');
    			if ( typeof context.getElementsByTagName !== strundefined ) {
    				return context.getElementsByTagName( tag );
    			}
    
    		} :
            //否则采用过滤掉注释节点的写法。
    		function( tag, context ) {
                console.log('find tag begin');
    			var elem,
    				tmp = [],
    				i = 0,
    				results = context.getElementsByTagName( tag );
    
    			// Filter out possible comments
                //必须过滤注释节点
    			if ( tag === "*" ) {
                    //赋值判断写法。
    				while ( (elem = results[i++]) ) {
    					if ( elem.nodeType === 1 ) {
    						tmp.push( elem );
    					}
    				}
    
    				return tmp;
    			}
    			return results;
    		};
    
    	// Class
    	Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
            console.log('find class begin');
    		if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
    			return context.getElementsByClassName( className );
    		}
    	};
    
    
    	/* QSA/matchesSelector
    	---------------------------------------------------------------------- */
    
    	// QSA and matchesSelector support
    
    	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
    	rbuggyMatches = [];
    
    	// qSa(:focus) reports false when true (Chrome 21)
    	// We allow this because of a bug in IE8/9 that throws an error
    	// whenever `document.activeElement` is accessed on an iframe
    	// So, we allow :focus to pass through QSA all the time to avoid the IE error
    	// See http://bugs.jquery.com/ticket/13378
        //用来存放有bug的QSA字符串,最后用|连接起来当作正则表达式,用来检测选择符是否有bug
    	rbuggyQSA = [];
    
    	if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
    		// Build QSA regex
    		// Regex strategy adopted from Diego Perini
            //这两个assert没有返回值,主要是把有bug的QSA字符串检测出来。
    		assert(function( div ) {
    			// Select is set to empty string on purpose
    			// This is to test IE's treatment of not explicitly
    			// setting a boolean content attribute,
    			// since its presence should be enough
    			// http://bugs.jquery.com/ticket/12359
                //这个bug检测很简练
    			div.innerHTML = "<select>
    <option selected="selected"></option>
    
    
    
    
    </select>";
    
    			// Support: IE8, Opera 10-12
    			// Nothing should be selected when empty strings follow ^= or $= or *=
                //!!!空白字符串不应该跟在 ^=、$=、*=这样的字符后面,否则逻辑上是走不通的,你想想啊,^=''的意思是匹配开头为空字符的字符串。。哪个字符串开头是空字符?,剩下同理
    			if ( div.querySelectorAll("[t^='']").length ) {
    				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|"")" );
    			}
    
    			// Support: IE8
    			// Boolean attributes and "value" are not treated correctly
                //!!!IE8中的QSA不能正确识别非key=value形式的属性选择符
    			if ( !div.querySelectorAll("[selected]").length ) {
    				rbuggyQSA.push( "\[" + whitespace + "*(?:value|" + booleans + ")" );
    			}
    
    			// Webkit/Opera - :checked should return selected option elements
    			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
    			// IE8 throws error here and will not see later tests
                //!!!!:checked伪类选择器理论应该返回被选择的option元素,但IE8下有bug。。
    			if ( !div.querySelectorAll(":checked").length ) {
    				rbuggyQSA.push(":checked");
    			}
    		});
    
    		assert(function( div ) {
    			// Support: Windows 8 Native Apps
    			// The type and name attributes are restricted during .innerHTML assignment
                //????卧槽。。win8不能直接用innerHTML来创建type和name属性?
    			var input = doc.createElement("input");
    			input.setAttribute( "type", "hidden" );
    			div.appendChild( input ).setAttribute( "name", "D" );
    
    			// Support: IE8
    			// Enforce case-sensitivity of name attribute
                //!!!!增强对name大小写的敏感性,如果大小写不敏感,则不能使用带有name=.的属性选择符
    			if ( div.querySelectorAll("[name=d]").length ) {
    				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
    			}
    
    			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
    			// IE8 throws error here and will not see later tests
                //!!!!理论上hidden的元素还应该能用:enabled伪类选择符选择到的,但IE8和FF3.5下有bug
    			if ( !div.querySelectorAll(":enabled").length ) {
    				rbuggyQSA.push( ":enabled", ":disabled" );
    			}
    
    			// Opera 10-11 does not throw on post-comma invalid pseudos
                //?????
    			div.querySelectorAll("*,:x");
    			rbuggyQSA.push(",.*:");
    		});
    	}
    
    	if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector ||
    		docElem.mozMatchesSelector ||
    		docElem.oMatchesSelector ||
    		docElem.msMatchesSelector) )) ) {
    
    		assert(function( div ) {
    			// Check to see if it's possible to do matchesSelector
    			// on a disconnected node (IE 9)
                //!!!!检测是否matchesSelector会匹配到没有连接的节点
    			support.disconnectedMatch = matches.call( div, "div" );
    
    			// This should fail with an exception
    			// Gecko does not error, returns false instead
                //?????
    			matches.call( div, "[s!='']:x" );
    			rbuggyMatches.push( "!=", pseudos );
    		});
    	}
    
    	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
    	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
    
    	/* Contains
    	---------------------------------------------------------------------- */
    	hasCompare = rnative.test( docElem.compareDocumentPosition );
    
    	// Element contains another
    	// Purposefully does not implement inclusive descendent
    	// As in, an element does not contain itself
        //这里和懒函数的方式异曲同工,如果有原生compareDocumentPosition或contains
    	contains = hasCompare || rnative.test( docElem.contains ) ?
    		function( a, b ) {
                console.log('contains begin');
    			var adown = a.nodeType === 9 ? a.documentElement : a,
    				bup = b && b.parentNode;
    			return a === bup || !!( bup && bup.nodeType === 1 && (
    				adown.contains ?
    					adown.contains( bup ) :
                        //这里&上16仅仅是判断a是否包含bup,b如果是a本身,则两者应该不算包含关系
                        //http://www.2cto.com/kf/201301/181075.html
    					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
    			));
    		} :
            //否则就用土办法不断遍历DOM树
    		function( a, b ) {
    			if ( b ) {
    				while ( (b = b.parentNode) ) {
    					if ( b === a ) {
    						return true;
    					}
    				}
    			}
    			return false;
    		};
    
    	/* Sorting
    	---------------------------------------------------------------------- */
    
    	// Document order sorting
        //用来传给sort函数的排序方法
    	sortOrder = hasCompare ?
    	function( a, b ) {
    		// Flag for duplicate removal
    		if ( a === b ) {
    			hasDuplicate = true;
    			return 0;
    		}
    
    		// Sort on method existence if only one input has compareDocumentPosition
            //!undefined === true
            //!function (){} === false
            //if(-1){console.log(1);} -->1
            //用以检查是否两个输入都有该API
    		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
    		if ( compare ) {
    			return compare;
    		}
    
    		// Calculate position if both inputs belong to the same document
    		compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
    			a.compareDocumentPosition( b ) :
    
    			// Otherwise we know they are disconnected
    			1;
    
    		// Disconnected nodes
    		if ( compare & 1 ||
    			(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
    
    			// Choose the first element that is related to our preferred document
    			if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
    				return -1;
    			}
    			if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
    				return 1;
    			}
    
    			// Maintain original order
    			return sortInput ?
                    //最后这里处理了给字符串调用sort的情况
                    //后面有一句代码
                    //support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
    				( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
    				0;
    		}
    
    		return compare & 4 ? -1 : 1;
    	} :
        //如果没有contains这样的原生API使用
    	function( a, b ) {
    		// Exit early if the nodes are identical
    		if ( a === b ) {
    			hasDuplicate = true;
    			return 0;
    		}
    
    		var cur,
    			i = 0,
    			aup = a.parentNode,
    			bup = b.parentNode,
    			ap = [ a ],
    			bp = [ b ];
    
    		// Parentless nodes are either documents or disconnected
            //只要其中有一个元素没有父元素
    		if ( !aup || !bup ) {
    			return a === doc ? -1 :
    				b === doc ? 1 :
    				aup ? -1 :
    				bup ? 1 :
    				sortInput ?
    				( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
    				0;
    
    		// If the nodes are siblings, we can do a quick check
            //或者两个元素的父元素是兄弟,就可以立马判定
    		} else if ( aup === bup ) {
    			return siblingCheck( a, b );
    		}
    
    		// Otherwise we need full lists of their ancestors for comparison
            //否则遍历出所有祖先路径,然后一一对比,直到找到分歧点,再比较分歧点的序数即可
    		cur = a;
    		while ( (cur = cur.parentNode) ) {
    			ap.unshift( cur );
    		}
    		cur = b;
    		while ( (cur = cur.parentNode) ) {
    			bp.unshift( cur );
    		}
    
    		// Walk down the tree looking for a discrepancy
    		while ( ap[i] === bp[i] ) {
    			i++;
    		}
    
    		return i ?
    			// Do a sibling check if the nodes have a common ancestor
    			siblingCheck( ap[i], bp[i] ) :
    
    			// Otherwise nodes in our document sort first
    			ap[i] === preferredDoc ? -1 :
    			bp[i] === preferredDoc ? 1 :
    			0;
    	};
        //最后返回文档节点
    	return doc;
    };
    

    上面这个初始化函数实在太长。。再调用一下就好。

    // Initialize against the default document
    setDocument();
    
  • 相关阅读:
    Codeforces467C George and Job
    Codeforces205E Little Elephant and Furik and RubikLittle Elephant and Furik and Rubik
    Codeforce205C Little Elephant and Interval
    51nod1829 函数
    51nod1574 排列转换
    nowcoder35B 小AA的数列
    Codeforce893E Counting Arrays
    gym101612 Consonant Fencity
    CodeForces559C Gerald and Giant Chess
    CodeForces456D A Lot of Games
  • 原文地址:https://www.cnblogs.com/suprise/p/3624339.html
Copyright © 2011-2022 走看看