document.createElement (“script”) một cách đồng bộ


81

Có thể gọi .jsđồng bộ trong một tệp và sau đó sử dụng nó ngay sau đó không?

<script type="text/javascript">
    var head = document.getElementsByTagName('head').item(0);
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('src', 'http://mysite/my.js');
    head.appendChild(script);

    myFunction(); // Fails because it hasn't loaded from my.js yet.

    window.onload = function() {
        // Works most of the time but not all of the time.
        // Especially if my.js injects another script that contains myFunction().
        myFunction();
    };
</script>

Điều này được đơn giản hóa. Trong phần triển khai của tôi, công cụ createElement nằm trong một hàm. Tôi đã nghĩ đến việc thêm thứ gì đó vào hàm có thể kiểm tra xem liệu một biến nhất định có được khởi tạo trước khi trả lại quyền điều khiển hay không. Nhưng sau đó vẫn còn vấn đề là phải làm gì khi bao gồm các j từ một trang web khác mà tôi không kiểm soát được.

Suy nghĩ?

Biên tập:

Tôi đã chấp nhận câu trả lời tốt nhất hiện tại vì nó đưa ra lời giải thích tốt cho những gì đang xảy ra. Nhưng nếu bất kỳ ai có bất kỳ đề xuất nào về cách cải thiện điều này, tôi rất sẵn lòng cho họ. Đây là một ví dụ về những gì tôi muốn làm.

// Include() is a custom function to import js.
Include('my1.js');
Include('my2.js');

myFunc1('blarg');
myFunc2('bleet');

Tôi chỉ muốn không phải biết quá nhiều về nội bộ và chỉ có thể nói, "Tôi muốn sử dụng mô-đun này, và bây giờ tôi sẽ sử dụng một số mã từ nó."


Tôi chưa tìm ra cách tạo các tham chiếu đến cùng một giá trị mà không cần tạo một mảng (cho số lượng). Nếu không, tôi nghĩ nó là tự giải thích (khi mọi thứ được tải, eval()mọi tệp theo thứ tự đã cho, nếu không thì chỉ cần lưu trữ phản hồi).
Kang Rofingi

Câu trả lời:


134

Bạn có thể tạo <script>phần tử của mình bằng trình xử lý "onload" và phần tử đó sẽ được gọi khi tập lệnh đã được tải và đánh giá bởi trình duyệt.

var script = document.createElement('script');
script.onload = function() {
  alert("Script loaded and ready");
};
script.src = "http://whatever.com/the/script.js";
document.getElementsByTagName('head')[0].appendChild(script);

Bạn không thể làm điều đó một cách đồng bộ.

chỉnh sửa - người ta chỉ ra rằng, đúng với hình thức, IE không kích hoạt sự kiện "tải" trên <script>các thẻ được tải / đánh giá. Vì vậy, tôi cho rằng điều tiếp theo cần làm là tìm nạp tập lệnh với một XMLHttpRequest và sau eval()đó tự nó. (Hoặc, tôi cho là nhét văn bản vào <script>thẻ bạn thêm vào; môi trường thực thi của eval()nó bị ảnh hưởng bởi phạm vi cục bộ, vì vậy nó sẽ không nhất thiết phải làm những gì bạn muốn.)

chỉnh sửa - Kể từ đầu năm 2013 , tôi thực sự khuyên bạn nên xem xét một công cụ tải tập lệnh mạnh mẽ hơn như Requijs . Có rất nhiều trường hợp đặc biệt phải lo lắng. Đối với các tình huống thực sự đơn giản, có yepnope , hiện được tích hợp vào Modernizr .


3
Rất tiếc, nó không phải là trình duyệt chéo.
gblazex

69
Có thật không?? Ai không kích hoạt sự kiện "tải" khi tập lệnh được tải? Chờ đã - đừng nói với tôi.
Pointy

1
@Pointy Tôi đã giải quyết vấn đề này bằng cách sử dụng XMLHttpRequest và sau đó eval(). Tuy nhiên, gỡ lỗi nó là một cơn ác mộng b / c thông báo lỗi báo cáo dòng eval()xuất hiện, không phải là lỗi thực tế
PUK

