xiaolingzi's blog

每天都在成长...

欢迎您:亲

html5shiv.js分析-读源码之javascript系列

xiaolingzi 发表于 2012-05-31 23:42:29

首先,我们先了解一下html5shiv.js是什么。

html5shiv.js是一套实现让ie低版本等浏览器支持html5标签的解决方案。

实现原理:见如何让ie低版本浏览器支持html5标签 。

废话不多说,我们先上源代码,代码有点长,但保持原来的注释有利于大家理解,不想直接阅读的就点收缩代码然后往下看。源码原地址:https://github.com/aFarkas/html5shiv 。

/*! HTML5 Shiv vpre3.6 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed */
;(function(window, document) {
                                    
  /** Preset options */
  var options = window.html5 || {};
                                    
  /** Used to skip problem elements */
  var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup
)$/i;
                                    
  /** Not all elements can be cloned in IE (this list can be shortend) **/
  var saveClones = /^<(?:a|b|button|code|div|fieldset|form|h1|h2|h3|h4|h5|h6|
i|iframe|img|input|label|li|link|ol|option|p|param|q|script|select|span|
strong|style|table|tbody|td|textarea|tfoot|th|thead|tr|ul)$/i;
                                    
  /** Detect whether the browser supports default html5 styles */
  var supportsHtml5Styles;
                                    
  /** Name of the expando, to work with multiple documents or to re-shiv one document */
  var expando = '_html5shiv';
                                    
  /** The id for the the documents expando */
  var expanID = 0;
                                    
  /** Cached data for each document */
  var expandoData = {};
                                    
  /** Detect whether the browser supports unknown elements */
  var supportsUnknownElements;
                                    
  (function() {
    var a = document.createElement('a');
                                    
    a.innerHTML = '<xyz></xyz>';
                                    
    //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
    supportsHtml5Styles = ('hidden' in a);
                                    
    supportsUnknownElements = a.childNodes.length == 1 || (function() {
      // assign a false positive if unable to shiv
      try {
        (document.createElement)('a');
      } catch(e) {
        return true;
      }
      var frag = document.createDocumentFragment();
      return (
        typeof frag.cloneNode == 'undefined' ||
        typeof frag.createDocumentFragment == 'undefined' ||
        typeof frag.createElement == 'undefined'
      );
    }());
                                    
  }());
                                    
  /*--------------------------------------------------------------------------*/
                                    
  /**
* Creates a style sheet with the given CSS text and adds it to the document.
* @private
* @param {Document} ownerDocument The document.
* @param {String} cssText The CSS text.
* @returns {StyleSheet} The style element.
*/
  function addStyleSheet(ownerDocument, cssText) {
    var p = ownerDocument.createElement('p'),
        parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
                                    
    p.innerHTML = 'x<style>' + cssText + '</style>';
    return parent.insertBefore(p.lastChild, parent.firstChild);
  }
                                    
  /**
* Returns the value of `html5.elements` as an array.
* @private
* @returns {Array} An array of shived element node names.
*/
  function getElements() {
    var elements = html5.elements;
    return typeof elements == 'string' ? elements.split(' ') : elements;
  }
                                      
    /**
* Returns the data associated to the given document
* @private
* @param {Document} ownerDocument The document.
* @returns {Object} An object of data.
*/
  function getExpandoData(ownerDocument) {
    var data = expandoData[ownerDocument[expando]];
    if (!data) {
        data = {};
        expanID++;
        ownerDocument[expando] = expanID;
        expandoData[expanID] = data;
    }
    return data;
  }
                                    
  /**
* returns a shived element for the given nodeName and document
* @memberOf html5
* @param {String} nodeName name of the element
* @param {Document} ownerDocument The context document.
* @returns {Object} The shived element.
*/
  function createElement(nodeName, ownerDocument, data){
    if (!ownerDocument) {
        ownerDocument = document;
    }
    if(supportsUnknownElements){
        return ownerDocument.createElement(nodeName);
    }
    data = data || getExpandoData(ownerDocument);
    var node;
                                    
    if (data.cache[nodeName]) {
        node = data.cache[nodeName].cloneNode();
    } else if (saveClones.test(nodeName)) {
        node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
    } else {
        node = data.createElem(nodeName);
    }
                                    
    // Avoid adding some elements to fragments in IE < 9 because
    // * Attributes like `name` or `type` cannot be set/changed once an element
    // is inserted into a document/fragment
    // * Link elements with `src` attributes that are inaccessible, as with
    // a 403 response, will cause the tab/window to crash
    // * Script elements appended to fragments will execute when their `src`
    // or `text` property is set
    return node.canHaveChildren && !reSkip.test(nodeName) ? data.frag.appendChild(node) : node;
  }
                                    
  /**
* returns a shived DocumentFragment for the given document
* @memberOf html5
* @param {Document} ownerDocument The context document.
* @returns {Object} The shived DocumentFragment.
*/
  function createDocumentFragment(ownerDocument, data){
    if (!ownerDocument) {
        ownerDocument = document;
    }
    if(supportsUnknownElements){
        return ownerDocument.createDocumentFragment();
    }
    data = data || getExpandoData(ownerDocument);
    var clone = data.frag.cloneNode(),
        i = 0,
        elems = getElements(),
        l = elems.length;
    for(;i<l;i++){
        clone.createElement(elems[i]);
    }
    return clone;
  }
                                    
  /**
* Shivs the `createElement` and `createDocumentFragment` methods of the document.
* @private
* @param {Document|DocumentFragment} ownerDocument The document.
* @param {Object} data of the document.
*/
  function shivMethods(ownerDocument, data) {
    if (!data.cache) {
        data.cache = {};
        data.createElem = ownerDocument.createElement;
        data.createFrag = ownerDocument.createDocumentFragment;
        data.frag = data.createFrag();
    }
                                    
                                    
    ownerDocument.createElement = function(nodeName) {
      //abort shiv
      if (!html5.shivMethods) {
          return data.createElem(nodeName);
      }
      return createElement(nodeName);
    };
                                    
    ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
      'var n=f.cloneNode(),c=n.createElement;' +
      'h.shivMethods&&(' +
        // unroll the `createElement` calls
        getElements().join().replace(/\w+/g, function(nodeName) {
          data.createElem(nodeName);
          data.frag.createElement(nodeName);
          return 'c("' + nodeName + '")';
        }) +
      ');return n}'
    )(html5, data.frag);
  }
                                    
  /*--------------------------------------------------------------------------*/
                                    
  /**
* Shivs the given document.
* @memberOf html5
* @param {Document} ownerDocument The document to shiv.
* @returns {Document} The shived document.
*/
  function shivDocument(ownerDocument) {
    if (!ownerDocument) {
        ownerDocument = document;
    }
    var data = getExpandoData(ownerDocument);
                                    
    if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
      data.hasCSS = !!addStyleSheet(ownerDocument,
        // corrects block display not defined in IE6/7/8/9
        'article,aside,figcaption,figure,footer,header,hgroup,nav
,section{display:block}' +
        // adds styling not present in IE6/7/8/9
        'mark{background:#FF0;color:#000}'
      );
    }
    if (!supportsUnknownElements) {
      shivMethods(ownerDocument, data);
    }
    return ownerDocument;
  }
                                    
  /*--------------------------------------------------------------------------*/
                                    
  /**
* The `html5` object is exposed so that more elements can be shived and
* existing shiving can be detected on iframes.
* @type Object
* @example
*
* // options can be changed before the script is included
* html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false };
*/
  var html5 = {
                                    
    /**
* An array or space separated string of node names of the elements to shiv.
* @memberOf html5
* @type Array|String
*/
    'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video',
                                    
    /**
* A flag to indicate that the HTML5 style sheet should be inserted.
* @memberOf html5
* @type Boolean
*/
    'shivCSS': !(options.shivCSS === false),
                                    
    /**
* Is equal to true if a browser supports creating unknown/HTML5 elements
* @memberOf html5
* @type boolean
*/
    'supportsUnknownElements': supportsUnknownElements,
                                    
    /**
* A flag to indicate that the document's `createElement` and `createDocumentFragment`
* methods should be overwritten.
* @memberOf html5
* @type Boolean
*/
    'shivMethods': !(options.shivMethods === false),
                                    
    /**
* A string to describe the type of `html5` object ("default" or "default print").
* @memberOf html5
* @type String
*/
    'type': 'default',
                                    
    // shivs the document according to the specified `html5` object options
    'shivDocument': shivDocument,
                                    
    //creates a shived element
    createElement: createElement,
                                    
    //creates a shived documentFragment
    createDocumentFragment: createDocumentFragment
  };
                                    
  /*--------------------------------------------------------------------------*/
                                    
  // expose html5
  window.html5 = html5;
                                    
  // shiv the document
  shivDocument(document);
                                    
}(this, document));

