$()函数到底做的什么

  jQuery在前端领域路人皆知,对于一向喜欢玩js的博主来说,虽然能力有限,但是还是很喜欢研究他的做为。那么一个简单的美元符号$与一对常见的()括号,jQuery底层到底做了哪些工作,如果你是前端新人,并喜欢刨根问底,你可以看一下下面的介绍。如果你是有经验的牛人,你可以指出错误,毕竟博主还是个半瓶子醋,没法完全理解。

一、函数调用

$(selector, context) : $()这一个简单的代码,其实调用了jQuery的构造函数,其中的selector和context是传递给jQuery构造函数的参数表达式,前者代表着选择器字符串,后者是上下文,jQuery会根据这两个参数,在页面中找到相应的dom对象,并包装成jQuery对象返回给我们。

var jQuery = function(selector, context) {
return new jQuery.fn.init(selector, context, rootjQuery);
} jQuery.fn = jQuery.prototype = {
init: function (selector, context, rootjQuery) {
// ...
}
} jQuery.fn.init.prototype = jQuery.fn;

调用jQuery()后,返回了一个new jQuery.fn.init(),其实就是jQuery实例,因为jQuery.fn.init 的原型指向了jQuery.fn,也就是jQuery的原型,这部分不需要解释了,是原型链的问题了。

二、步入函数

就这样jQuery进入到了init函数中,接下来看下面代码

// 如果没有参数,返回当前jQuery对像
if (!selector) {
return this;
}
//如果selector有nodeType属性,则是dom对象,返回包装后的jQuery对象
if (selector.nodeType) {
this.context = this[0] = selector;
this.length = 1;
return this;
}
//如果selector是body,由于body只有一个,并且我们可以明确的指出,所以直接把body元素包装后返回jQuery对象
if (selector === "body" && !context && document.body) {
this.context = document;
this[0] = document.body;
this.selector = selector;
this.length = 1;
return this;
}

由此我们可以知道 $()反回了个空的jQuery对象, $(document.body) 走了第二个if返回了body对象的jQuery对象, $('body')走了第三个if也返回了body对象的jQuery对象。

接下来,jQuery构造函数进入的最难的部分,请看着源码来读下面的介绍。

一、如果selector是字符串类型,有两种情况:

1. 如果是dom元素字符串,如$('<div>')  或者 $('<div>', {title: 'good'})等情况,走一种处理方法

2. 如果是单独的div选择器,如$('#box') ,走一种处理方法

3.1. 如果不是上面两种情况,如果context参数存在并且是jQuery对象,则用这个jQuery对象查找selector,如 $('span', $('body')) 相当于 $('body').find('span')

3.2. 如果不是1、2两种情况,并没有context参数,就用根jQuery对象(document的jQuery对象)查找selector,如: $('span') 相当于 $(document).find('span')

4. 其他情况,就是context是dom对象时,用context包装成jQuery对象查找selector, 如$('span', document.body) => $(document.body).find('span')

二、如果selector是函数,那么进入ready方法,等待dom加载完毕执行函数,也就是$(function () {...})

三、如果以上都不是,并且selector有selector和context属性,这代表什么呢?当然是传进来的jQuery对象了,如果是这样,那jQuery返回一个类似的新对象。。。

三、难区详解(selector是字符串类型的1、2两种情况)

  由容易到难,现说是id选择器的情况,再详细说是dom元素字符串的情况。。。

1. selector是个id选择器

elem = document.getElementById(match[2]);

// 黑莓手机兼容解决
if (elem && elem.parentNode) {
// ie 欧朋 选择name解决
if (elem.id !== match[2]) {
return rootjQuery.find(selector);
} this.length = 1; this[0] = elem;
} this.context = document;
this.selector = selector;
return this;

如果是id选择器,就直接选出元素包装成jQuery对象返回。

2. selector是dom元素字符串


这里又分为3种情况:

1. 如果selector是一个简单的dom字符串,如: $('<div>'), $('<div></div>')

  1.1 如果第二个参数context是个纯js对象,也就是键值对,保存着属性和属性值,如$('<div>', {title: 'good'}) ,那么创建dom对象并赋值这些属性

  1.2 如果第二个参数存在并且是dom对象,那么就用这个dom对象创建selector这个dom, 如果context不是dom对象,就用document创建selector这个dom

2. 如果selector并不是一个简单的字符串,其中有属性赋值在里面,如 $('<div id="box">'),进入 jQuery.buildFragment 方法构造dom对象结构

四、dom对象的构造中转站

这里不贴代码了,同学们可以看着源码走下去。

jQuery.buildFragment中,jquery先取到要转化成dom的字符串,和要插入到真是dom的dom对象,也就是context参数或者是document对象。

然后根据各种条件判断,这个dom对象是否是可缓存的,如果是可缓存,查找缓存区是否已经有了,如有有了,直接返回,如果没有,那么进入jQuery.clean方法创建并缓存。

五、dom对象的构造工坊

jQuery.clean 函数中, jQuery还是先确定下要创建的dom字符串数组,和在哪个dom下创建。

进入创建循环,看到这样一句话

if (!rhtml.test(elem)) {
elem = context.createTextNode(elem);
}

博主看rhtml正则,觉得这个是判断是不是实体字符和html标签,如果不是实体字符或html标签,创建文本节点。

