โหลดสคริปต์แบบอะซิงโครนัส


125

ฉันใช้ปลั๊กอินหลายตัววิดเจ็ตแบบกำหนดเองและไลบรารีอื่น ๆ จาก JQuery ส่งผลให้ฉันมีไฟล์. js และ. css หลายไฟล์ ฉันต้องการสร้างตัวโหลดสำหรับไซต์ของฉันเนื่องจากต้องใช้เวลาพอสมควรในการโหลด จะเป็นการดีถ้าฉันสามารถแสดงตัวโหลดก่อนที่จะนำเข้าทั้งหมด:

<script type="text/javascript" src="js/jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="js/myFunctions.js"></script>
<link type="text/css" href="css/main.css" rel="stylesheet" />
... 
....
 etc

ฉันพบบทเรียนหลายอย่างที่ช่วยให้ฉันสามารถนำเข้าไลบรารี JavaScript แบบอะซิงโครนัสได้ ตัวอย่างเช่นฉันสามารถทำสิ่งต่างๆเช่น:

  (function () {
        var s = document.createElement('script');
        s.type = 'text/javascript';
        s.async = true;
        s.src = 'js/jquery-ui-1.8.16.custom.min.js';
        var x = document.getElementsByTagName('script')[0];
        x.parentNode.insertBefore(s, x);
    })();

ด้วยเหตุผลบางประการเมื่อฉันทำสิ่งเดียวกันกับไฟล์ทั้งหมดของฉันหน้าเว็บไม่ทำงาน ฉันพยายามมานานแล้วที่จะพยายามค้นหาว่าปัญหาอยู่ที่ใด แต่ไม่พบ ก่อนอื่นฉันคิดว่าอาจเป็นเพราะฟังก์ชันจาวาสคริปต์บางอย่างขึ้นอยู่กับฟังก์ชันอื่น ๆ แต่ฉันโหลดตามลำดับที่ถูกต้องโดยใช้ฟังก์ชันหมดเวลาเมื่อหนึ่งเสร็จฉันดำเนินการต่อไปและหน้ายังคงทำงานแปลก ๆ ตัวอย่างเช่นฉันไม่สามารถคลิกที่ลิงค์ ฯลฯ ... ภาพเคลื่อนไหวยังคงใช้งานได้ ..

อย่างไรก็ตาม

นี่คือสิ่งที่ฉันคิด ... ฉันเชื่อว่าเบราว์เซอร์มีแคชนั่นเป็นสาเหตุที่ต้องใช้เวลานานในการโหลดหน้าเว็บในครั้งแรกและในครั้งต่อไปที่รวดเร็ว ดังนั้นสิ่งที่ฉันคิดจะทำคือแทนที่หน้า index.html ของฉันด้วยหน้าที่โหลดไฟล์ทั้งหมดนี้แบบอะซิงโครนัส เมื่อ ajax เสร็จสิ้นการโหลดไฟล์เหล่านั้นทั้งหมดจะเปลี่ยนเส้นทางไปยังหน้าที่ฉันวางแผนจะใช้ เมื่อใช้หน้านั้นไม่ควรใช้เวลานานในการโหลดเนื่องจากไฟล์ควรจะรวมอยู่ในแคชของเบราว์เซอร์ ในหน้าดัชนีของฉัน (หน้าที่มีไฟล์. js และ. css โหลดแบบอะซิงโครนัส) ฉันไม่สนใจว่าจะได้รับข้อผิดพลาด ฉันจะแสดงตัวโหลดและเปลี่ยนเส้นทางหน้าเมื่อเสร็จสิ้น ...

ความคิดนี้เป็นทางเลือกที่ดีหรือไม่? หรือฉันควรพยายามใช้วิธีการแบบอะซิงโครนัสต่อไป?


แก้ไข

วิธีที่ฉันโหลดทุกอย่าง async เป็นดังนี้:

importScripts();

function importScripts()
{
    //import: jquery-ui-1.8.16.custom.min.js
    getContent("js/jquery-1.6.2.min.js",function (code) {
                var s = document.createElement('script');
                s.type = 'text/javascript';
                //s.async = true;
                s.innerHTML=code;
                var x = document.getElementsByTagName('script')[0];
                x.parentNode.insertBefore(s, x);
                setTimeout(insertNext1,1);
            });


    //import: jquery-ui-1.8.16.custom.min.js
    function insertNext1()
    {
        getContent("js/jquery-ui-1.8.16.custom.min.js",function (code) {
                    var s = document.createElement('script');
                    s.type = 'text/javascript';
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext2,1);
                });
    }

    //import: jquery-ui-1.8.16.custom.css
    function insertNext2()
    {

        getContent("css/custom-theme/jquery-ui-1.8.16.custom.css",function (code) {
                    var s = document.createElement('link');
                    s.type = 'text/css';
                    s.rel ="stylesheet";
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext3,1);
                });
    }

    //import: main.css
    function insertNext3()
    {

        getContent("css/main.css",function (code) {
                    var s = document.createElement('link');
                    s.type = 'text/css';
                    s.rel ="stylesheet";
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext4,1);
                });
    }

    //import: jquery.imgpreload.min.js
    function insertNext4()
    {
        getContent("js/farinspace/jquery.imgpreload.min.js",function (code) {
                    var s = document.createElement('script');
                    s.type = 'text/javascript';
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext5,1);
                });
    }


    //import: marquee.js
    function insertNext5()
    {
        getContent("js/marquee.js",function (code) {
                    var s = document.createElement('script');
                    s.type = 'text/javascript';
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext6,1);
                });
    }


    //import: marquee.css
    function insertNext6()
    {

        getContent("css/marquee.css",function (code) {
                    var s = document.createElement('link');
                    s.type = 'text/css';
                    s.rel ="stylesheet";
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext,1);
                });
    }



    function insertNext()
    {
        setTimeout(pageReadyMan,10);        
    }
}