3
Nhưng làm thế nào để requestjs làm điều này sau đó ?? Làm thế nào chúng bao gồm nhiều tập lệnh và kích hoạt chúng theo đúng thứ tự?
mmm,

4
Tất nhiên, document.write () là những gì bạn đang tìm kiếm. Không đẹp, nhưng nó hoạt động.
Jiri Vetyska

26

Điều này không đẹp, nhưng nó hoạt động:

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
</script>

<script type="text/javascript">
  functionFromOther();
</script>

Hoặc là

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  window.onload = function() {
    functionFromOther();
  };
</script>

Tập lệnh phải được bao gồm trong một <script>thẻ riêng biệt hoặc trước đó window.onload().

Điều này sẽ không hoạt động:

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  functionFromOther(); // Error
</script>

Điều tương tự có thể được thực hiện với việc tạo một nút, như Pointy đã làm, nhưng chỉ trong FF. Bạn không đảm bảo khi nào tập lệnh sẽ sẵn sàng trong các trình duyệt khác.

Là một người theo chủ nghĩa thuần túy XML, tôi thực sự ghét điều này. Nhưng nó hoạt động có thể đoán trước được. Bạn có thể dễ dàng bọc những thứ xấu xí document.write()đó để không cần phải nhìn vào chúng. Bạn thậm chí có thể thực hiện các bài kiểm tra và tạo một nút và nối nó sau đó quay trở lại document.write().


Bạn có chắc chắn rằng đoạn mã đầu tiên của bạn hoạt động trên tất cả các trình duyệt không?
Bogdan Gusiev

@BogdanGusiev Tôi không chắc 100%. Tôi đã thử nghiệm trong IE 8, (phiên bản hiện tại của) Firefox và Chrome. Rất có thể điều này sẽ không hoạt động với các loại tài liệu XHTML được dùng làm loại nội dung application/xhtml+xml.
Josh Johnson

1
Rất tiếc, không thể sử dụng thẻ script trong các tệp JS.
Clem

@Clem Bạn có thể làm a document.write("<SCR" + "IPT>" + "...").
John Weisz

Đây là một giải pháp thay thế OK cho các tập lệnh bên trong <head>tải một số phụ thuộc khác (hoặc các tệp riêng tư).
alecov

18

Đây là cách muộn nhưng để tham khảo trong tương lai cho bất kỳ ai muốn làm điều này, bạn có thể sử dụng như sau:

function require(file,callback){
    var head=document.getElementsByTagName("head")[0];
    var script=document.createElement('script');
    script.src=file;
    script.type='text/javascript';
    //real browsers
    script.onload=callback;
    //Internet explorer
    script.onreadystatechange = function() {
        if (this.readyState == 'complete') {
            callback();
        }
    }
    head.appendChild(script);
}

Tôi đã thực hiện một bài đăng blog ngắn về nó một thời gian trước đây http://crlog.info/2011/10/06/dynamently-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its -nạp vào/


điều này thực sự hoạt động? xem câu hỏi của tôi: stackoverflow.com/questions/17978255/…
mmm,

1
Điều này có vẻ thú vị. Một câu hỏi ... tại sao cần phải thực thi phương thức gọi lại hai lần? (script.onload = callback và callback () được sử dụng trong onreadystatechange)
Clem

1
onreadysteatechange dành cho IE và sẽ chỉ kích hoạt trên IE vì tải về sẽ không kích hoạt cho IE
Guilherme Ferreira

7

Lập trình không đồng bộ phức tạp hơn một chút vì hệ quả của việc thực hiện một yêu cầu được gói gọn trong một hàm thay vì tuân theo câu lệnh yêu cầu. Nhưng hành vi thời gian thực mà người dùng trải nghiệm có thể tốt hơn đáng kể vì họ sẽ không thấy máy chủ chậm chạp hoặc mạng chậm chạp khiến trình duyệt hoạt động như thể nó đã bị treo. Lập trình đồng bộkhông tôn trọngkhông nên được sử dụng trong các ứng dụng được mọi người sử dụng.

