Sunny-lucking/blog

关于【script加载和执行】十问 (读《高性能JavaScript》)

Opened this issue · 0 comments

第一问:请说出关于下面使用方式中script的区别

默认

<html>
<head>
  <script type="text/javascript" src="script1.js" ></script>
  <script type="text/javascript" src="script1.js" ></script>
</head>

<body>
</body>
</html>

使用defer

<html>
<head>
  <script type="text/javascript" src="script1.js" defer="defer"></script>
  <script type="text/javascript" src="script2.js" defer="defer"></script>
</head>

<body>
</body>
</html>

使用async

<html>
<head>
  <script type="text/javascript" src="script1.js" defer="async"></script>
  <script type="text/javascript" src="script2.js" defer="async"></script>
</head>

<body>
</body>
</html>

默认方式:浏览器会并行加载script, 但是执行是书写的顺序,如果script1执行未完毕,就不会开始执行script2,尽管script2已经加载完。

而且这种方式会阻碍script标签后面其他元素的渲染,直到script1执行完毕才会渲染后面的dom

defer方式:也叫延迟脚本,使用defer后,该脚本会被马上加载,但是脚本会被延迟到整个页面都解析完再执行,即等浏览器遇到</html>标签后在执行。并且这两个脚本会按顺序执行。

async方式:也叫异步脚本: ,使用async后,该脚本会被马上加载,加载完立即执行,但是不会影响页面的解析,。并且这两个脚本不会按顺序执行。谁先加载完,谁就先执行

第二问:第一种方式中script是并行下载的吗?

是的,大多数浏览器现在已经允许并行下载JavaScript文件。这是个好消息,因为<script>标签在下载外部资源时不会阻塞其他<script>标签。遗憾的是,JavaScript 下载过程仍然会阻塞其他资源的下载,比如样式文件和图片(http连接个数的限制,当然,这个原因通常可以用减少http请求来解决,就是合并JavaScript脚本)。尽管脚本的下载过程不会互相影响,但页面仍然必须等待所有 JavaScript 代码下载并执行完成才能继续。因此,尽管最新的浏览器通过允许并行下载提高了性能,但问题尚未完全解决,脚本阻塞仍然是一个问题。

第三问:为什么script脚本要放在body尾部,而不放在head里。

若在head元素中包含js文件,意味着必须等js代码都被下载,解析,执行后才能开始呈现页面(浏览器在遇到<body>标签时才开始呈现页面)

第四问:那我硬是要放在head呢?怎么解决(除了用defer,async)

动态脚本加载: 时可以用另外一种方式加载脚本,叫做动态脚本加载

文档对象模型(DOM)允许您使用 JavaScript 动态创建 HTML 的几乎全部文档内容。<script>元素与页面其他元素一样,可以非常容易地通过标准 DOM 函数创建:

 通过标准 DOM 函数创建`<script>`元素
var script = document.createElement ("script");
 script.type = "text/javascript";
 script.src = "script1.js";
 document.getElementsByTagName("head")[0].appendChild(script);

新的<script>元素加载 script1.js 源文件。此文件当元素添加到页面之后立刻开始下载。此技术的重点在于:无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程。您甚至可以将这些代码放在<head>部分而不会对其余部分的页面代码造成影响(除了用于下载文件的 HTTP 连接)。

当文件使用动态脚本节点下载时,返回的代码通常立即执行(除了 Firefox 和 Opera,他们将等待此前的所有动态脚本节点执行完毕)。

XMLHttpRequest脚本注入

另外一种无阻塞加载的脚本方法是使用XMLHttpRequest对象获取脚本并注入页面中。此技术会先创建一个XHR对象,然后用它下载JS文件,最后通过创建动态<script>元素将代码注入页面中。

var xmlhttp;
if (window.XMLHttpRequest){ // code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp=new XMLHttpRequest();
}
else{ // code for IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}

xmlhttp.onreadystatechange=function(){
  if (xmlhttp.readyState==4 && xmlhttp.status==200){
    document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
   }
}
xmlhttp.open("GET","test.js",true);
xmlhttp.send();
}

这段代码发送一个GET请求获取test.js文件。事件处理函数onReadyStateChange检查readyState是否为4,同时校验HTTP状态码是否有效(200表示有效响应,304意味着从缓存中读取)。

这种方法主要优点是:你可以下载JS代码但不立即执行。由于代码是在<script>标签之外返回的,因此它下载后不会自动执行,这使得你可以把脚本的执行推行到你准备好的时候。另一个优点是,同样的代码再所有的主流浏览器中无一例外都能正常工作。