// get the content of url and pass that content to specified function
function getContent( url, callBackFunction )
{
     // attempt to create the XMLHttpRequest and make the request
     try
     {
        var asyncRequest; // variable to hold XMLHttpRequest object
        asyncRequest = new XMLHttpRequest(); // create request object

        // register event handler
        asyncRequest.onreadystatechange = function(){
            stateChange(asyncRequest, callBackFunction);
        } 
        asyncRequest.open( 'GET', url, true ); // prepare the request
        asyncRequest.send( null ); // send the request
     } // end try
     catch ( exception )
     {
        alert( 'Request failed.' );
     } // end catch
} // end function getContent

// call function whith content when ready
function stateChange(asyncRequest, callBackFunction)
{
     if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 )
     {
           callBackFunction(asyncRequest.responseText);
     } // end if
} // end function stateChange

และส่วนที่แปลกคือการทำงานของสไตล์ทั้งหมดบวกกับฟังก์ชันจาวาสคริปต์ทั้งหมด หน้าถูกแช่แข็งด้วยเหตุผลบางประการแม้ว่า ...

คำตอบ:


176

วิธีแก้ปัญหาสองสามข้อสำหรับการโหลด async:

//this function will work cross-browser for loading scripts asynchronously
function loadScript(src, callback)
{
  var s,
      r,
      t;
  r = false;
  s = document.createElement('script');
  s.type = 'text/javascript';
  s.src = src;
  s.onload = s.onreadystatechange = function() {
    //console.log( this.readyState ); //uncomment this line to see which ready states are called.
    if ( !r && (!this.readyState || this.readyState == 'complete') )
    {
      r = true;
      callback();
    }
  };
  t = document.getElementsByTagName('script')[0];
  t.parentNode.insertBefore(s, t);
}

หากคุณมี jQuery อยู่แล้วให้ใช้:

$.getScript(url, successCallback)* * * *

นอกจากนี้อาจเป็นไปได้ว่าสคริปต์ของคุณกำลังโหลด / ดำเนินการก่อนที่เอกสารจะโหลดเสร็จซึ่งหมายความว่าคุณต้องรอ document.readyก่อนที่เหตุการณ์จะถูกผูกไว้กับองค์ประกอบ

ไม่สามารถบอกได้อย่างชัดเจนว่าปัญหาของคุณคืออะไรโดยไม่ต้องดูรหัส

วิธีแก้ไขที่ง่ายที่สุดคือให้สคริปต์ทั้งหมดของคุณอยู่ในบรรทัดที่ด้านล่างของหน้าเพื่อไม่ให้บล็อกการโหลดเนื้อหา HTML ขณะดำเนินการ นอกจากนี้ยังหลีกเลี่ยงปัญหาในการโหลดสคริปต์ที่ต้องการแบบอะซิงโครนัส

หากคุณมีการโต้ตอบแบบแฟนซีโดยเฉพาะที่ไม่ได้ใช้เสมอไปซึ่งต้องใช้สคริปต์ที่ใหญ่กว่าในบางประเภทการหลีกเลี่ยงการโหลดสคริปต์นั้นจนกว่าจะจำเป็น (ขี้เกียจโหลด) อาจเป็นประโยชน์

* สคริปต์ที่โหลดด้วย$.getScriptจะไม่ถูกแคช


สำหรับใครก็ตามที่สามารถใช้คุณสมบัติที่ทันสมัยเช่นPromiseวัตถุloadScriptฟังก์ชันนี้กลายเป็นเรื่องง่ายขึ้นอย่างมาก:

function loadScript(src) {
    return new Promise(function (resolve, reject) {
        var s;
        s = document.createElement('script');
        s.src = src;
        s.onload = resolve;
        s.onerror = reject;
        document.head.appendChild(s);
    });
}

โปรดทราบว่าเวอร์ชันนี้ไม่ยอมรับการcallbackโต้แย้งอีกต่อไปเนื่องจากสัญญาที่ส่งคืนจะจัดการกับการโทรกลับ สิ่งที่ก่อนหน้านี้จะได้รับตอนนี้จะเป็นloadScript(src, callback)loadScript(src).then(callback)

