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

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

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

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

二、jqLiteParseHTML函数

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

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

  1. function jqLiteBuildFragment(html, context) {
  2. var tmp, tag, wrap,
  3. fragment = context.createDocumentFragment(), //首先创建一个碎片元素作为载体
  4. nodes = [], i;
  5. if (jqLiteIsTextNode(html)) {
  6. // Convert non-html into a text node
  7. nodes.push(context.createTextNode(html));
  8. } else {
  9. // Convert html into DOM nodes
  10. tmp = tmp || fragment.appendChild(context.createElement("div"));
  11. tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
  12. wrap = wrapMap[tag] || wrapMap._default;
  13. tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];//对应的元素用对应的标签包裹起来。
  14. // Descend through wrappers to the right content
  15. i = wrap[0];
  16. while (i--) {
  17. tmp = tmp.lastChild;
  18. }
  19. nodes = concat(nodes, tmp.childNodes);
  20. tmp = fragment.firstChild;
  21. tmp.textContent = "";
  22. }
  23. // Remove wrapper from fragment
  24. fragment.textContent = "";
  25. fragment.innerHTML = ""; // Clear inner HTML
  26. forEach(nodes, function(node) {
  27. fragment.appendChild(node);
  28. });
  29. return fragment;
  30. }

函数首先创建一个碎片元素作为载体,然后用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

  1. function jqLiteAddNodes(root, elements) {
  2. // THIS CODE IS VERY HOT. Don't make changes without benchmarking. //这段代码将会被频繁调用,没有特别需要不要修改
  3. if (elements) {
  4. // if a Node (the most common case)
  5. if (elements.nodeType) {
  6. root[root.length++] = elements;
  7. } else {
  8. var length = elements.length;
  9. // if an Array or NodeList and not a Window
  10. if (typeof length === 'number' && elements.window !== elements) {
  11. if (length) {
  12. for (var i = 0; i < length; i++) {
  13. root[root.length++] = elements[i];
  14. }
  15. }
  16. } else {
  17. root[root.length++] = elements;
  18. }
  19. }
  20. }
  21. }

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

四、JQLite的原型:JQLitePrototype