Douglas Crockford ( YUI Blog )

Được rồi, thắt dây an toàn vì đây sẽ là một chuyến đi gập ghềnh. Ngày càng có nhiều người hỏi về việc tải script động qua javascript, nó dường như là một chủ đề nóng.

Những lý do chính khiến điều này trở nên phổ biến là:

  • mô đun phía máy khách
  • quản lý phụ thuộc dễ dàng hơn
  • xử lý lỗi
  • lợi thế về hiệu suất

Về mô đun : rõ ràng là việc quản lý các phụ thuộc phía máy khách nên được xử lý ngay trên phía máy khách. Nếu một đối tượng, mô-đun hoặc thư viện nào đó là cần thiết, chúng tôi chỉ cần yêu cầu nó và tải nó động.

Xử lý lỗi : nếu tài nguyên bị lỗi, chúng tôi vẫn có cơ hội chỉ chặn các phần phụ thuộc vào tập lệnh bị ảnh hưởng, hoặc thậm chí có thể thử lại với một số thời gian trễ.

Hiệu suất đã trở thành một lợi thế cạnh tranh giữa các trang web, nó hiện là một yếu tố xếp hạng tìm kiếm. Những gì tập lệnh động có thể làm là bắt chước hành vi không đồng bộ trái ngược với cách chặn mặc định về cách trình duyệt xử lý tập lệnh. Tập lệnh chặn các tài nguyên khác, tập lệnh chặn phân tích thêm tài liệu HTML, tập lệnh chặn giao diện người dùng. Giờ đây với các thẻ tập lệnh động và các lựa chọn thay thế trình duyệt chéo của nó, bạn có thể thực hiện các yêu cầu không đồng bộ thực và chỉ thực thi mã phụ thuộc khi chúng có sẵn. Các tập lệnh của bạn sẽ tải song song ngay cả với các tài nguyên khác và kết xuất sẽ hoàn hảo.

Lý do tại sao một số người gắn bó với kịch bản đồng bộ là vì họ đã quen với nó. Họ nghĩ rằng đó là cách mặc định, nó là cách dễ dàng hơn, và một số thậm chí có thể nghĩ rằng đó là cách duy nhất.

Nhưng điều duy nhất chúng ta nên quan tâm khi điều này cần được quyết định liên quan đến thiết kế của ứng dụng là trải nghiệm người dùng cuối . Và trong lĩnh vực này không đồng bộ không thể bị đánh bại. Người dùng nhận được phản hồi ngay lập tức (hoặc nói lời hứa) và một lời hứa luôn tốt hơn là không có gì. Một màn hình trống khiến mọi người sợ hãi. Các nhà phát triển không nên lười biếng để nâng cao hiệu suất nhận thức .

Và cuối cùng là một số từ về mặt bẩn. Bạn nên làm gì để nó hoạt động trên các trình duyệt:

  1. học cách suy nghĩ không đồng bộ
  2. tổ chức mã của bạn theo mô-đun
  3. tổ chức mã của bạn để xử lý tốt các lỗi và các trường hợp cạnh tranh
  4. tăng cường dần dần
  5. luôn quan tâm đến lượng phản hồi phù hợp