代码结构分析:

1.整个代码放在一个匿名函数里面并执行,该匿名使用的模式如下:

(function{}())

受到作用域的限制,执行时将当前window(this)和document作为参数传递进去。关于匿名函数执行形式的相关说明请参考之前的文章javascript匿名函数的各种执行形式

2.我们从上往下依次浏览一下代码,并将代码划分为五部分。

第一部分是从开始到第30行。

第二部分是从第31行到55行。

第三部分是从56行到234行。

第四部分是从235行到278行。

第五部分是剩下部分。

好,那么下面我们就从这五部分中分别找出我们可以学习的一些知识点。

第一部分:该部分主要是私有变量的定义,学习到的知识点有:

1.了解到不是所有元素都可以在IE中进行复制,具体参看saveClones 的定义和上面的注释。

2.了解到可以通过一对空大括号{}对一对象进行初始化,见expandoData的定义。

3.了解到变量在开始集中定义的习惯。因为在javascript中,就算将变量定义在其他地方也会预先执行定义,所以可以集中在前面一起定义,这样也有利于变量的管理。

第二部分执行一个匿名函数来检测浏览器对html5中的css和未知标签的支持情况,并保存结果。学到的知识点有:

1.如何检测浏览器对html5的样式的支持。此处的思路是通过定义一个超链接元素a,然后检测在当前浏览器中a元素是否具备hidden属性,hidden为html5中新增的一个属性,使用该属性可以对元素进行隐藏。