往下看,如果不是实体字符,jQuery把这个字符串转化为开闭式的合格dom字符串并且取到了dom的名称

elem = elem.replace(rxhtmlTag, "<$1></$2>");

var tag = (rtagName.exec(elem) || ["", ""])[1].toLowerCase()

由于tr td option等标签,是依赖于上级的标签,所以jQuery在这里需要考虑如果要创建他们,需要先创建他们上级,所以下面代码有个

wrap = wrapMap[tag] || wrapMap._default

那我们去查看wrapMap这个对象是什么

wrapMap = {
option: [1, "<select multiple='multiple'>", "</select>"],
legend: [1, "<fieldset>", "</fieldset>"],
thead: [1, "<table>", "</table>"],
tr: [2, "<table><tbody>", "</tbody></table>"],
td: [3, "<table><tbody><tr>", "</tr></tbody></table>"],
col: [2, "<table><tbody></tbody><colgroup>", "</colgroup></table>"],
area: [1, "<map>", "</map>"],
_default: [0, "", ""]
}

我们来到了这里,看到了这个对象。我们可以看到,这个对象包含了那些需要上级dom才能存活的dom列表,他们的值是一个数组,数组第一个表示的层级关系,比如option是select下的第一级, tr是table下的第二级,数组第二个和第三个元素很明显分别表示了开口标签闭口标签,那么有什么用呢?继续往下看

div.innerHTML = wrap[1] + elem + wrap[2];

看到下面几行,终于茅塞顿开,原来是这样创建的依赖上级元素的 dom元素,前后上级标签中间夹着需要创建dom的字符串,把整个字符串赋值给一个div,构成了完整的dom。知道了后,往回走。

safeChildNodes = safeFragment.childNodes

上面还有一行这个是什么意思呢?我们去看看safeFragment把

//    h5标签
nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", safeFragment = createSafeFragment(document); function createSafeFragment(document) {
var list = nodeNames.split("|"),
safeFrag = document.createDocumentFragment(); if (safeFrag.createElement) {
while (list.length) {
safeFrag.createElement(
list.pop()
);
}
}
return safeFrag;
}

我们找到了这三个重要的表达式,哦,原来这个safeFragment保存着所有h5的标签

if (context === document) {
safeFragment.appendChild(div);
} else {
createSafeFragment(context).appendChild(div);
}

接下来jQuery保存了之前我们创建的div,还不知道要干什么,继续往下看吧。

while (depth--) {
div = div.lastChild;
}

接下来,jQuery根据层级关系,一层一层的把外部容器去掉,只留下最后一层包裹着我们想要的那个dom。

下面,就是对ie的两个兼容,ie下自动生成tobody,jQuery做了兼容,ie下破坏首行缩进,jQuery做了兼容。

elem = div.childNodes;
if (div) {
div.parentNode.removeChild(div); if (safeChildNodes.length > 0) {
remove = safeChildNodes[safeChildNodes.length - 1]; if (remove && remove.parentNode) {
remove.parentNode.removeChild(remove);
}
}
}
}

保存我们需要的dom节点后,把此次循环的div删掉。把插入到safeFragment的div也删掉,清空干净,马上要进入下一循环

var len;
if (!jQuery.support.appendChecked) {
if (elem[0] && typeof(len = elem.length) === "number") {
for (j = 0; j < len; j++) {
findInputs(elem[j]);
}
} else {
findInputs(elem);
}
}

上面这一个if针对的 checkbox和radio元素在ie下checked失效的bug

if (elem.nodeType) {
ret.push(elem);
} else {
ret = jQuery.merge(ret, elem);
}

终于,保存了此次dom元素到ret数组中。然后下面的一个if是对script标签的插入处理,博主没有去看,因为不常用啊。。

终点

  终于,jQuery方法走完了,走的好慢好艰辛,走过了这次行程,下一次是不是就很好走了O(∩_∩)O~。

最新文章

  1. Linux之od命令详解
  2. 自适应学习率调整:AdaDelta
  3. Android 源码下载
  4. Java程序员的发展前景
  5. 【转】介绍几个图论和复杂网络的程序库 —— BGL,QuickGraph,igraph和NetworkX
  6. Handler不同线程间的通信
  7. 怎样学习java?
  8. 笔记:Struts2 的 JSON 插件
  9. 【SPOJ839】Optimal Marks 网络流
  10. Rafy 领域实体框架简介
  11. 虚拟机中克隆后使用eth0
  12. 【转】Java 线程池
  13. Linux 定时运行设置
  14. .NET英文技术文章导读(2017-02-09)
  15. 调整代码生成工具Database2Sharp的Winform界面生成,使其易于列表工具栏的使用。
  16. DataBaseDaoAbstract
  17. mac 下安装ES 与 Head插件 以及安装Kibana
  18. Django 添加应用
  19. 【Spring学习笔记-MVC-13.2】Spring MVC之多文件上传
  20. ORA-00054 资源正忙

热门文章

  1. ZooKeeper(二)Java API使用
  2. QoS policy-map class-map
  3. PCB直角走线的影响
  4. JavaScript中的数据属性和访问器属性
  5. 解决vue跨域axios异步通信
  6. canvas绘制折线图(仿echarts)
  7. MongoDB Python官方驱动 PyMongo 的简单封装
  8. Scala的类继承
  9. Scala的静态方法和实例方法
  10. 9.13 开课第十天(JS脚本语音:语句:循环)