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

 

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

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

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

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

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

-收缩代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
/*! 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://xxling.com/article/41.aspx

原文地址:https://www.cnblogs.com/aimyfly/p/3425374.html