1.给原型绑定函数

  1. var JQLitePrototype = JQLite.prototype = {
  2. ready: function(fn) { //定义ready函数
  3. var fired = false;
  4. function trigger() {
  5. if (fired) return;
  6. fired = true;
  7. fn();
  8. }
  9. // check if document is already loaded
  10. if (document.readyState === 'complete') { //dom已经加载完
  11. setTimeout(trigger);
  12. } else {
  13. this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 //监听dom加载完
  14. // we can not use jqLite since we are not done loading and jQuery could be loaded later.
  15. // jshint -W064
  16. JQLite(window).on('load', trigger); // fallback to window.onload for others
  17. // jshint +W064
  18. }
  19. },
  20. toString: function() {
  21. var value = [];
  22. forEach(this, function(e) { value.push('' + e);});
  23. return '[' + value.join(', ') + ']';
  24. },
  25. eq: function(index) { //定义eq
  26. return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
  27. },
  28. length: 0,
  29. push: push,
  30. sort: [].sort,
  31. splice: [].splice
  32. };

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

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

  1. forEach({
  2. data: jqLiteData,
  3. inheritedData: jqLiteInheritedData,
  4. scope: function(element) {...},
  5. isolateScope: function(element) {...},
  6. controller: jqLiteController,
  7. injector: function(element) {...},
  8. removeAttr: function(element, name) {...},
  9. hasClass: jqLiteHasClass,
  10. css: function(element, name, value) {...},
  11. attr: function(element, name, value) {...},
  12. prop: function(element, name, value) {...},
  13. text: (function() {...},
  14. html: function(element, value) {...},
  15. empty: jqLiteEmpty
  16. }, function(fn, name) {
  17. /**
  18. * Properties: writes return selection, reads return first value
  19. */
  20. JQLite.prototype[name] = function(arg1, arg2) {
  21. var i, key;
  22. var nodeCount = this.length;
  23. // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
  24. // in a way that survives minification.
  25. // jqLiteEmpty takes no arguments but is a setter.
  26. if (fn !== jqLiteEmpty &&
  27. (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
  28. if (isObject(arg1)) {
  29. // we are a write, but the object properties are the key/values
  30. for (i = 0; i < nodeCount; i++) {
  31. if (fn === jqLiteData) {
  32. // data() takes the whole object in jQuery
  33. fn(this[i], arg1);
  34. } else {
  35. for (key in arg1) {
  36. fn(this[i], key, arg1[key]);
  37. }
  38. }
  39. }
  40. // return self for chaining
  41. return this;
  42. } else {
  43. // we are a read, so read the first child.
  44. // TODO: do we still need this?
  45. var value = fn.$dv;
  46. // Only if we have $dv do we iterate over all, otherwise it is just the first element.
  47. var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
  48. for (var j = 0; j < jj; j++) {
  49. var nodeValue = fn(this[j], arg1, arg2);
  50. value = value ? value + nodeValue : nodeValue;
  51. }
  52. return value;
  53. }
  54. } else {
  55. // we are a write, so apply to all children
  56. for (i = 0; i < nodeCount; i++) {
  57. fn(this[i], arg1, arg2);
  58. }
  59. // return self for chaining
  60. return this;
  61. }
  62. };
  63. });

3.继续绑定

  1. forEach({
  2. removeData: jqLiteRemoveData,
  3. on: function jqLiteOn(element, type, fn, unsupported) {...},
  4. off: jqLiteOff,
  5. one: function(element, type, fn) {...},
  6. replaceWith: function(element, replaceNode) {...},
  7. children: function(element) {...},
  8. contents: function(element) {...},
  9. append: function(element, node) {...},
  10. prepend: function(element, node) {...},
  11. wrap: function(element, wrapNode) {...},
  12. remove: jqLiteRemove,
  13. detach: function(element) {...},
  14. after: function(element, newElement) {...},
  15. addClass: jqLiteAddClass,
  16. removeClass: jqLiteRemoveClass,
  17. toggleClass: function(element, selector, condition) {...},
  18. parent: function(element) {...},
  19. next: function(element) {...},
  20. find: function(element, selector) {...},
  21. clone: jqLiteClone,
  22. triggerHandler: function(element, event, extraParameters) {...}
  23. }, function(fn, name) {
  24. /**
  25. * chaining functions
  26. */
  27. JQLite.prototype[name] = function(arg1, arg2, arg3) {
  28. var value;
  29. for (var i = 0, ii = this.length; i < ii; i++) {
  30. if (isUndefined(value)) {
  31. value = fn(this[i], arg1, arg2, arg3);
  32. if (isDefined(value)) {
  33. // any function which returns a value needs to be wrapped
  34. value = jqLite(value);
  35. }
  36. } else {
  37. jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
  38. }
  39. }
  40. return isDefined(value) ? value : this;
  41. };
  42. // bind legacy bind/unbind to on/off
  43. JQLite.prototype.bind = JQLite.prototype.on;
  44. JQLite.prototype.unbind = JQLite.prototype.off;
  45. });

五、$$jqLite service

  1. // Provider for private $$jqLite service
  2. function $$jqLiteProvider() {
  3. this.$get = function $$jqLite() {
  4. return extend(JQLite, {
  5. hasClass: function(node, classes) {
  6. if (node.attr) node = node[0];
  7. return jqLiteHasClass(node, classes);
  8. },
  9. addClass: function(node, classes) {
  10. if (node.attr) node = node[0];
  11. return jqLiteAddClass(node, classes);
  12. },
  13. removeClass: function(node, classes) {
  14. if (node.attr) node = node[0];
  15. return jqLiteRemoveClass(node, classes);
  16. }
  17. });
  18. };
  19. }

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

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

这里可以看到,它直接调用了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函数改了。

  1. //修复ie8上的clone html5 错误问题
  2. 'use strict';
  3. function ie8_ele_clone(element){
  4. function createSafeFragment( document ) {
  5. var list = nodeNames.split( "|" ),
  6. safeFrag = document.createDocumentFragment();
  7. if ( safeFrag.createElement ) {
  8. while ( list.length ) {
  9. safeFrag.createElement(
  10. list.pop()
  11. );
  12. }
  13. }
  14. return safeFrag;
  15. }
  16. var html5Clone =
  17. document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav></:nav>",
  18. nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
  19. "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
  20. rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
  21. safeFragment = createSafeFragment( document ),
  22. fragmentDiv = safeFragment.appendChild( document.createElement("div") );
  23. if(html5Clone){
  24. return element.cloneNode(true);
  25. }
  26. function copy(elem){
  27. var clone;
  28. if(rnoshimcache.test( "<" + elem.nodeName + ">" )){
  29. fragmentDiv.innerHTML = elem.outerHTML;
  30. fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
  31. }
  32. else
  33. {
  34. clone = elem.cloneNode(true);
  35. }
  36. for(var i = 0; i < elem.children.length ; i ++){
  37. var tmp_node = elem.children[i];
  38. if(tmp_node.children.length == 0 && !rnoshimcache.test( "<" + tmp_node.nodeName + ">" ))continue;
  39. var copy_node = copy(tmp_node);
  40. var clone_replace = clone.children[i];
  41. clone.insertBefore(copy_node,clone_replace);
  42. clone.removeChild(clone_replace);
  43. }
  44. return clone;
  45. }
  46. return copy(element);
  47. };

改后的jqLiteClone函数:

  1. function jqLiteClone(element) {
  2. if(typeof ie8_ele_clone == 'function'){
  3. return ie8_ele_clone(element);
  4. }
  5. else
  6. {
  7. return element.cloneNode(true);
  8. }
  9. }

上一期: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. MySQL的学习--join和union的用法

    感觉工作之后一直在用框架,数据库的一些基本的东西都忘记了,这次借着这个系列的博客回顾一下旧知识,学一点新知识. 今天就先从join和union开始. join 是两张表做交连后里面条件相同的部分记录产 ...

  2. Testing - 测试基础 - 用例

    测试用例 是指对一项特定的软件产品进行测试任务的描述,体现测试方案.方法.技术和策略. 内容包括测试目标.测试环境.输入数据.测试步骤.预期结果.测试脚本等,并形成文档. 每个具体测试用例都将包括下列 ...

  3. tomcat连接器

    Connector是Tomcat最核心的组件之一,负责处理一个WebServer最核心的连接管理.Net IO.线程(可选).协议解析和处理的工作.一.连接器介绍在开始Connector探索之路之前, ...

  4. 使用国内镜像加速下载Android SDK

    本文转自:http://blog.kuoruan.com/24.html.感谢原作者. 什么是Android SDK SDK:(software development kit)软件开发工具包.被软件 ...

  5. C#中enum类型

    最近碰到了枚举类型,就顺便整理下. 枚举的基类Enum,可以是除 Char 外的任何整型.不做显示声明的话,默认是整形(Int32). 声明一个Enum类型: /// <summary> ...

  6. Laravel4中的Validator

    不管写接口还是写web页面,实质都是传入参数,然后进行业务逻辑,然后再输出具体内容.所以,对参数的验证是不可避免的一个环节,比如传过来的email是不是为空,是不是合法的email格式?laravel ...

  7. WebGL实现HTML5的3D贪吃蛇游戏

    js1k.com收集了小于1k的javascript小例子,里面有很多很炫很酷的游戏和特效,今年规则又增加了新花样,传统的classic类型基础上又增加了WebGL类型,以及允许增加到2K的++类型, ...

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

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

  9. Visual Studio开发Cordova应用示例

    作者:Grey 原文地址:http://www.cnblogs.com/greyzeng/p/5455728.html 本文的GIF动画均使用ScreenToGif进行录制. Cordova是什么? ...

  10. Eclipse窗口总是在最前的解决办法

    Eclipse窗口总是在最前的解决办法 状况: Eclipse在偶然的情况下,会莫名其妙地保持在窗口的最前面,一直保持在最前:然后alt + tab,或者鼠标点击其他窗口.想切换/激活其他窗口时,根本 ...