• angular源码分析:angular中jqLite的实现——你可以丢掉jQuery了


    一、从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的各种语法糖。

  • 相关阅读:
    LintCode "Subarray Sum II"
    LintCode "Maximum Subarray Difference"
    LeetCode "Flip Game II"
    LintCode "Sliding Window Median" & "Data Stream Median"
    LintCode "Permutation Index"
    LintCode "Count of Smaller Number before itself"
    LeetCode "Nim Game"
    Etcd在Linux CentOS7下载、安装
    CentOS7 查看开启端口
    CentOS7-防火墙firewall 状态、重启、关闭
  • 原文地址:https://www.cnblogs.com/web2-developer/p/angular-5.html
Copyright © 2020-2023  润新知