สิ่งนี้มีโบนัสเพิ่มเติมจากความสามารถในการตรวจจับและจัดการกับความล้มเหลวเช่นใคร ๆ ก็โทร ...

loadScript(cdnSource)
    .catch(loadScript.bind(null, localSource))
    .then(successCallback, failureCallback);

... และมันจะจัดการปัญหา CDN ได้อย่างสง่างาม


ฉันกำลังจะเริ่มทดลองใช้ แต่ถ้าฉันโหลดสคริปต์เหล่านั้นในเพจอื่นเมื่อฉันเปลี่ยนเส้นทางเพจไปยังเพจอื่นและเพจนั้นมีสคริปต์เดียวกันก็ไม่ควรใช้เวลาโหลด มันควรจะอยู่ในแคชใช่ไหม ดังนั้นฉันไม่ควรใช้เทคนิคนั้นแทน?
โตโน่น้ำ

1
ฉันเปลี่ยนและต่อท้ายเด็กที่แท็กหัวแทนตัว… ..var x = document.getElementsByTagName ('head') [0]; x.appendChild (s);
โตโน่น้ำ

1
@TonoNam คุณสามารถใช้ได้document.head.appendChildแต่มีปัญหาเฉพาะบางอย่างเกี่ยวกับการเพิ่มสคริปต์ลงใน<head>; ไม่มีสิ่งใดที่จะป้องกันไม่ให้สคริปต์โหลดและเรียกใช้งาน
zzzzBov

1
ฉันต้องใช้ t.parentNode.insertBefore (ตรงข้ามกับ t.parent)
Phil LaNasa

1
@MuhammadUmer หากคุณกำลังเขียนแท็กสคริปต์เพิ่มเติมdocument.writeก่อนที่เอกสารจะโหลดสคริปต์จะทำงานพร้อมกันเป็นส่วนหนึ่งของการโหลดหน้าเว็บและจะไม่รวมพฤติกรรมการโทรกลับแบบอะซิงโครนัสด้วยตัวมันเอง หากคุณกำลังสร้างองค์ประกอบสคริปต์และเพิ่มลงในเพจคุณจะมีสิทธิ์เข้าถึงเพื่อเพิ่มตัวฟังเหตุการณ์เพื่อทริกเกอร์แบบอะซิงโครนัส tl; dr:ขึ้นอยู่กับว่าคุณกำลังโหลดสคริปต์ด้วย JS อย่างไร
zzzzBov

33

แอตทริบิวต์ "async" ใหม่ของ HTML5 ควรทำเคล็ดลับ 'defer' ยังรองรับในเบราว์เซอร์ส่วนใหญ่หากคุณสนใจ IE

async - HTML

<script async src="siteScript.js" onload="myInit()"></script>

เลื่อน - HTML

<script defer src="siteScript.js" onload="myInit()"></script>

ในขณะที่วิเคราะห์โค้ดหน่วยโฆษณา Adsense ใหม่ฉันสังเกตเห็นแอตทริบิวต์และการค้นหานำฉันมาที่นี่: http://davidwalsh.name/html5-async


จากความเข้าใจของฉันหลังจากอ่าน stackoverflow.com/questions/2774373/…แล้ว asyn ไม่ได้มีไว้สำหรับการโหลด แต่ใช้สำหรับกำหนดเวลาที่จะรันจาวาสคริปต์ที่มีอยู่
JackDev

9
แอตทริบิวต์ defer ทำให้เบราว์เซอร์เลื่อนการเรียกใช้สคริปต์จนกว่าจะโหลดและแยกวิเคราะห์เอกสารและพร้อมที่จะจัดการ แอตทริบิวต์ async ทำให้เบราว์เซอร์เรียกใช้สคริปต์โดยเร็วที่สุด แต่ไม่บล็อกการแยกวิเคราะห์เอกสารในขณะที่กำลังดาวน์โหลดสคริปต์
shuseel

1
"asyn ไม่ได้มีไว้สำหรับการโหลด แต่สำหรับการพิจารณาว่าเมื่อใดที่จะเรียกใช้จาวาสคริปต์ที่มีอยู่" สิ่งนี้ไม่ขัดแย้งกัน ในทุกกรณีเบราว์เซอร์จะเริ่มโหลดสคริปต์โดยเร็ว แต่ถ้าไม่มี asyncหรือdeferจะบล็อกการแสดงผลในขณะที่โหลด การตั้งค่าasyncจะบอกให้ไม่บล็อกและเรียกใช้สคริปต์โดยเร็ว การตั้งค่าdeferจะบอกให้ไม่บล็อก แต่รอด้วยการรันสคริปต์จนกว่าหน้าจะเสร็จสิ้น โดยปกติแล้วจะไม่มีปัญหาในการเรียกใช้สคริปต์โดยเร็วตราบเท่าที่ไม่มีการอ้างอิงใด ๆ (เช่น jQuery) หากสคริปต์หนึ่งมีการอ้างอิงกับอีกสคริปต์หนึ่งให้ใช้onLoadเพื่อรอ
Stijn de Witt

