브라우저 환경에서 JavaScript 스크립트 로딩 및 실행 분석: 지연 및 비동기 특성_javascript 기술

WBOY
풀어 주다: 2016-05-16 15:20:02
원래의
1618명이 탐색했습니다.

defer 및 async 기능은 많은 JavaScript 개발자에게 "익숙하지만 익숙하지 않은" 두 가지 기능이라고 생각됩니다. 문자 그대로 보면 이 두 기능이 "지연 스크립트"입니다. 및 "비동기 스크립트" 효과입니다. 그러나 연기를 예로 들면 개발자는 연기 기능이 있는 스크립트의 실행이 언제 지연되는지, 내부 스크립트와 외부 스크립트가 연기 후 스크립트를 지원할 수 있는지 여부와 같은 일부 세부 사항에 익숙하지 않을 수 있습니다. 지연 실행 외에 특별한 기능은 무엇입니까? 이 기사는 개발자가 이 두 기능을 더 잘 익히는 데 도움이 되기를 바라며 기존 기사와 MDN 문서의 두 기능에 대한 설명을 결합하여 지연 및 비동기에 대한 보다 포괄적인 연구와 요약을 수행합니다.

1 소개

"브라우저 환경에서 JavaScript 스크립트 로드 및 실행 분석: 코드 실행 순서"에서 JavaScript 코드를 실행하면 페이지의 구문 분석 및 렌더링과 다른 리소스의 다운로드가 차단된다고 언급했습니다. 물론 JavaScript는 단일 스레드 언어이기 때문에 일반적인 상황에서는 페이지의 JavaScript 코드가 위에서 아래로 순서대로만 실행될 수 있습니다. 브라우저 환경에서의 스크립트 로딩 및 실행 "실행 순서 "에서 분석한 것처럼 document.write를 통해 스크립트를 입력하거나 동적 스크립트 기술을 통해 스크립트를 도입하는 경우와 같은 경우에는 JavaScript 코드의 실행 순서가 반드시 위에서 아래로 엄격한 순서를 따를 필요는 없습니다. 지연 및 비동기도 우리가 "비정상적인 상황"이라고 부르는 것입니다.

실제 개발에서 우리가 일반적으로 가장 우려하는 차단과 사용자 경험에 가장 큰 영향을 미치는 차단은 다음과 같은 측면이 있어야 합니다.


[1] 페이지 분석 및 렌더링 차단


[2] 우리가 작성한 페이지 초기화 스크립트(일반적으로 DOMContentLoaded 이벤트를 수신하기 위해 바인딩된 스크립트) 스크립트의 이 부분은 사용자 상호 작용과 가장 관련된 코드를 작성하기 때문에 먼저 실행하려는 스크립트입니다. 여기 )


[3] 페이지 내 외부 리소스(사진 등) 다운로드 차단


시간이 많이 걸리는 스크립트 작업이 있고 이 스크립트가 위에서 언급한 세 곳을 차단하는 경우 이 웹페이지의 성능이나 사용자 경험이 매우 저하됩니다.


defer와 async의 두 가지 기능의 원래 의도는 차단이 페이지 경험에 미치는 영향을 해결하거나 완화하는 것입니다. 아래에서 이 두 가지 기능을 주로 다음과 같은 측면에서 분석하겠습니다.


[1]지연 또는 비동기 스크립트의 실행 시간은 언제인가요? 페이지 차단은 어떻습니까?


[2] 내부 및 외부 스크립트 모두 지연 또는 비동기 구현이 가능합니까?


[3] 브라우저는 이 두 가지 기능을 얼마나 잘 지원합니까? 관련된 버그가 있나요?


[4] 이 두 가지 기능을 사용하는 스크립트를 사용할 때 또 주의해야 할 사항이 있나요?


2가지 연기 기능

2.1 defer 스크립트 실행 시기에 대해

defer 기능은 HTML4 사양에 정의된 확장 기능입니다. 처음에는 IE4 및 Firefox3.5에서만 지원되었으며 나중에 Chrome과 같은 브라우저에서도 defer="defer"를 사용하여 지원을 추가했습니다. defer는 지연을 의미하며, 이는 스크립트 실행이 지연된다는 의미입니다. 일반적인 상황에서는 우리가 소개하는 스크립트가 즉시 다운로드되어 실행됩니다. 그러나 defer 기능을 사용하면 다운로드 후 스크립트가 즉시 실행되지 않고 페이지가 구문 분석된 후에 실행됩니다. HTML4 표준의 defer 설명을 살펴보겠습니다.