这种方法的主要局限性是JS文件必须与所请求的页面处于相同的域,这意味着JS文件不能从CDN下载。因此大型Web应用通常不会采用XHR脚本注入。

当不是引入外部脚本时可以用window.load或$(‘document’).ready来使JavaScript等页面加载完再执行

第五问:那我该怎么知道动态脚本已经加载完毕了呢

我们可以对加载的 JS 对象使用 onload 来判断(js.onload),此方法 Firefox2、Firefox3、Safari3.1+、Opera9.6+ 浏览器都能很好的支持,但 IE6、IE7 却不支持。曲线救国 —— IE6、IE7 我们可以使用 js.onreadystatechange 来跟踪每个状态变化的情况(一般为 loading 、loaded、interactive、complete),当返回状态为 loaded 或 complete 时,则表示加载完成,返回回调函数。

对于 readyState 状态需要一个补充说明:

在 interactive 状态下,用户可以参与互动。

Opera 其实也支持 js.onreadystatechange,但他的状态和 IE 的有很大差别。

<script>
function include_js(file) {
    var _doc = document.getElementsByTagName('head')[0];
    var js = document.createElement('script');
    js.setAttribute('type', 'text/javascript');
    js.setAttribute('src', file);
    _doc.appendChild(js);
    if (!/*@cc_on!@*/0) { //if not IE
        //Firefox2、Firefox3、Safari3.1+、Opera9.6+ support js.onload
        js.onload = function () {
            alert('Firefox2、Firefox3、Safari3.1+、Opera9.6+ support js.onload');
        }
    } else {
        //IE6、IE7 support js.onreadystatechange
        js.onreadystatechange = function () {
            if (js.readyState == 'loaded' || js.readyState == 'complete') {
                alert('IE6、IE7 support js.onreadystatechange');
            }
        }
    }
    return false;
}

include_js('http://www.planabc.net/wp-includes/js/jquery/jquery.js');
</script>

第六问:动态加载的脚本会按顺序执行吗?怎么解决?

浏览器不保证文件加载的顺序。所有主流浏览器之中,只有 Firefox 和 Opera 保证脚本按照你指定的顺序执行。其他浏览器将按照服务器返回它们的次序下载并运行不同的代码文件。

解决方法:一个一个按顺序加载。加载完1.js,再加载2.js,如代码:

function loadScript(){
	var scriptArr =  Array.prototype.slice.apply(arguments);
	var script = document.createElement('script');
	script.type = 'text/javascript'; 
	
	var rest = scriptArr.slice(1);

	if(rest.length > 0){
		script.onload = script.onreadystatechange = function() { 
			if ( !this.readyState || this.readyState === "loaded" || 
			this.readyState === "complete" ) { 
				loadScript.apply(null, rest); 
				// Handle memory leak in IE 
				script.onload = script.onreadystatechange = null; 
			} 
		}; 	
	}					

	script.src = scriptArr[0];
	document.body.appendChild(script);
}	
loadScript('1.js','2.js','3.js');

第七问:有见过noscript标签吗?知道是干嘛用的吗?

如果浏览器不支持支持脚本,那么它会显示出 noscript 元素中的文本。

  <body>
  ...
  ...
  <script type="text/vbscript">
   <!--
   document.write("Hello World!")
   '-->
  </script>
  
  <noscript>Your browser does not support VBScript!</noscript>
  ...
  ...
</body>

总结

减少 JavaScript 对性能的影响有以下几种方法:

  • 将所有的<script>标签放到页面底部,也就是</body>闭合标签之前,这能确保在脚本执行前页面已经完成了渲染。
  • 尽可能地合并脚本。页面中的<script>标签越少,加载也就越快,响应也越迅速。无论是外链脚本还是内嵌脚本都是如此。
  • 采用无阻塞下载 JavaScript 脚本的方法:
  • 使用<script>标签的 defer 属性(仅适用于 IE 和 Firefox 3.5 以上版本);
  • 使用动态创建的<script>元素来下载并执行代码;
  • 使用 XHR 对象下载 JavaScript 代码并注入页面中。
  • 通过以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web 网站和应用的实际性能。

该模块后续补充,也欢迎大家补充

看高性能js这本书时,有一段话让我很不解他想表达什么意思,如下,既然是放在body底部了为什么还要动态加载?(我的理解:这描述的应该是懒加载,动态加载,你不需要就可以先不加载,
欢迎交流)