@StijndeWitt จะเกิดอะไรขึ้นถ้าไม่มีการระบุ async หรือ defer?
Mohammed Shareef C

@MohammedShareefC ถ้าคุณไม่ระบุคุณจะได้รับสิ่งที่คล้ายกับพฤติกรรมการซิงค์ เบราว์เซอร์จะบล็อกการประมวลผลเพิ่มเติมในขณะที่ดาวน์โหลดและเรียกใช้สคริปต์ ดังนั้นพฤติกรรมเริ่มต้นคือการซิงค์อย่างมีประสิทธิภาพ แต่เนื่องจากสิ่งนี้เป็นอันตรายต่อประสิทธิภาพการทำงานเบราว์เซอร์จะแก้ไขปัญหาให้มากที่สุดเท่าที่จะทำได้ พวกเขาสามารถดำเนินการต่อด้วยการแยกวิเคราะห์โหลดและใช้ CSS เป็นต้น แต่ไม่สามารถเริ่มรันส่วนของสคริปต์ถัดไปได้ ดังนั้นสิ่งต่างๆจะปิดกั้น การตั้งค่า async หมายความว่าคุณกำลังบอกเบราว์เซอร์ว่าสามารถดำเนินการต่อได้และคุณจะต้องทำให้มันทำงานได้เอง
Stijn de Witt

8

ฉันโหลดสคริปต์แบบอะซิงโครนัส (html 5 มีคุณสมบัตินั้น) เมื่อสคริปต์ทั้งหมดที่โหลดเสร็จแล้วฉันเปลี่ยนทิศทางเพจไปที่ index2.html โดยที่ index2.html ใช้ไลบรารีเดียวกัน เนื่องจากเบราว์เซอร์มีแคชเมื่อหน้าเปลี่ยนเส้นทางไปที่ index2.html แล้ว index2.html จะโหลดภายในเวลาไม่ถึงหนึ่งวินาทีเนื่องจากมีทุกสิ่งที่จำเป็นในการโหลดหน้า ในหน้า index.html ของฉันฉันยังโหลดรูปภาพที่ฉันวางแผนจะใช้เพื่อให้เบราว์เซอร์วางรูปภาพเหล่านั้นบนแคช ดังนั้น index.html ของฉันจึงดูเหมือน:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
    <title>Project Management</title>

    <!-- the purpose of this page is to load all the scripts on the browsers cache so that pages can load fast from now on -->

    <script type="text/javascript">

        function stylesheet(url) {
            var s = document.createElement('link');
            s.type = 'text/css';
            s.async = true;
            s.src = url;
            var x = document.getElementsByTagName('head')[0];
            x.appendChild(s);
        }

        function script(url) {
            var s = document.createElement('script');
            s.type = 'text/javascript';
            s.async = true;
            s.src = url;
            var x = document.getElementsByTagName('head')[0];
            x.appendChild(s);
        }

        //load scritps to the catche of browser
        (function () {            
                stylesheet('css/custom-theme/jquery-ui-1.8.16.custom.css');
                stylesheet('css/main.css');
                stylesheet('css/marquee.css');
                stylesheet('css/mainTable.css');

                script('js/jquery-ui-1.8.16.custom.min.js');
                script('js/jquery-1.6.2.min.js');
                script('js/myFunctions.js');
                script('js/farinspace/jquery.imgpreload.min.js');
                script('js/marquee.js');            
        })();

    </script>

    <script type="text/javascript">
       // once the page is loaded go to index2.html
        window.onload = function () {
            document.location = "index2.html";
        }
    </script>

</head>
<body>

<div id="cover" style="position:fixed; left:0px; top:0px; width:100%; height:100%; background-color:Black; z-index:100;">Loading</div>

<img src="images/home/background.png" />
<img src="images/home/3.png"/>
<img src="images/home/6.jpg"/>
<img src="images/home/4.png"/>
<img src="images/home/5.png"/>
<img src="images/home/8.jpg"/>
<img src="images/home/9.jpg"/>
<img src="images/logo.png"/>
<img src="images/logo.png"/>
<img src="images/theme/contentBorder.png"/>

</body>
</html>

อีกสิ่งที่ดีเกี่ยวกับเรื่องนี้คือฉันสามารถวางโหลดเดอร์ไว้ในหน้าและเมื่อโหลดหน้าเสร็จแล้วตัวโหลดจะหายไปและหน้าใหม่จะทำงานในเวลาไม่กี่มิลลิวินาที