defer: 설정되면 이 부울 속성은 스크립트가 문서 콘텐츠를 생성하지 않을 것이라는 힌트를 사용자 에이전트에 제공하므로(예: javascript에서는 "document.write"가 없음) 사용자 에이전트가 구문 분석을 계속할 수 있습니다. 렌더링합니다.


즉, defer가 설정되면 이 스크립트는 문서 콘텐츠를 생성하지 않을 것임을 사용자 에이전트에 알리므로 사용자 에이전트는 계속해서 구문 분석하고 렌더링할 수 있습니다. MDN의 defer에 대한 주요 설명을 다시 살펴보겠습니다:


defer: async 속성은 없지만 defer 속성이 있는 경우 페이지 구문 분석이 완료되면 스크립트가 실행됩니다.


표준의 정의를 통해 defer 스크립트가 페이지 구문 분석을 차단하지 않고 페이지 구문 분석이 완료될 때까지 기다렸다가 실행한다는 점을 분명히 할 수 있습니다. 그러나 여전히 시간이 많이 걸리는 defer가 있을 수 있습니다. 외부 리소스 다운로드를 차단하면 DOMContentLoaded 이벤트가 차단되나요? 실제로 defer 스크립트는 DOMContentLoaded 이벤트 이전에 계속 실행되므로 DOMContentLoaded에서 스크립트를 계속 차단합니다. 다음 그림을 사용하면 defer 스크립트의 실행 타이밍을 이해하는 데 도움이 됩니다.



표준의 정의에 따르면 내부 스크립트는 연기를 지원하지 않지만, IE9 이하 브라우저에서는 내부 스크립트에 대한 연기 지원을 제공합니다.


2.2 브라우저 지원 연기

연기 기능에 대한 브라우저 지원을 살펴보겠습니다.


IE9 이하 브라우저에는 버그가 있는데, 이에 대해서는 추후 DEMO에서 자세히 설명하겠습니다.

2.3 DEMO: 연기 기능의 기능 검증

연기 속성의 기능을 확인하기 위해 "스크립트 연기 속성"에서 Olivier Rochard가 사용한 방법을 모방합니다.

먼저 6개의 외부 스크립트를 준비했습니다.

1.js:

test = "나는 외부 스크립트 n입니다."

2.js

test = "나는 신체 외부 스크립트 n입니다"

3.js

test = "저는 하단 외부 스크립트 n입니다"

defer1.js

test = "나는 헤드 외부 지연 스크립트 n입니다"

defer2.js

test = "나는 신체 외부 지연 스크립트 n입니다"

defer3.js

test = "저는 하단 외부 지연 스크립트 n입니다"

HTML의 코드는 다음과 같습니다.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>defer attribute test</title>
<script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script>
<script type="text/javascript">var test = "";</script>
<script src="defer1.js" type="text/javascript" defer="defer"></script>
<script src="1.js" type="text/javascript"></script>
<script defer="defer">
test += "我是head延迟内部脚本\n";
</script>
<script>
test += "我是head内部脚本\n";
</script>
</head>
<body>
<button id="test">点击一下</button>
<script src="defer2.js" type="text/javascript" defer="defer"></script>
<script src="2.js" type="text/javascript"></script>
</body>
<script src="defer3.js" type="text/javascript" defer="defer"></script>
<script src="3.js" type="text/javascript"></script>
<script>
$(function(){
test += "我是DOMContentLoaded里面的脚本
";
})
window.onload = function(){
test += "我是window.onload里面的脚本
";
var button = document.getElementById("test");
button.onclick = function(){
alert(test);
}
}
</script>
</html> 
로그인 후 복사

코드에서는 DOMContentLoaded 이벤트 구현을 용이하게 하기 위해 jQuery를 도입했습니다(나중에 호환되는 DOMContentLoaded를 직접 구현하는 방법을 소개할 예정입니다). 그런 다음 헤드, 바디 내부 및 외부에 지연 스크립트를 도입했습니다. 스크립트의 본문과 일반 스크립트를 살펴보고, 전역 문자열을 통해 각 코드의 실행 상태를 기록합니다. 각 브라우저의 실행 결과를 살펴보겠습니다.