判断的代码如下:

supportsHtml5Styles = ('hidden' in a);

对各浏览器的测试结果如下:

浏览器支持情况
Chrome(18.0.1025.168 m)
true
FireFox(12.0)
true
Safari(5.1.7)
true
Opera(11.64)
true
IE9
false
IE8
false
IE7
false

2.如何检测浏览器对未知元素的支持情况。此处的思路是,给定义的a元素填充一未知元素,然后检测a的子元素的个数,若等于1则表示支持未知元素,否则不支持。其次通过检查一个错位执行的支持情况(document.createElement)('a');,但个人测试各浏览器都通过,不知道作者检查是哪些浏览器。最后才通过对文档碎片节点的一些方法的支持情况来进行判断。

对各浏览器的测试结果如下:


a.childNodes.
length

(document.createElement)('a')
frag.cloneNode
frag.createDocumentFragment

frag.
createElement

Chrome(18.0.1025.168 m)
1通过defined
undefined
undefined
FireFox(12.0)
1通过
defined
undefined
undefined
Safari(5.1.7)
1通过
defined
undefined
undefined
Opera(11.64)
1通过
defined
undefined
undefined
IE9
1通过
defined
undefined
undefined
IE8
0通过
defined
defined
defined
IE7
0通过
defined
defined
defined

第三部分:该部分主要是定义一系列的私有方法。学到的知识点有如下:

1.了解了个浏览器对lastChild的支持情况。我们来看addStyleSheet方法,该方法的主要作用是将样式添加到页面中。我们留意到在加入style标签的时候前面多加了一个x。为什么要这样子做呢?这是由于在只有一个节点的情况lastChild会出现兼容性问题,主要表现在IE8和IE7无法通过它来获取到那唯一的节点。

对各浏览器的测试结果如下:


p.lastChild(不加x)
p.lastChild(加x)
Chrome(18.0.1025.168 m)
object HTMLStyleElement
object HTMLStyleElement
FireFox(12.0)
object HTMLStyleElement
object HTMLStyleElement
Safari(5.1.7)
object HTMLStyleElement
object HTMLStyleElement
Opera(11.64)
object HTMLStyleElement
object HTMLStyleElement
IE9
object HTMLStyleElement
object HTMLStyleElement
IE8
null
object HTMLStyleElement
IE7
null
object

2.Function的使用,注意这里是首字母大写的。此处的使用请查看shivMethods方法。Function主要是用来实现动态执行。它可以实现跟eval一样的工作,但由于它在性能方面胜过eval,所以很多人都推荐使用Function。

Function的执行形式如下:
var 函数名 = new Function('argument1','argument2',...,'argumentN','函数体');
或者
var 函数名 = new Function('argument1,argument2,...,argumentN','函数体');
或者
new Function('执行体');

我们看到上面的形式都使用了new关键字进行实例化,但是我们看到本例源码中却没有new,经过测试发现new关键字可以省略。

3.createDocumentFragment即创建文档碎片节点的使用。创建文档碎片节点的目的是为了减少浏览器渲染的次数来提升性能。比如,当我们要往页面中添加一系列节点时,如果每次都实时向页面使用appendChild来添加节点时,那么每次浏览器都会渲染一次,而过多次数的渲染就会造成性能问题。如果我们先把要添加的节点都先加到文档碎片节点中去,完成后再一次添加到页面中去就只渲染一次。

第四部分:该部分主要定义html5对象的一些属性和方法。学到的知识点如下:

1.通过json的方式进行属性和方法的封装。可以大大减少全局变量的污染。具体没必要再详说。

2.通过将私有方法或属性赋值给全局对象的属性来将方法公开。比如当我们定义了许多方法或属性,但我们不想公开所有方法或属性,此时就可以通过闭包将方法私有化,然后再通过返回赋值给全局变量的方式公开部分属性和方法。如此处通过名字叫html5的全局对象的属性进行公开。

第五部分:该部分主要是将html5对象保留给全局window,并执行入口函数。学到的知识点主要是如何在函数中将对象暴露给window(全局化)。

除了上面说的知识点外,还有一个非常重要的地方就是学习别人优秀的设计模式和架构。

好吧,文章就到此结束。如有不对之处欢迎指出交流。

      

转载请注明出处:http://www.xxling.com/article/41.aspx

  • 分类: javascript
  • 阅读: (11389)
  • 评论: (5)
砖墙
blue
感觉源代码里面好多代码都是多余的,不知道原作者为什么要做这么多的检测
xiaolingzi blue
还好了 那些检测是为了检查浏览器的支持情况
jiangjunzhangaa
钻研精神令人佩服!
supervsky
IE9不支持HTML5,博主不是开玩笑吧?还有钻研怎么可能不加IE6。
xiaolingzi supervsky
1.请认真看文章再评论,然后自己去测试 2.IE6你才是开玩笑吧,我们早放弃IE6兼容了
拍砖 取消
请输入昵称
请输入邮箱
*
 选择评论类型
300字以内  请输入评论内容