一、从function JQLite(element)函数开始。

function JQLite(element) {
if (element instanceof JQLite) { //情况1
return element;
} var argIsString; if (isString(element)) { //情况2
element = trim(element); //先去掉两头的空格、制表等字符
argIsString = true;
}
if (!(this instanceof JQLite)) {
if (argIsString && element.charAt(0) != '<') { //判断第一个字符,是不是'<'开动
throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
}
return new JQLite(element); //将自身作为构造函数重新调用
} //作为构造函数主要执行的部分
if (argIsString) {
jqLiteAddNodes(this, jqLiteParseHTML(element));
} else {
jqLiteAddNodes(this, element);
}
}

这段代码分两种情况处理:情况1,传入的参数已经是一个JQLite对象,直接返回;情况2,传入的是不是一个JQLite对象,若是字符串,先判断第一个字符如果不是"<"抛出错误,将自己作为构造函数重新调用。

如果是字符串,先调用jqLiteParseHTML将字符串解析为一个element。

二、jqLiteParseHTML函数

function jqLiteParseHTML(html, context) {
context = context || document; //上面的代码没有传入content,那么context = document;
var parsed; if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
return [context.createElement(parsed[1])]; //对于没有属性和子几点得元素,直接调用createElement方法创建出来就行了
} if ((parsed = jqLiteBuildFragment(html, context))) {
return parsed.childNodes;
} return [];
}

var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;这个正则表达式分析,可得它将匹配一个没有属性的和子节点的元素,如果"< input />"或者"<div></div>"。而对于没有属性和子几点得元素,直接调用createElement方法创建出来就行了。不然就只有调用jqLiteBuildFragment,开始复杂的构造了。

function jqLiteBuildFragment(html, context) {
var tmp, tag, wrap,
fragment = context.createDocumentFragment(), //首先创建一个碎片元素作为载体
nodes = [], i; if (jqLiteIsTextNode(html)) {
// Convert non-html into a text node
nodes.push(context.createTextNode(html));
} else {
// Convert html into DOM nodes
tmp = tmp || fragment.appendChild(context.createElement("div"));
tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
wrap = wrapMap[tag] || wrapMap._default;
tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];//对应的元素用对应的标签包裹起来。 // Descend through wrappers to the right content
i = wrap[0];
while (i--) {
tmp = tmp.lastChild;
} nodes = concat(nodes, tmp.childNodes); tmp = fragment.firstChild;
tmp.textContent = "";
} // Remove wrapper from fragment
fragment.textContent = "";
fragment.innerHTML = ""; // Clear inner HTML
forEach(nodes, function(node) {
fragment.appendChild(node);
}); return fragment;
}

函数首先创建一个碎片元素作为载体,然后用function jqLiteIsTextNode(html) { return !HTML_REGEXP.test(html);}判断元素是不是文本元素,如果是,加入到nodes这个临时缓存,后面再处理。我们来分析一下var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;这个复杂的正则表达式,第一是以"<"开头,第二是预搜索,表示接在"<"后面的不能是area、br、col、embed、hr、img、input、link、meta、param,第三是结尾以"/>"结尾。那么这个表达式将匹配第二中排除的自闭合标签的 而写成了自闭合标签的元素。而html.replace(XHTML_TAG_REGEXP, "<$1></$2>"),就是按照xhtml规范,将这些标签给改回到非自闭合的状态。

三、函数jqLiteAddNodes

function jqLiteAddNodes(root, elements) {
// THIS CODE IS VERY HOT. Don't make changes without benchmarking. //这段代码将会被频繁调用,没有特别需要不要修改 if (elements) { // if a Node (the most common case)
if (elements.nodeType) {
root[root.length++] = elements;
} else {
var length = elements.length; // if an Array or NodeList and not a Window
if (typeof length === 'number' && elements.window !== elements) {
if (length) {
for (var i = 0; i < length; i++) {
root[root.length++] = elements[i];
}
}
} else {
root[root.length++] = elements;
}
}
}
}

通过上面的这段代码,最终将dom元素转变成了JQLite数组。

四、JQLite的原型:JQLitePrototype

1.给原型绑定函数