<본체>
IE7 IE9 IE10 크롬 파이어폭스

저는 외부 스크립트 책임자입니다

IE7 IE9 IE10 CHROME firefox

我是head外部脚本
我是head内部脚本
我是body外部脚本
我是底部外部脚本
我是head外部延迟脚本
我是head延迟内部脚本
我是body外部延迟脚本
我是底部外部延迟脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本

我是head外部脚本
我是head内部脚本
我是body外部脚本
我是底部外部脚本
我是head外部延迟脚本
我是head延迟内部脚本
我是body外部延迟脚本
我是底部外部延迟脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本

我是head外部脚本
我是head延迟内部脚本
我是head内部脚本
我是body外部脚本
我是底部外部脚本
我是head外部延迟脚本
我是body外部延迟脚本
我是底部外部延迟脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本

我是head外部脚本
我是head延迟内部脚本
我是head内部脚本
我是body外部脚本
我是底部外部脚本
我是head外部延迟脚本
我是body外部延迟脚本
我是底部外部延迟脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本

<br />我是head外部脚本
<span style="color: rgb(255,0,0)">我是head延迟内部脚本</span>
我是head内部脚本
我是body外部脚本
我是底部外部脚本
我是head外部延迟脚本
我是body外部延迟脚本
我是底部外部延迟脚本
<span style="color: rgb(255,0,0)">我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本</span>
로그인 후 복사
저는 내부 스크립트 책임자입니다<🎜> 나는 신체 외부 스크립트입니다<🎜> 저는 하단 외부 스크립트입니다 <🎜> 저는 외부 지연 스크립트를 맡고 있습니다 <🎜> 저는 헤드 딜레이 내부 스크립트입니다<🎜> 나는 신체 외부 지연 스크립트 <🎜> 저는 하단 외부 지연 스크립트입니다 <🎜> 나는 DOMContentLoaded의 스크립트입니다<🎜> 저는 window.onload의 스크립트입니다<🎜>
<🎜>저는 외부 스크립트 책임자입니다<🎜> 저는 내부 스크립트 책임자입니다<🎜> 나는 신체 외부 스크립트입니다<🎜> 저는 하단 외부 스크립트입니다 <🎜> 저는 외부 지연 스크립트를 맡고 있습니다 <🎜> 저는 헤드 딜레이 내부 스크립트입니다<🎜> 나는 신체 외부 지연 스크립트 <🎜> 저는 하단 외부 지연 스크립트입니다 <🎜> 나는 DOMContentLoaded의 스크립트입니다<🎜> 저는 window.onload의 스크립트입니다<🎜> <🎜>저는 외부 스크립트 책임자입니다<🎜> 내부 스크립트가 너무 지연되었습니다<🎜> 저는 내부 스크립트 책임자입니다<🎜> 나는 신체 외부 스크립트입니다<🎜> 저는 하단 외부 스크립트입니다 <🎜> 저는 외부 지연 스크립트를 맡고 있습니다 <🎜> 나는 신체 외부 지연 스크립트 <🎜> 저는 하단 외부 지연 스크립트입니다 <🎜> 나는 DOMContentLoaded의 스크립트입니다<🎜> 저는 window.onload의 스크립트입니다<🎜> <🎜>저는 외부 스크립트 책임자입니다<🎜> 내부 스크립트가 너무 지연되었습니다<🎜> 저는 내부 스크립트 책임자입니다<🎜> 나는 신체 외부 스크립트입니다<🎜> 저는 하단 외부 스크립트입니다 <🎜> 저는 외부 지연 스크립트를 맡고 있습니다 <🎜> 나는 신체 외부 지연 스크립트 <🎜> 저는 하단 외부 지연 스크립트입니다 <🎜> 나는 DOMContentLoaded의 스크립트입니다<🎜> 저는 window.onload의 스크립트입니다<🎜> <🎜>

从输出的结果中我们可以确定,只有IE9及以下浏览器支持内部延迟脚本,并且defer后的脚本都会在DOMContentLoaded事件之前触发,因此也是会堵塞DOMContentLoaded事件的。

2.4 DEMO:IE<=9的defer特性bug