1
@MohammedShareefC มันประชดนะ หน้าพิเศษสำหรับโหลดข้อมูลล่วงหน้าไม่ได้เป็นแนวคิดที่ดีสำหรับ SEO การโหลดล่วงหน้าเป็นสิ่งที่ดี แต่คุณไม่จำเป็นต้องเปลี่ยนเส้นทางเพื่อให้บรรลุ เพียงแค่ติดลิงค์ใน div ที่ซ่อนอยู่และเบราว์เซอร์จะดูแลให้คุณโดยอัตโนมัติ จากนั้นอย่าเปลี่ยนเส้นทาง แต่เพียงแค่แสดงหน้าจริง COPY-PASTERS อ่านสิ่งนี้ : โปรดเปลี่ยนการเข้ารหัส ISO-88591 เป็น UTF-8 ก่อนที่จะใช้ HTML ใด ๆ นี้เพื่อให้พวกเราที่เหลือสามารถออกจากการเข้ารหัสได้ทุกเมื่อในไม่ช้า ขอบคุณ!
Stijn de Witt

โปรดดูคำตอบใหม่ของฉัน: stackoverflow.com/questions/7718935/load-scripts-asynchronously/…
asmmahmud

8

ตัวอย่างจาก google

<script type="text/javascript">
  (function() {
    var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
    po.src = 'https://apis.google.com/js/plusone.js?onload=onLoadCallback';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
  })();
</script>

8

หมายเหตุหลายประการ:

  • s.async = trueไม่ถูกต้องสำหรับ HTML5 Doctype อย่างถูกต้องคือs.async = 'async'(การใช้จริงtrue ถูกต้องขอบคุณamnที่ชี้ให้เห็นในความคิดเห็นด้านล่าง)
  • การใช้การหมดเวลาเพื่อควบคุมคำสั่งนั้นไม่ดีและปลอดภัยมากนักและคุณยังทำให้เวลาในการโหลดใหญ่ขึ้นมากเพื่อให้เท่ากับผลรวมของการหมดเวลาทั้งหมด!

เนื่องจากมีเหตุผลล่าสุดในการโหลดไฟล์แบบอะซิงโครนัส แต่ตามลำดับฉันขอแนะนำวิธีการทำงานที่ขับเคลื่อนด้วยฟังก์ชันมากกว่าตัวอย่างของคุณ (ลบออกconsole.logเพื่อใช้ในการผลิต :)):

(function() {
    var prot = ("https:"===document.location.protocol?"https://":"http://");

    var scripts = [
        "path/to/first.js",
        "path/to/second.js",
        "path/to/third.js"
    ];

    function completed() { console.log('completed'); }  // FIXME: remove logs

    function checkStateAndCall(path, callback) {
        var _success = false;
        return function() {
            if (!_success && (!this.readyState || (this.readyState == 'complete'))) {
                _success = true;
                console.log(path, 'is ready'); // FIXME: remove logs
                callback();
            }
        };
    }

    function asyncLoadScripts(files) {
        function loadNext() { // chain element
            if (!files.length) completed();
            var path = files.shift();
            var scriptElm = document.createElement('script');
            scriptElm.type = 'text/javascript';
            scriptElm.async = true;
            scriptElm.src = prot+path;
            scriptElm.onload = scriptElm.onreadystatechange = \
                checkStateAndCall(path, loadNext); // load next file in chain when
                                                   // this one will be ready 
            var headElm = document.head || document.getElementsByTagName('head')[0];
            headElm.appendChild(scriptElm);
        }
        loadNext(); // start a chain
    }

    asyncLoadScripts(scripts);
})();

4
s.async = trueเป็นที่ถูกต้อง สถานที่ให้บริการasyncที่ระบุไว้อย่างชัดเจนว่าบูลในของ W3C HTML 5 ข้อเสนอแนะที่w3.org/TR/html5/scripting-1.html#attr-script-async คุณกำลังสับสนasync แอตทริบิวต์กับasyncคุณสมบัติที่เปิดเผยโดยอ็อบเจ็กต์ที่ใช้HTMLScriptElementอินเทอร์เฟซ DOM อันที่จริงเมื่อจัดการแอตทริบิวต์ที่เกี่ยวข้องขององค์ประกอบผ่านสิ่งที่ต้องการElement.setAttributeคุณควรใช้element.setAttribute("async", "async")เนื่องจากแอตทริบิวต์ HTML ทั้งหมดเป็นข้อความแรกและสำคัญที่สุด
59

"เนื่องจากมีเหตุผลล่าสุดในการโหลดไฟล์แบบอะซิงโครนัส แต่ตามลำดับฉันขอแนะนำวิธีที่เน้นฟังก์ชันการทำงานมากกว่าตัวอย่างของคุณ [... ]" ฉันรู้ว่า async มักจะ "ดีกว่า" แต่คุณอ้างถึงเหตุผล "ล่าสุด" อะไร
BluE

ฉันเปลี่ยน 'if (! files.length) เสร็จสิ้น ();' การเพิ่ม 'return': 'if (! files.length) {complete (); return;} '
ฟิล

4

ด้วย HTML5 ตอนนี้คุณสามารถประกาศสคริปต์ที่คุณต้องการโหลดแบบอะซิงโครนัสได้โดยการเพิ่ม "async" ในแท็ก:

<script async>...</script>