var JQLitePrototype = JQLite.prototype = {
ready: function(fn) { //定义ready函数
var fired = false; function trigger() {
if (fired) return;
fired = true;
fn();
} // check if document is already loaded
if (document.readyState === 'complete') { //dom已经加载完
setTimeout(trigger);
} else {
this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 //监听dom加载完
// we can not use jqLite since we are not done loading and jQuery could be loaded later.
// jshint -W064
JQLite(window).on('load', trigger); // fallback to window.onload for others
// jshint +W064
}
},
toString: function() {
var value = [];
forEach(this, function(e) { value.push('' + e);});
return '[' + value.join(', ') + ']';
}, eq: function(index) { //定义eq
return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
}, length: 0,
push: push,
sort: [].sort,
splice: [].splice
};

在这里,代码向JQLite的原型上绑定了几个基本的函数。集中ready用于等待dom加载完成,开始整个程序的执行。eq用于索引JQLite数组的元素。

2.向原型绑定更多的函数

forEach({
data: jqLiteData,
inheritedData: jqLiteInheritedData, scope: function(element) {...}, isolateScope: function(element) {...}, controller: jqLiteController, injector: function(element) {...}, removeAttr: function(element, name) {...}, hasClass: jqLiteHasClass, css: function(element, name, value) {...}, attr: function(element, name, value) {...}, prop: function(element, name, value) {...}, text: (function() {...}, html: function(element, value) {...}, empty: jqLiteEmpty
}, function(fn, name) {
/**
* Properties: writes return selection, reads return first value
*/
JQLite.prototype[name] = function(arg1, arg2) {
var i, key;
var nodeCount = this.length; // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
// in a way that survives minification.
// jqLiteEmpty takes no arguments but is a setter.
if (fn !== jqLiteEmpty &&
(isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
if (isObject(arg1)) { // we are a write, but the object properties are the key/values
for (i = 0; i < nodeCount; i++) {
if (fn === jqLiteData) {
// data() takes the whole object in jQuery
fn(this[i], arg1);
} else {
for (key in arg1) {
fn(this[i], key, arg1[key]);
}
}
}
// return self for chaining
return this;
} else {
// we are a read, so read the first child.
// TODO: do we still need this?
var value = fn.$dv;
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
for (var j = 0; j < jj; j++) {
var nodeValue = fn(this[j], arg1, arg2);
value = value ? value + nodeValue : nodeValue;
}
return value;
}
} else {
// we are a write, so apply to all children
for (i = 0; i < nodeCount; i++) {
fn(this[i], arg1, arg2);
}
// return self for chaining
return this;
}
};
});

3.继续绑定

forEach({
removeData: jqLiteRemoveData,
on: function jqLiteOn(element, type, fn, unsupported) {...}, off: jqLiteOff, one: function(element, type, fn) {...}, replaceWith: function(element, replaceNode) {...}, children: function(element) {...}, contents: function(element) {...}, append: function(element, node) {...}, prepend: function(element, node) {...}, wrap: function(element, wrapNode) {...}, remove: jqLiteRemove, detach: function(element) {...}, after: function(element, newElement) {...}, addClass: jqLiteAddClass,
removeClass: jqLiteRemoveClass, toggleClass: function(element, selector, condition) {...}, parent: function(element) {...}, next: function(element) {...}, find: function(element, selector) {...}, clone: jqLiteClone, triggerHandler: function(element, event, extraParameters) {...}
}, function(fn, name) {
/**
* chaining functions
*/
JQLite.prototype[name] = function(arg1, arg2, arg3) {
var value; for (var i = 0, ii = this.length; i < ii; i++) {
if (isUndefined(value)) {
value = fn(this[i], arg1, arg2, arg3);
if (isDefined(value)) {
// any function which returns a value needs to be wrapped
value = jqLite(value);
}
} else {
jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
}
}
return isDefined(value) ? value : this;
}; // bind legacy bind/unbind to on/off
JQLite.prototype.bind = JQLite.prototype.on;
JQLite.prototype.unbind = JQLite.prototype.off;
});

五、$$jqLite service

// Provider for private $$jqLite service
function $$jqLiteProvider() {
this.$get = function $$jqLite() {
return extend(JQLite, {
hasClass: function(node, classes) {
if (node.attr) node = node[0];
return jqLiteHasClass(node, classes);
},
addClass: function(node, classes) {
if (node.attr) node = node[0];
return jqLiteAddClass(node, classes);
},
removeClass: function(node, classes) {
if (node.attr) node = node[0];
return jqLiteRemoveClass(node, classes);
}
});
};
}

六、jqLiteClone、HTML5、IE8加载一起的坑

function jqLiteClone(element) {
return element.cloneNode(true);
}

这里可以看到,它直接调用了element.cloneNode。而在ie8下这个方法在复制H5新元素(section,footer,header,em等)时,会自动变成“:element”(即:section,:footer,:header,:em),而angular中ng-if,ng-repeat等都使用了jqLiteClone。这就会导致css选择器失败,样式就变得不堪入目了。笔者阅读了jQuery的源码,结果发现它依然是一个坑,一层h5元素的情况处理了,多层的确没有处理。并且这个bug官方也貌似没打算修复。不得已,写了一个修复文件: ie8_ele_clone.js,并且把angular的jqLiteClone函数改了。

//修复ie8上的clone html5 错误问题
'use strict'; function ie8_ele_clone(element){
function createSafeFragment( document ) {
var list = nodeNames.split( "|" ),
safeFrag = document.createDocumentFragment(); if ( safeFrag.createElement ) {
while ( list.length ) {
safeFrag.createElement(
list.pop()
);
}
}
return safeFrag;
} var html5Clone =
document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav></:nav>",
nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
safeFragment = createSafeFragment( document ),
fragmentDiv = safeFragment.appendChild( document.createElement("div") ); if(html5Clone){
return element.cloneNode(true);
} function copy(elem){
var clone; if(rnoshimcache.test( "<" + elem.nodeName + ">" )){
fragmentDiv.innerHTML = elem.outerHTML;
fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
}
else
{
clone = elem.cloneNode(true);
} for(var i = 0; i < elem.children.length ; i ++){
var tmp_node = elem.children[i];
if(tmp_node.children.length == 0 && !rnoshimcache.test( "<" + tmp_node.nodeName + ">" ))continue;
var copy_node = copy(tmp_node); var clone_replace = clone.children[i];
clone.insertBefore(copy_node,clone_replace);
clone.removeChild(clone_replace);
}
return clone;
} return copy(element);
};

改后的jqLiteClone函数:

function jqLiteClone(element) {
if(typeof ie8_ele_clone == 'function'){
return ie8_ele_clone(element);
}
else
{
return element.cloneNode(true);
}
}

上一期:angular源码分析:angular的源代码目录结构说明

下一期:angular源码分析:injector.js文件分析——angular中的依赖注入式如何实现的(续)

ps,在《angular源码分析:injector.js文件分析——angular中的依赖注入式如何实现的(续)》中,我们补充讲解了《angular中的依赖注入式如何实现的》中没有讲到的部分,还有provider的各种语法糖。

angular源码分析:angular中jqLite的实现——你可以丢掉jQuery了的更多相关文章

  1. angular源码分析:angular中脏活累活承担者之$parse

    我们在上一期中讲 $rootscope时,看到$rootscope是依赖$prase,其实不止是$rootscope,翻看angular的源码随便翻翻就可以发现很多地方是依赖于$parse的.而$pa ...

  2. angular源码分析:angular中入境检察官$sce

    一.ng-bing-html指令问题 需求:我需要将一个变量$scope.x = '<a href="http://www.cnblogs.com/web2-developer/&qu ...

  3. angular源码分析:angular中$rootscope的实现——scope的一生

    在angular中,$scope是一个关键的服务,可以被注入到controller中,注入其他服务却只能是$rootscope.scope是一个概念,是一个类,而$rootscope和被注入到cont ...

  4. angular源码分析:injector.js文件分析——angular中的依赖注入式如何实现的(续)

    昨天晚上写完angular源码分析:angular中jqLite的实现--你可以丢掉jQuery了,给今天定了一个题angular源码分析:injector.js文件,以及angular的加载流程,但 ...

  5. angular源码分析:angular中各种常用函数,比较省代码的各种小技巧

    angular的工具函数 在angular的API文档中,在最前面就是讲的就是angular的工具函数,下面列出来 angular.bind //用户将函数和对象绑定在一起,返回一个新的函数 angu ...

  6. angular源码分析:angular中脏活累活的承担者之$interpolate

    一.首先抛出两个问题 问题一:在angular中我们绑定数据最基本的方式是用两个大括号将$scope的变量包裹起来,那么如果想将大括号换成其他什么符号,比如换成[{与}],可不可以呢,如果可以在哪里配 ...

  7. angular源码分析:angular中的依赖注入式如何实现的

    一.准备 angular的源码一份,我这里使用的是v1.4.7.源码的获取,请参考我另一篇博文:angular源码分析:angular源代码的获取与编译环境安装 二.什么是依赖注入 据我所知,依赖注入 ...

  8. angular源码分析:angular的整个加载流程

    在前面,我们讲了angular的目录结构.JQLite以及依赖注入的实现,在这一期中我们将重点分析angular的整个框架的加载流程. 一.从源代码的编译顺序开始 下面是我们在目录结构哪一期理出的an ...

  9. angular源码分析:angular的源代码目录结构说明

    一.读源码,是选择"编译合并后"的呢还是"编译前的"呢? 有朋友说,读angular源码,直接看编译后的,多好,不用管模块间的关系,从上往下读就好了.但是在我看 ...

随机推荐

  1. 开发高效的Tag标签系统数据库设计

    需求背景 目前主流的博客系统.CMS都会有一个TAG标签系统,不仅可以让内容链接的结构化增强,而且可以让文章根据Tag来区分.相比传统老式的Keyword模式,这种Tag模式可以单独的设计一个Map的 ...

  2. unity3d中 刚体(Rigidbody) 碰撞体(Collider) 触发器(Is Trigger)

      刚体(Rigidbody)的官方(摘自Unity3d的官方指导书<Unity4.x从入门到精通>)解释如下: Rigidbody(刚体)组件可使游戏对象在物理系统的控制下来运动,刚体可 ...

  3. PetaPojo —— JAVA版的PetaPoco

    背景 由于工作的一些原因,需要从C#转成JAVA.之前PetaPoco用得真是非常舒服,在学习JAVA的过程中熟悉了一下JAVA的数据组件: MyBatis 非常流行,代码生成也很成熟,性能也很好.但 ...

  4. Windows Azure Virtual Machine (32) 如何在Windows操作系统配置SFTP

    <Windows Azure Platform 系列文章目录> 下载地址:http://files.cnblogs.com/files/threestone/Windows_SFTP.pd ...

  5. SQL Server代理(6/12):作业里的工作流——深入作业步骤

    SQL Server代理是所有实时数据库的核心.代理有很多不明显的用法,因此系统的知识,对于开发人员还是DBA都是有用的.这系列文章会通俗介绍它的很多用法. 如我们在这里系列的前几篇文章所见,SQL ...

  6. mysql基于init-connect+binlog完成审计功能

    目前社区版本的mysql的审计功能还是比较弱的,基于插件的审计目前存在于Mysql的企业版.Percona和MariaDB上,但是mysql社区版本有提供init-connect选项,基于此我们可以用 ...

  7. Maven提高篇系列之(四)——使用Profile

    这是一个Maven提高篇的系列,包含有以下文章: Maven提高篇系列之(一)——多模块 vs 继承 Maven提高篇系列之(二)——配置Plugin到某个Phase(以Selenium集成测试为例) ...

  8. 基于git的源代码管理模型——git flow

    基于git的源代码管理模型--git flow A successful Git branching model

  9. 数据结构(C语言第2版)----时间复杂度和单链表

    马上要到校招了,复习下相关的基础知识. 时间复杂度是什么? 官方解释: 算法的执行时间需要依据算法所编制的程序在计算机上于运行时所消耗的时间来度量.在算法中可以使用基本的语句的执行次数作为算法的时间复 ...

  10. 年薪10w和年薪100w的人,差在哪里?

    职场10年,为什么有人已经当上了董事总经理,而有的人还是资深销售经理? 出道10年,为什么有人已经当上了主编.出版人,而有的人还是资深编辑? 打拼10年,为什么有人已经身价数十亿美金,而有的人还在为竞 ...