从2.3节中的demo可以看出,defer后的脚本还是能够保持执行顺序的,也就是按照添加的顺序依次执行。而在IE<=9中,这个问题存在一个bug:假如我们向文档中增加了多个defer的脚本,而且之前的脚本中有appendChild,innerHTML,insertBefore,replaceChild等修改了DOM的接口调用,那么后面的脚本可能会先于该脚本执行。可以参考github的issue:https://github.com/h5bp/lazyweb-requests/issues/42

我们通过DEMO验证一下,首先修改1.js的代码为(这段代码只为模拟,事实上这段代码存在极大的性能问题):

document.body.innerHTML = "

我是后来加入的
";
document.body.innerHTML += "
我是后来加入的
";
document.body.innerHTML += "
我是后来加入的
";
document.body.innerHTML += "
我是后来加入的
";
document.body.innerHTML += "
我是后来加入的
";
document.body.innerHTML += "
我是后来加入的
";
document.body.innerHTML += "
我是后来加入的
";
alert("我是第1个脚本");

2.js

alert("我是第2个脚本");

修改HMTL中的代码为:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>defer bug in IE=9 test</title>
<script src="1.js" type="text/javascript" defer="defer"></script>
<script src="2.js" type="text/javascript" defer="defer"></script>
</head>
<body>
</body>
</html>
로그인 후 복사

正常情况下,浏览器中弹出框的顺序肯定是:我是第1个脚本-》我是第2个脚本,然而在IE<=9中,执行结果却为:我是第2个脚本-》我是第1个脚本,验证了这个bug。

2.5 defer总结

在总结之前,首先要说一个注意点:正如标准中提到的,defer的脚本中不应该出现document.write的操作,浏览器会直接忽略这些操作。

总的来看,defer的作用一定程度上与将脚本放置在页面底部有一定的相似,但由于IE<=9中的bug,如果页面中出现多个defer时,脚本的执行顺序可能会被打乱从而导致代码依赖可能会出错,因此实际项目中很少会使用defer特性,而将脚本代码放置在页面底部可以替代defer所提供的功能。

3 async特性

3.1 关于async脚本的执行时机

async特性是HTML5中引入的特性,使用方式为:async="async",我们首先看一下标准中对于async特性的相关描述:

async:If the async attribute is present, then the script will be executed asynchronously, as soon as it is available.

需要指出,这里的异步,指的其实是异步加载而不是异步执行,也就是说,浏览器遇到一个async的script标签时,会异步的去加载(个人认为这个过程主要是下载的过程),一旦加载完毕就会执行代码,而执行的过程肯定还是同步的,也就是阻塞的。我们可以通过下图来综合理解defer和async:


这样来看的话,async脚本的执行时机是无法确定的,因为脚本何时加载完毕也是不确定的。我们通过下面的demo来感受一下:

async1.js

alert("我是异步的脚本");

HTML代码:

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>async attribute test</title>
<script src="/delayfile.php&#63;url=http://localhost/js/load/async1.js&delay=2" async="async" type="text/javascript"></script>
<script>
alert("我是同步的脚本");
</script>
</head>
<body>
</body>
</html> 
로그인 후 복사

여기서는 "브라우저 환경의 JavaScript 스크립트 로딩 및 실행 분석: 코드 실행 순서"에서 지연 파일 스크립트를 빌려 비동기를 지원하는 브라우저에서 이 스크립트의 팝업 상자 순서가 일반적입니다. : 나는 동기 스크립트입니다 -> 나는 비동기 스크립트입니다.

3.2 브라우저 비동기 지원

비동기 기능에 대한 브라우저 지원을 살펴보겠습니다.

보시다시피 IE10만 비동기 기능을 지원하고, Opera Mini는 비동기 기능을 지원하지 않으며, 비동기는 내부 스크립트를 지원하지 않습니다.

3.3 비동기 요약

async는 비동기식 스크립트를 의미합니다. 즉, 스크립트가 비동기적으로 로드되는 과정은 블로킹을 일으키지 않지만, 비동기 스크립트의 실행 시기도 불확실하고, 실행 순서도 불확실하므로 비동기를 사용하는 스크립트는 반드시 코드(예: 타사 통계 코드 또는 광고 코드)에 의존하지 않는 스크립트. 그렇지 않으면 실행 오류가 발생합니다.

지연과 비동기 사이의 4가지 우선순위 문제

이 표준은 다음과 같이 규정하고 있습니다.

[1]

인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