หมายเหตุ: แอ็ตทริบิวต์ async ใช้สำหรับสคริปต์ภายนอกเท่านั้น (และควรใช้เฉพาะในกรณีที่มีแอตทริบิวต์ src เท่านั้น)

หมายเหตุ: สคริปต์ภายนอกสามารถเรียกใช้งานได้หลายวิธี:

  • หากมี async: สคริปต์จะทำงานแบบอะซิงโครนัสกับส่วนที่เหลือของเพจ (สคริปต์จะถูกเรียกใช้งานในขณะที่เพจยังคงทำการแยกวิเคราะห์)
  • หากไม่มี async และมีการเลื่อนเวลา: สคริปต์จะทำงานเมื่อหน้าเสร็จสิ้นการแยกวิเคราะห์
  • หากไม่มีทั้ง async หรือ defer: สคริปต์จะถูกดึงและดำเนินการทันทีก่อนที่เบราว์เซอร์จะแยกวิเคราะห์หน้าต่อไป

ดูสิ่งนี้: http://www.w3schools.com/tags/att_script_async.asp


2

ฉันจะกรอกคำตอบของ zzzzBov ด้วยการตรวจสอบการมีอยู่ของการโทรกลับและอนุญาตให้ส่งผ่านข้อโต้แย้ง:

    function loadScript(src, callback, args) {
      var s, r, t;
      r = false;
      s = document.createElement('script');
      s.type = 'text/javascript';
      s.src = src;
      if (typeof(callback) === 'function') {
        s.onload = s.onreadystatechange = function() {
          if (!r && (!this.readyState || this.readyState === 'complete')) {
            r = true;
            callback.apply(args);
          }
        };
      };
      t = document.getElementsByTagName('script')[0];
      t.parent.insertBefore(s, t);
    }

2

นี่คือวิธีแก้ปัญหาร่วมสมัยที่ยอดเยี่ยมสำหรับการโหลดสคริปต์แบบอะซิงโครนัสแม้ว่าจะระบุเฉพาะสคริปต์ js ด้วยasync falseเท็จ

มีบทความดีๆเขียนใน www.html5rocks.com - ดำดิ่งสู่ห้วงน้ำมืดมนของการโหลดสคริปต์ดำน้ำลึกลงไปในน้ำมืดของการโหลดสคริปต์

หลังจากพิจารณาวิธีแก้ปัญหาที่เป็นไปได้มากมายผู้เขียนสรุปว่าการเพิ่มสคริปต์ js ที่ส่วนท้ายขององค์ประกอบเนื้อหาเป็นวิธีที่ดีที่สุดในการหลีกเลี่ยงการปิดกั้นการแสดงผลเพจด้วยสคริปต์ js

ในเวลานั้นผู้เขียนได้เพิ่มทางเลือกอื่นที่ดีสำหรับผู้ที่ต้องการโหลดและเรียกใช้สคริปต์แบบอะซิงโครนัส

พิจารณาว่าคุณตั้งชื่อสคริปต์สี่ตัวscript1.js, script2.js, script3.js, script4.jsแล้วคุณสามารถทำได้โดยใช้ async = false :