Cảm ơn, cô gái. Tôi đoán tôi nên rõ ràng hơn. Tôi đã mong đợi điều này là không đồng bộ cuối cùng. Tôi chỉ muốn một cách để truy cập nó có ý nghĩa hợp lý đối với lập trình viên. Tôi muốn tránh những thứ như: Import ("package.mod1", function () {// thực hiện công việc với mod1}); Import ("package.mod2", function () {// thực hiện công việc với mod2}); Tôi đã xem qua tập lệnh và labj của bạn và mặc dù rất hay, nhưng có vẻ phức tạp hơn đối với nhu cầu của tôi. Tôi nghĩ rằng có thể có một cách đơn giản hơn và muốn tránh mang lại sự phụ thuộc thêm.
Josh Johnson

1
Bạn đã bỏ lỡ điểm của bài viết của tôi. Đó là tất cả về người dùng. Đây nên là ưu tiên hàng đầu của bạn. Mọi thứ khác chỉ là thứ yếu.
gblazex

2
Galam, điểm rất tốt. Trải nghiệm người dùng là rất quan trọng. Nói rõ hơn, tôi không sẵn sàng hy sinh trải nghiệm người dùng HOẶC chất lượng, mã có thể bảo trì. Tôi sẽ xem xét việc đóng cửa và labjs để xem họ có thể làm gì cho tôi. Nhưng hiện tại, tôi có thể cần gắn thẻ <script>. Thật không may, tôi không làm việc này một mình. Tôi làm việc với một nhóm các nhà phát triển cỡ trung bình nên ưu tiên cao là mã có thể bảo trì được. Nếu mọi người không thể tìm ra cách sử dụng lib hiệu quả thì điểm kinh nghiệm của người dùng sẽ ngay lập tức. Gọi lại là trực quan. Một cuộc gọi lại vì bạn đã nhập một gói thì không.
Josh Johnson

Một lần nữa, để rõ ràng, "đồng bộ" là một lựa chọn tồi của từ được sử dụng để nói lên quan điểm của tôi. Tôi không muốn trình duyệt bị đóng băng khi mọi thứ đang tải.
Josh Johnson

1
Nếu bạn cần tải đồng bộ thì sao? Nếu bạn thực sự cần chặn để duy trì trải nghiệm người dùng. Nếu bạn đang sử dụng hệ thống thử nghiệm A / B hoặc MVT dựa trên JavaScript. Làm cách nào để bạn muốn tải nội dung không đồng bộ và thay thế nội dung mặc định mà không gặp phải hiệu ứng nhấp nháy làm hỏng trải nghiệm người dùng? Tôi đang mở để gợi ý. Tôi có hơn 500 đồng nghiệp muốn biết giải pháp cho vấn đề này. Nếu bạn không có, vui lòng không đi kèm với các biểu thức như "Lập trình đồng bộ là không tôn trọng và không nên được sử dụng trong các ứng dụng được mọi người sử dụng."
transilvlad

6

Các câu trả lời trên đã chỉ cho tôi một hướng đi đúng. Đây là phiên bản chung của những gì tôi đã làm việc:

  var script = document.createElement('script');
  script.src = 'http://' + location.hostname + '/module';
  script.addEventListener('load', postLoadFunction);
  document.head.appendChild(script);

  function postLoadFunction() {
     // add module dependent code here
  }      

Khi nào được postLoadFunction()gọi?
Josh Johnson

1
@JoshJohnson script.addEventListener('load', postLoadFunction);có nghĩa là postLoadFunction được gọi khi tải tập lệnh.
Eric

4

Tôi đã gặp (các) vấn đề sau với các câu trả lời hiện có cho câu hỏi này (và các biến thể của câu hỏi này trên các chuỗi stackoverflow khác):

  • Không có mã đã tải nào có thể gỡ lỗi được
  • Nhiều giải pháp yêu cầu gọi lại để biết khi nào tải xong thay vì thực sự chặn, có nghĩa là tôi sẽ gặp lỗi thực thi khi gọi ngay mã đã tải (tức là đang tải).

Hay, chính xác hơn một chút:

  • Không có mã đã tải nào có thể gỡ lỗi (ngoại trừ từ khối thẻ tập lệnh HTML, nếu và chỉ khi giải pháp thêm phần tử tập lệnh vào dom và không bao giờ là tập lệnh có thể xem riêng lẻ.) => Với số lượng tập lệnh tôi phải tải ( và gỡ lỗi), điều này không thể chấp nhận được.
  • Không chặn được các giải pháp sử dụng sự kiện 'onreadystatechange' hoặc 'onload', đây là một vấn đề lớn vì mã ban đầu được tải đồng bộ các tập lệnh động bằng cách sử dụng 'request ([filename,' dojo / domReady ']);' và tôi đã thoát khỏi võ đường.

Giải pháp cuối cùng của tôi, tải tập lệnh trước khi quay lại VÀ có tất cả các tập lệnh có thể truy cập đúng cách trong trình gỡ lỗi (ít nhất là dành cho Chrome) như sau:

CẢNH BÁO: Mã sau đây CHỈ ĐƯỢC SỬ DỤNG trong chế độ 'phát triển'. (Đối với chế độ 'phát hành', tôi khuyên bạn nên đóng gói trước và thu nhỏ KHÔNG tải tập lệnh động, hoặc ít nhất là không có đánh giá).

//Code User TODO: you must create and set your own 'noEval' variable

require = function require(inFileName)
{
    var aRequest
        ,aScript
        ,aScriptSource
        ;

    //setup the full relative filename
    inFileName = 
        window.location.protocol + '//'
        + window.location.host + '/'
        + inFileName;

    //synchronously get the code
    aRequest = new XMLHttpRequest();
    aRequest.open('GET', inFileName, false);
    aRequest.send();

    //set the returned script text while adding special comment to auto include in debugger source listing:
    aScriptSource = aRequest.responseText + '\n////# sourceURL=' + inFileName + '\n';

    if(noEval)//<== **TODO: Provide + set condition variable yourself!!!!**
    {
        //create a dom element to hold the code
        aScript = document.createElement('script');
        aScript.type = 'text/javascript';

        //set the script tag text, including the debugger id at the end!!
        aScript.text = aScriptSource;

        //append the code to the dom
        document.getElementsByTagName('body')[0].appendChild(aScript);
    }
    else
    {
        eval(aScriptSource);
    }
};

4
function include(file){
return new Promise(function(resolve, reject){
        var script = document.createElement('script');
        script.src = file;
        script.type ='text/javascript';
        script.defer = true;
        document.getElementsByTagName('head').item(0).appendChild(script);

        script.onload = function(){
        resolve()
        }
        script.onerror = function(){
          reject()
        }
      })

 /*I HAVE MODIFIED THIS TO  BE PROMISE-BASED 
   HOW TO USE THIS FUNCTION 

  include('js/somefile.js').then(function(){
  console.log('loaded');
  },function(){
  console.log('not loaded');
  })
  */
}


1

Tôi đã quen với việc có nhiều tệp .js trên trang web của mình phụ thuộc vào nhau. Để tải chúng và đảm bảo rằng các phụ thuộc được đánh giá theo đúng thứ tự, tôi đã viết một hàm tải tất cả các tệp và sau đó, khi tất cả chúng được nhận, eval()chúng. Hạn chế chính là vì điều này không hoạt động với CDN. Đối với các thư viện như vậy (ví dụ: jQuery), tốt hơn là nên bao gồm chúng một cách tĩnh. Lưu ý rằng chèn nút script trong HTML động sẽ không đảm bảo rằng các kịch bản được đánh giá theo thứ tự đúng, ít nhất là không trong Chrome (đây là lý do chính để viết chức năng này).

function xhrs(reqs) {
  var requests = [] , count = [] , callback ;

  callback = function (r,c,i) {
    return function () {
      if  ( this.readyState == 4 ) {
        if (this.status != 200 ) {
          r[i]['resp']="" ;
        } 
        else {
          r[i]['resp']= this.responseText ;
        }
        c[0] = c[0] - 1 ;
        if ( c[0] == 0 ) {
          for ( var j = 0 ; j < r.length ; j++ ) {
            eval(r[j]['resp']) ;
          }
        }
      }
    }
  } ;
  if ( Object.prototype.toString.call( reqs ) === '[object Array]' ) {
    requests.length = reqs.length ;
  }
  else {
    requests.length = 1 ;
    reqs = [].concat(reqs);
  }
  count[0] = requests.length ;
  for ( var i = 0 ; i < requests.length ; i++ ) {
    requests[i] = {} ;
    requests[i]['xhr'] = new XMLHttpRequest () ;
    requests[i]['xhr'].open('GET', reqs[i]) ;
    requests[i]['xhr'].onreadystatechange = callback(requests,count,i) ;
    requests[i]['xhr'].send(null);
  }
}

Tôi chưa tìm ra cách tạo các tham chiếu đến cùng một giá trị mà không cần tạo một mảng (cho số lượng). Nếu không, tôi nghĩ nó là tự giải thích (khi mọi thứ được tải, eval()mọi tệp theo thứ tự đã cho, nếu không thì chỉ cần lưu trữ phản hồi).

Ví dụ sử dụng:

xhrs( [
       root + '/global.js' ,
       window.location.href + 'config.js' ,
       root + '/js/lib/details.polyfill.min.js',
       root + '/js/scripts/address.js' ,
       root + '/js/scripts/tableofcontents.js' 
]) ;

0

Trớ trêu thay, tôi có những gì bạn muốn, nhưng muốn một cái gì đó gần hơn với những gì bạn đã có.

Tôi đang tải mọi thứ theo cách động và không đồng bộ, nhưng với một lệnh loadgọi lại như vậy (sử dụng dojo và xmlhtpprequest)

  dojo.xhrGet({
    url: 'getCode.php',
    handleAs: "javascript",
    content : {
    module : 'my.js'
  },
  load: function() {
    myFunc1('blarg');
  },
  error: function(errorMessage) {
    console.error(errorMessage);
  }
});

Để được giải thích chi tiết hơn, hãy xem tại đây

Vấn đề là ở đâu đó dọc theo dòng, mã được đánh giá và nếu có bất kỳ điều gì sai với mã của bạn, console.error(errorMessage);câu lệnh sẽ chỉ ra dòng đó ở đâu eval(), không phải lỗi thực sự. Đây là một vấn đề lớn mà tôi thực sự đang cố gắng chuyển đổi trở lại các <script>câu lệnh (xem tại đây .


Thực tế thú vị: Tôi cũng đã quay lại <script>các thẻ và sử dụng quy ước (cùng với một số gói xây dựng) để chỉ gói các j của tôi theo cách có ý nghĩa.
Josh Johnson

@JoshJohnson tôi không phải là may mắn b / c tôi cần phải làm một tải rộng đầu tiên của gói với các kịch bản trong vòng này tải không đồng bộ và các kịch bản giữa các vành đai được nạp đồng bộ
PUK

Tôi đã may mắn và có thể giải quyết được điều gì đó. Tôi không ghen tị với vị trí của bạn.
Josh Johnson

0

Điều này hoạt động đối với các trình duyệt 'thường xanh' hiện đại hỗ trợ async / awaitfetch .

Ví dụ này được đơn giản hóa, không xử lý lỗi, để hiển thị các nguyên tắc cơ bản tại nơi làm việc.

// This is a modern JS dependency fetcher - a "webpack" for the browser
const addDependentScripts = async function( scriptsToAdd ) {

  // Create an empty script element
  const s=document.createElement('script')

  // Fetch each script in turn, waiting until the source has arrived
  // before continuing to fetch the next.
  for ( var i = 0; i < scriptsToAdd.length; i++ ) {
    let r = await fetch( scriptsToAdd[i] )

    // Here we append the incoming javascript text to our script element.
    s.text += await r.text()
  }

  // Finally, add our new script element to the page. It's
  // during this operation that the new bundle of JS code 'goes live'.
  document.querySelector('body').appendChild(s)
}

// call our browser "webpack" bundler
addDependentScripts( [
  'https://code.jquery.com/jquery-3.5.1.slim.min.js',
  'https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js'
] )

chúng tôi không thể nói nó như webpack... 1. đối với mọi tập lệnh, nó đang gửi a new HTTP request, 2. Điều này cũng sẽ không kiểm tra sự phụ thuộc giữa chúng, 3. Không phải tất cả các trình duyệt đều hỗ trợ async/awaitvà 4. Hiệu suất khôn ngoan là chúng tôi tẻ nhạt sau đó bình thường. Sẽ rất tốt nếu bạn thêm cái này vàohead
santosh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.