在我的上一篇随笔里面分析了jQuery的构造函数,jQuery对象中有一个原型方法init才是是真正的构造函数,通过init的原型对象跟jQuery的原型对象保持引用关系使得init的实例可以正常调用jQuery的原型方法,就好像是jQuery的实例一样。下面就来看看init这个幕后的构造函数是怎么写的:
init: function( selector, context, rootjQuery ) { ... }
可以看到这个方法接受3个参数,其前两个参数是jQuery方法传递过来的
var jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context, rootjQuery ); },
Selector原则上可以输入任意值,但并不是所有值都是有意义的,只有undefined、DOM 元素、字符串、函数、jQuery 对象、普通 JavaScript 对象这几种类型是有效的,这个参数是通常是填写的但是不填写也不会报错
console.log($()); //[constructor: function, init: function, selector: "", jquery: "1.7.1", size: function…]
Context作为执行上下文或者叫执行范围可以不传入,或者传入 DOM 元素、jQuery 对象、普通 JavaScript 对象之一
参数 rootjQuery:包含了 document 对象的 jQuery 对象,用于 document.getElementById() 查找失败、selector 是选择器表达式且未指定 context、selector 是函数的情况,其实就是$(document)。
下面根据参数的不同分为12种情况逐个讨论
1.selector 可以转换为false
// Handle $(""), $(null), or $(undefined) if ( !selector ) { return this; }
源码中的注释已经写得很清楚了,当是这三种情况时直接return不进行任何处理
2.参数 selector 是 DOM 元素
例如: $(document)这种写法
// Handle $(DOMElement) if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; return this; }
只要是dom元素肯定有节点类型,然后把这个节点变成jquery对象的第一个元素并且赋值给上下文context,length属性是jQuery的原型属性默认为0
// The default length of a jQuery object is 0 length: 0,
这里有了一个元素之后就把length属性修改为1。return this 操作使得函数执行后的结果依然是jQuery对象这样就可以实现类似$(document).each()这样的链式调用了。最终得到的类似这样的{0:document,context:document,length:1....}对象,其实所有的情况最后都会变成这种形式的对象,除了jQuery原型属性和方法之外就是获取的dom节点并且按照阿拉伯数字依次排列,所以我们可以使用$(selector)[0]的形式代替$(selector).get(0)来获取dom对象。例如:
<!doctype html> <html> <head> <title></title> </head> <body> <div></div> <div></div> <div></div> </body> <script src='jquery-1.7.1.js'></script> <script> console.log($('div')); /*[div, div, div, prevObject: jQuery.fn.jQuery.init[1], context: document, selector: "div", constructor: function, init: function…] 0: div 1: div 2: div context: document length: 3 prevObject: jQuery.fn.jQuery.init[1]__proto__: jQuery[0] selector: "div" . */ </script> </html>
3.参数是特殊的字符串“body”
由于body元素在一个文档对象中只有一个所以单独列出来处理
// The body element only exists once, optimize finding it if ( selector === "body" && !context && document.body ) { this.context = document; this[0] = document.body; this.selector = selector; this.length = 1; return this; }
这里有3个条件必须同时满足,第二个必须没有上下文的条件我也不是太理解,$(‘body’,document)这样的看起来很正常的写法也会被这种情况“忽视”
console.log($('body',document)); /* jQuery.fn.jQuery.init[1] 0: body context: document length: 1 prevObject: jQuery.fn.jQuery.init[1] selector: "body" __proto__: jQuery[0] */
虽然和$('body')的结果是一样的,但是却被当做两种情况来看待,可能是因为body只有一个上下文只能是document没有必要添加吧,否则又要判断上下文是不是document。第三个条件是保证document.body必须存在,那么什么情况下会前两个情况满足document.body又不存在呢?首先就是js代码先于html代码加载时会出现这个是初学者经常会犯的错误,通常我们要写成:
$(function(){...})
或者
$(document).ready(function(){...})
其实这两个是一样的调取的是一个方法,dom加载这一块以后在分析。对此我们可以做个测试html代码如下:
<!doctype html>
<html>
<head>
<title></title>
<script src='jquery-1.7.1.js'></script>
<script>
$('body')
</script>
</head>
<body>
<div></div>
<div></div>
<div></div>
</body>
</html>
然后再jQuery源代码里面输出selector、context和document.body
console.log(selector+context+document.body); // The body element only exists once, optimize finding it if ( selector === "body" && !context && document.body ) { this.context = document; this[0] = document.body; this.selector = selector; this.length = 1; return this; }
虽然我们只写了一个其实执行了四次,只有最后一次才是是我们调用后的结果,最后一次的结果是bodyundefinednull这个时候前两个就是满足的但是最后一个是null。回想起第一篇jQuery总体架构架构里面undefined会被重新,那么document.body会不会被重写为null呢?当我尝试在代码中修改时就会报错看来是不会的,那这个条件就是预防没有加载html就执行的情况吧
第四种是除了上述的字符串情况之外的其他字符串,情况比较多放在下一篇吧。