[
  'script1.js',
  'script2.js',
  'script3.js',
  'script4.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

ตอนนี้สเป็คบอก : ดาวน์โหลดพร้อมกันดำเนินการตามลำดับทันทีที่ดาวน์โหลดทั้งหมด

Firefox <3.6, Opera กล่าวว่า:ฉันไม่รู้ว่าสิ่งที่ "async" นี้คืออะไร แต่มันก็เกิดขึ้นฉันเรียกใช้สคริปต์ที่เพิ่มผ่าน JS ตามลำดับที่เพิ่มเข้ามา

Safari 5.0 พูดว่า:ฉันเข้าใจ "async" แต่ไม่เข้าใจการตั้งค่าเป็น "เท็จ" ด้วย JS ฉันจะเรียกใช้สคริปต์ของคุณทันทีที่มาถึงไม่ว่าจะเป็นลำดับใดก็ตาม

IE <10 พูดว่า:ไม่มีความคิดเกี่ยวกับ“ async” แต่มีวิธีแก้ปัญหาโดยใช้“ onreadystatechange”

อย่างอื่นพูดว่า:ฉันเป็นเพื่อนของคุณเราจะทำตามหนังสือ

ตอนนี้โค้ดเต็มพร้อม IE <10 วิธีแก้ปัญหา:

var scripts = [
  'script1.js',
  'script2.js',
  'script3.js',
  'script4.js'
];
var src;
var script;
var pendingScripts = [];
var firstScript = document.scripts[0];

// Watch scripts load in IE
function stateChange() {
  // Execute as many scripts in order as we can
  var pendingScript;
  while (pendingScripts[0] && ( pendingScripts[0].readyState == 'loaded' || pendingScripts[0].readyState == 'complete' ) ) {
    pendingScript = pendingScripts.shift();
    // avoid future loading events from this script (eg, if src changes)
    pendingScript.onreadystatechange = null;
    // can't just appendChild, old IE bug if element isn't closed
    firstScript.parentNode.insertBefore(pendingScript, firstScript);
  }
}

// loop through our script urls
while (src = scripts.shift()) {
  if ('async' in firstScript) { // modern browsers
    script = document.createElement('script');
    script.async = false;
    script.src = src;
    document.head.appendChild(script);
  }
  else if (firstScript.readyState) { // IE<10
    // create a script and add it to our todo pile
    script = document.createElement('script');
    pendingScripts.push(script);
    // listen for state changes
    script.onreadystatechange = stateChange;
    // must set src AFTER adding onreadystatechange listener
    // else we’ll miss the loaded event for cached scripts
    script.src = src;
  }
  else { // fall back to defer
    document.write('<script src="' + src + '" defer></'+'script>');
  }
}

1

สาเหตุหนึ่งที่ทำให้สคริปต์ของคุณโหลดช้ามากก็คือหากคุณเรียกใช้สคริปต์ทั้งหมดของคุณในขณะที่โหลดหน้าเช่น

callMyFunctions();

แทน:

$(window).load(function() {
      callMyFunctions();
});

สคริปต์บิตที่สองนี้รอจนกว่าเบราว์เซอร์จะโหลดโค้ด Javascript ทั้งหมดของคุณจนหมดก่อนที่จะเริ่มเรียกใช้สคริปต์ใด ๆ ของคุณทำให้ผู้ใช้เห็นว่าหน้าเว็บโหลดได้เร็วขึ้น

หากคุณต้องการปรับปรุงประสบการณ์ของผู้ใช้โดยการลดเวลาในการโหลดฉันจะไม่เลือกตัวเลือก "หน้าจอโหลด" ในความคิดของฉันมันน่ารำคาญกว่าการโหลดหน้าเว็บช้ากว่า


1

ฉันขอแนะนำให้คุณดูModernizr Modernizrเป็นไลบรารีน้ำหนักเบาขนาดเล็กที่คุณสามารถโหลดจาวาสคริปต์ของคุณแบบอะซิงโครนัสพร้อมคุณสมบัติที่ช่วยให้คุณตรวจสอบว่าไฟล์ถูกโหลดหรือไม่และเรียกใช้สคริปต์ในอื่น ๆ ที่คุณระบุ

นี่คือตัวอย่างของการโหลด jquery:

Modernizr.load([
  {
    load: '//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.js',
    complete: function () {
      if ( !window.jQuery ) {
            Modernizr.load('js/libs/jquery-1.6.1.min.js');
      }
    }
  },
  {
    // This will wait for the fallback to load and
    // execute if it needs to.
    load: 'needs-jQuery.js'
  }
]);

1

ฉันเขียนโพสต์เล็กน้อยเพื่อช่วยในเรื่องนี้คุณสามารถอ่านเพิ่มเติมได้ที่นี่https://timber.io/snippets/asynchronously-load-a-script-in-the-browser-with-javascript/แต่ฉันได้แนบ คลาสผู้ช่วยด้านล่าง โดยอัตโนมัติจะรอให้สคริปต์โหลดและส่งคืนแอตทริบิวต์ของหน้าต่างที่ระบุเมื่อเสร็จสิ้น

export default class ScriptLoader {
  constructor (options) {
    const { src, global, protocol = document.location.protocol } = options
    this.src = src
    this.global = global
    this.protocol = protocol
    this.isLoaded = false
  }

  loadScript () {
    return new Promise((resolve, reject) => {
      // Create script element and set attributes
      const script = document.createElement('script')
      script.type = 'text/javascript'
      script.async = true
      script.src = `${this.protocol}//${this.src}`

      // Append the script to the DOM
      const el = document.getElementsByTagName('script')[0]
      el.parentNode.insertBefore(script, el)

      // Resolve the promise once the script is loaded
      script.addEventListener('load', () => {
        this.isLoaded = true
        resolve(script)
      })

      // Catch any errors while loading the script
      script.addEventListener('error', () => {
        reject(new Error(`${this.src} failed to load.`))
      })
    })
  }

  load () {
    return new Promise(async (resolve, reject) => {
      if (!this.isLoaded) {
        try {
          await this.loadScript()
          resolve(window[this.global])
        } catch (e) {
          reject(e)
        }
      } else {
        resolve(window[this.global])
      }
    })
  }
}

การใช้งานเป็นดังนี้:

const loader = new Loader({
    src: 'cdn.segment.com/analytics.js',
    global: 'Segment',
})

// scriptToLoad will now be a reference to `window.Segment`
const scriptToLoad = await loader.load()

1

คุณอาจพบว่าบทความวิกินี้น่าสนใจ: http://ajaxpatterns.org/On-Demand_Javascript

จะอธิบายว่าควรใช้เทคนิคดังกล่าวอย่างไรและเมื่อใด


น่าเสียดายที่ดูเหมือนว่าลิงก์จะตายไปแล้ว :(
Eric Seastrand

@Eric เป็นเพราะนั่นเป็นวิธีการโหลดโค้ดแบบไดนามิกแบบโบราณ: D
tereško

0

ดีx.parentNodeผลตอบแทนส่วน HEAD เพื่อให้คุณใส่สคริปต์ก่อนแท็กหัว บางทีนั่นอาจเป็นปัญหา

ลองx.parentNode.appendChild()แทน


0

ลองดูhttps://github.com/stephen-lazarionok/async-resource-loaderนี้ มีตัวอย่างที่แสดงวิธีการโหลด JS, CSS และไฟล์หลายไฟล์ด้วยการถ่ายครั้งเดียว


เป็นไปได้ที่จะเปลี่ยนแปลงเล็กน้อยเพื่อกำจัดการพึ่งพา jquery
Stephen L.

0

คุณสามารถใช้LABJSหรือRequreJS

รถตักสคริปต์ชอบLABJS, RequireJSจะปรับปรุงความเร็วและคุณภาพของรหัสของคุณ


0

คุณพิจารณาใช้ Fetch Injection หรือไม่? ฉันใช้ไลบรารีโอเพนซอร์สที่เรียกว่าfetch-injectionเพื่อจัดการกรณีเช่นนี้ นี่คือลักษณะของตัวโหลดของคุณเมื่อใช้ lib:

fetcInject([
  'js/jquery-1.6.2.min.js',
  'js/marquee.js',
  'css/marquee.css',
  'css/custom-theme/jquery-ui-1.8.16.custom.css',
  'css/main.css'
]).then(() => {
  'js/jquery-ui-1.8.16.custom.min.js',
  'js/farinspace/jquery.imgpreload.min.js'
})

สำหรับการทำงานร่วมกันหลังงัดตรวจสอบคุณสมบัติและฤดูใบไม้ร่วงกลับไป XHR ฉีดหรือสคริปต์ DOM document.writeองค์ประกอบหรือเพียงแบบอินไลน์แท็กลงในหน้าโดยใช้


0

นี่คือโซลูชันที่กำหนดเองของฉันในการกำจัด JavaScript ที่บล็อกการแสดงผล:

// put all your JS files here, in correct order
const libs = {
  "jquery": "https://code.jquery.com/jquery-2.1.4.min.js",
  "bxSlider": "https://cdnjs.cloudflare.com/ajax/libs/bxslider/4.2.5/jquery.bxslider.min.js",
  "angular": "https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-beta.2/angular.min.js",
  "ngAnimate": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-beta.2/angular-animate.min.js"
}
const loadedLibs = {}
let counter = 0

const loadAsync = function(lib) {
  var http = new XMLHttpRequest()
  http.open("GET", libs[lib], true)
  http.onload = () => {
    loadedLibs[lib] = http.responseText
    if (++counter == Object.keys(libs).length) startScripts()
  }
  http.send()
}

const startScripts = function() {
  for (var lib in libs) eval(loadedLibs[lib])
  console.log("allLoaded")
}

for (var lib in libs) loadAsync(lib)

กล่าวโดยย่อคือโหลดสคริปต์ทั้งหมดของคุณแบบอะซิงโครนัสจากนั้นจึงดำเนินการตามนั้น

Github repo : https://github.com/mudroljub/js-async-loader


1
ใช้ไม่ได้No 'Access-Control-Allow-Origin' header is present on the requested resource
อับราม

1
นั่นคือปัญหา CORS stackoverflow.com/questions/20035101/…
Damjan Pavlica

0

ต่อไปนี้เป็นฟังก์ชั่น ES6 เล็ก ๆ น้อย ๆ หากมีใครต้องการใช้ใน React เช่น

import {uniqueId} from 'lodash' // optional
/**
 * @param {String} file The path of the file you want to load.
 * @param {Function} callback (optional) The function to call when the script loads.
 * @param {String} id (optional) The unique id of the file you want to load.
 */
export const loadAsyncScript = (file, callback, id) => {
  const d = document
  if (!id) { id = uniqueId('async_script') } // optional
  if (!d.getElementById(id)) {
    const tag = 'script'
    let newScript = d.createElement(tag)
    let firstScript = d.getElementsByTagName(tag)[0]
    newScript.id = id
    newScript.async = true
    newScript.src = file
    if (callback) {
      // IE support
      newScript.onreadystatechange = () => {
        if (newScript.readyState === 'loaded' || newScript.readyState === 'complete') {
          newScript.onreadystatechange = null
          callback(file)
        }
      }
      // Other (non-IE) browsers support
      newScript.onload = () => {
        callback(file)
      }
    }
    firstScript.parentNode.insertBefore(newScript, firstScript)
  } else {
    console.error(`The script with id ${id} is already loaded`)
  }
}


-3

ฉันขอแนะนำให้ดูที่การลดขนาดไฟล์ก่อนและดูว่าจะช่วยเพิ่มความเร็วได้มากพอหรือไม่ หากโฮสต์ของคุณทำงานช้าอาจลองวางเนื้อหาคงที่ใน CDN

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.