JavaScript ფაილების ჩატვირთვა საჭიროების მიხედვით

თანამედროვე ე.წ. RIA ვებ აპლიკაციებში, კლიენტის მხარეს განსაკუთრებული და საპასუხისმგებლო როლი აკისრია. სწორედ კლიენტის მხარეს იყრის თავს უამრავი CSS/JavaScript და სხვადასხვა გრაფიკული ელემენტები. RIA ტიპისი სისტემებში JavaScript – ს განსაკუთრებული როლი უჭირავს, რადგან სწორედ მისი მეშვეობით მიიღწევა აპლიკაციის განსაკუთრებული და მიმზიდველი ინტერქატიულობა. რაც უფრო მეტად არის დატვირთული მსგავსი ტიპის ელემენტებით აპლიკაცია მით უფრო მეტ რესურსს საჭიროებს იგი.

ერთი შეხედვით ამ ტიპის რესურსები(იგულისხმება ფაილების რაოდენობა, ზომა და ა.შ.) არ უნდა იწვედნენ განსაკუთრებულ სირთულეს, არც ჩატვირთვისა და არც სისწრაფის მიხედვით… თუმცა ეს მხოლოდ ერთი შეხედვით ჩანს ასე.

Yahoo! – ს ინჟინრებმა საკმაოდ საფუძვლიანი კვლევა ჩაატარეს ამ თემასთან დაკავშირებით და აღმოჩნდა რომ სერვერის გადატვირთვას სწორედ, დოკუმენტში ჭარბად გამოყენებული მსგავსი ელემენტები იწვევენ. გარდა სერვერის დატვირთვისა ეს თავისთავად ცხადია უარყოფითად აისახება თავად აპლიკაციის მომხმარებლებზე(ჩატვირთვის სისწრაფე, კოდის შესრულების/ინტერპრეტაციის დრო).

თუ მათ(და არა მხოლოდ მათ) რეკომენდაციებს გავითვალისწინებთ, ყოველი არასაჭირო ელემენტი უნდა ჩაიტვირთოს საჭიროების მიხედვით და არა ერთბაშად როგორც ეს ხდება უამრავ აპლიკაციაში.

ხშირ შემთხვევაში გამომდინარე ამოცანიდან და სისტემის სირთულიდან ასეთი ელემენტების საერთო რაოდენობამ ზომის მხრივ შესაძლებელია ჯამში შეადგინოს 500/1000 KB. ამ რესურსების ჩამოტვირთვას დამეთანხმებით რომ არცთუ ცოტა დრო და რესურსი ესაჭიროება. თუმცა ამ ელემენტებიდან მე პირადად გამოვარჩევდი JavaScript ფაილებს.

პრობლემა

რითია განსაკუთრებული JavaScript ფაილების ჩატვირთვის პროცესი? პირველ რიგში გავიხსენოთ თუ როგორ ვტვირთავთ JS ფაილებს ბრაუზერში:

<script src="somefolder/somefile.js" type="text/javascript"></script>

ეს არის სტანდარტული კოდი რომელსაც ვიყენებთ ყველა JS ფაილების ჩასატვირთად. რა არის მასში განსაკუთრებული და პრობლემატური? პირველ რიგში განსაკუთრებულობა მდგომარეობს იმაში, რომ სანამ ბრაუზერი სერვერიდან ჩამოტვირთავს ამ ფაილს, სხვა ყველა პროცესი ჩერდება. გარდა ჩამოტვირთვისა ბრაუზერმა ჩამოტვირთულ კოდს უნდა გაუკეთოს ანალიზი, აღარაფერს ვამბობ იმ შემთხვევაზე თუ ფაილი ჩატვირთვის თანავე სრულდება და გარკვეული სირთულის ოპერაციებს ასრულებს.

ცხადია ერთი და ორი მცირე ზომის ფაილის შემთხვევაში ეს არანაირ პრობლემას არ წარმოადგენს, მაგრამ შეგახსენებთ რომ ამ შემთხვევაში საუბარი გვაქვს RIA – ზე, სადაც ხშირ შემთხვევაში ათობით მსგავსი ფაილის ჩატვირთვა ხდება საჭირო, რაც თავისთავად ცხადია იწვევს აპლიკაციის ჩატვირთვის საგრძნობლად შენელებას.

ამ პრობლემის გვერდის ავლის რამდენიმე მექანიზმი არსებობს, მათ შორის ბევრი ფაილის ერთ ფაილში მოქცევა და მისი კომპრესია რაც ნამდვილად ძალიან ეფექტური საშუალებაა, და ასევე JS ფაილების ჩატვირთვის კოდის დოკუმენტის ბოლოში მოქცევა body ტეგის დახურვამდე.

ამ საშუალებებიდან პირველი ნებისმიერ შემთხვევაში აუცილებელია, თუმცა მეორე მიდგომა ყოველთვის არ გამოდგება, რადგან ხშირად აპლიკაცია სრულად JavaScript – ზეა დამოკიდებული და მისი ჩატვირთვა/ინტერპრეტირება/შესრულება ნებისმიერ შემთხვევაში უნდა დასრულდეს რათა მომხმარებელმა შეძლოს სისტემასთან მუშაობა.

მიუხედავად პირველი მეთოდის ეფექტურობისა და აუცილებლობისა(!) ხშირად ესეც კი არ არის საკმარისი. წარმოგიდგენიათ ისეთი სისტემა რომელსაც ყველა მოდული ერთდროულად ესაჭიროება? და ამ ყველაფრის ერთად ჩატვირთვა აუცილებელია? დამეთანხმებით რომ ასეთი ტიპის სისტემებს იშვიათად შევხვდებით. სწორედ ამ ბოლო პრობლემიდან გამომდინარეობს JS ფაილების საჭიროების მიხედვით ჩატვირთვის ამოცანა. ამ პრობლემის გადასაჭრელად არსებობს სპეციალური ბიბლიოთეკები, სხვადასხვა ბიბლიოთეკებს გააჩნიათ ამ ამოცანის გადაჭრის საშუალებები, თუმცა ყოველთვის გარე ბიბლიოთეკებზე დამოკიდებულნი ვერ ვიქნებით და ზოგს შესაძლებელია ჩემსავით უყვარს ამ ენაზე კოდირება და პრობლემების თავად გადაჭრა. სწორედ ასეთი ხალხისთვის არის განკუთვნილი ეს პოსტი :)

გადაწყვეტა

როგორ უნდა ჩავტვირთოთ JS ფაილი საჭიროების დროს? პრინციპში ეს ძალიან მარტივი გასაკეთებელია(თუმცა ერთი შეხედვით), მაგალითში მოყვანილია შესაბამისი კოდი:

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * @param String source of the JavaScript file
 */

function loadJS(src) {
      //შევქმნათ "script" ელემენტი
      var script = document.createElement('script');
      //მივუთითოთ ფაილის მისამართი
      script.src = src;
      script.type = 'text/javascript';
      //ჩავამატოთ ახლად შექმნილი "script" ელემენტი, დოკუმენტის head სექციაში
      document.head.appendChild(script);
}

სიმართლე ითქვას ფაილის ჩასატვირთად ზემოთ ნაჩვენები კოდი სრულებით საკმარისია… მაგრამ ზემოთ აღვნიშნე რომ ეს ამოცანა მარტივი მხოლოდ ერთი შეხედვით არის.

რაში მდგომარეობს ზუსტი პრობლემა? ამ ამოცანის გადაჭრისას თავის დროზე წავაწყდი შემდეგ პრობლემებს:

  1. თუ ფაილი ჩატვირთულია აღარ უნდა მოხდეს მისი ხელახალი ჩატვირთვა;
  2. ფაილის ზომიდან და ქსელის სისწრაფიდან გამომდინარე არ ვიცით როდის დაასრულებს ფაილი ჩატვირთვას;
  3. უნდა დავადგინოთ როდის დასრულდა ფაილის ჩატვირთვა, და ის კოდი რომელის შესრულებაც დამოკიდებულია ახლად ჩატვირთულ ფაილზე, შესრულდეს მხოლოდ ჩამოტვირთვის დასრულების შემდეგ;
  4. დასრულდა თუ არა ფაილის ჩამოტვირთვა მარტივი დასადგენია, თუმცა აქ განსხვავებამ იჩინა თავი ბრაუზერებს შორის. რა თქმა უნდა IE – მ გაიბრწყინა ტრადიციულად;

მაშ ასე, მივყვეთ ლოგიკურად:

  1. თუ ფაილი ჩატვირთულია აღარ უნდა მოხდეს მისი ხელახალი ჩატვირთვა

    ეს დეტალი სირთულეს არ წარმოადგენს, რადგან შესაძლებელია ჩატვირთული სკრიპტების მისამართების შენახვა და შემდგომ უკვე შედარების გაკეთება უკვე არსებულ მონაცემებთან.

    მე პირადად ამისათვის გამოვიყენე ჩვეულებრივი ობიექტი შემდეგი ხერხით:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
     * @param String source of the JavaScript file
     */

    function loadJS(src) {
        //რადგან ფუნქცია ასევე არის ობიექტი,
        //მისთვის ახალი "სტატიკური" წევრის დამატება
        //შესაძლებელია ქვემოთ ნაჩვენები ხერხით
        if (loadJS.scripts == undefined) {
            //ობიექტის შექმნა რომელშიც შევინახავთ ჩატვირთული ფაილების მისამართებს
            loadJS.scripts = {};
            //ყველა "script" ელემენტის მოპოვება და შემდგომ იტერაციის გზის მისამართების მოგროვება
            var scripts = document.getElementsByTagName('script');
            for (var i = 0; i < scripts.length; i++) {
                  if (scripts[i].src && scripts[i].src != null) {
                      //მისამართის შენახვა ობიექტის გასაღების სახით
                      loadJS.scripts[scripts[i].src] = 1;
                  }
            }
        }
    }

    როგორც მაგალითიდან ვხედავთ, ამ პრობლემის გადასაჭრელად საკმაოდ მარტივი მეთოდი გამოვიყენეთ. “script” ელემენტების იტერაციითა და “src” ატრიბუტის მნიშვნელობების მოგროვების გზით.

  2. ფაილის ზომიდან და ქსელის სისწრაფიდან გამომდინარე არ ვიცით როდის დაასრულებს ფაილი ჩატვირთვას;
  3. უნდა დავადგინოთ როდის დასრულდა ფაილის ჩატვირთვა, და ის კოდი რომელის შესრულებაც დამოკიდებულია ახლად ჩატვირთულ ფაილზე, შესრულდეს მხოლოდ ჩამოტვირთვის დასრულების შემდეგ;

    მეორე და მესამე პუნქტები ურთიერთდაკავშირებულია, და ამ პრობლემების გადასაჭრელად გვესაჭიროება ორი რამ: ა) “loadJS” ფუნქციისათვის მეორე პარამეტრის დამატება, რომელიც იქნება ე.წ. callback – ფუნქცია, რომლის გამოიძახებაც ფაილის ჩატვირთვის დასრულების შემდეგ მოხდება; ბ) ე.წ. queue(რიგი) რომელშიც შევინახავთ ამ callback – ებს, რათა მათი შესრულება მოხდეს კონკრეტულად მოთხოვნილი სკრიპტის ჩატვირთვის დასრულების შემდეგ. ეს საჭიროა იმ შემთხვევისათვის თუ ერთსა და იმავე სკრიპტზე მოთხოვნა რამდენჯერმე განმეორდება.

    აღსანიშნავია რომ “ა” პუნქტისათვის გასათვალისწინებელია ერთი დეტალი: ფუნქცია ყოველთვის არ სრულდება თავის საკუთარ კონტექსტში, ხშირად აუცილებელია ფუნქციის კონკრეტული ობიექტის კონტექსტში შესრულება. ამ პრობლემის თავიდან ასარიდებლად გამოვიყენებთ ჩვეულებრივ მასივს, რომლის პირველი ელემენტიც იქნება ობიექტი(ამ შემთხვევაში კონტექსტი), ხოლო მეორე პარამეტრი თავად callback ფუნქცია.

    “ბ” პუნქტისათვის საკმარისია აღვნიშნოთ, რომ “queue” – ს შესაქმნელად საკმარისია ჩვეულებრივი JS ობიექტი, რომელშიც გასაღების სახით გამოვიყენებთ “loadJS” ფუნქციის “src” პარამეტრს, ხოლო მნიშვნელობად ჩვეულებრივ მასივს რომელშიც შევინახავთ კონკრეტული მისამართის შესაბამის callback – ებს.

    თავად იმის დასადგენად სკრიპტის ჩამოტვირთვა დასრულებულია თუ არა გამოვიყენებთ “script” ელემენტის “onload” ივენთს, სწორედ მისი წარმოქმნის შემთხვევაში გვეცოდინება გარანტირებულად რომ სკრიპტი ჩამოიტვირთა და უსაფრთხოდ შეიძლება ამ სკრიპტთან დაკავშირებული callback – ების შესრულება.

    აქედან გამოდინარე კოდი მიიღებს შემდეგ სახეს:

    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
    /**
     * @param String source of the JavaScript file
     * @param Mixed callback function or object + function pair
     */

    function loadJS(src, callback) {
        //რადგან ფუნქცია ასევე არის ობიექტი,
        //მისთვის ახალი "სტატიკური" წევრის დამატება
        //შესაძლებელია ქვემოთ ნაჩვენები ხერხით
        if (loadJS.scripts == undefined) {
            //ობიექტის შექმნა რომელშიც შევინახავთ ჩატვირთული ფაილების მისამართებს
            loadJS.scripts = {};
            //ყველა "script" ელემენტის მოპოვება და შემდგომ იტერაციის გზის მისამართების მოგროვება
            var scripts = document.getElementsByTagName('script');
            for (var i = 0; i < scripts.length; i++) {
                  if (scripts[i].src && scripts[i].src != null) {
                      //მისამართის შენახვა ობიექტის გასაღების სახით
                      loadJS.scripts[scripts[i].src] = 1;
                  }
            }
        }
       
        //თუ "queue" ობიექტი არ არსებობს შევქმნათ იგი
        if (loadJS.queue == undefined) {
            loadJS.queue == {};
        }
       
        //"script" ელემენტის "onload" ივენთის ჰენდლერი
        /**
         * @param String source of the JavaScript File
         */

        var finishLoad = function(s) {
            //თუ მიმდინარე სკრიპტი იმყოფება რიგში
            if (loadJS.queue[s]) {
                //იტერაციის მეშვეობით სათითოად ვიღებთ ყველა "callback" ფუნქციას, და ვასრულებთ მათ
                for (var i = 0; i < loadJS.queue[s].length; i++) {
                    var _callback = loadJS.queue[s][i];
                    //თუ _callback არ არის მასივი ვასრულებთ მას როგორც ფუნქციას
                    //თუ მასივია ფუნქციის ".call" მეთოდის მეშვეობით ვასრულებთ მას შესაბამის კონტექსტში
                    !_callback.length ? _callback() : _callback[1].call(_callback[0]);
                }
                //რადგან სკრიპტის ჩატვირთვა დასრულდა, ვამატებთ მას ჩატვირთული კსრიპტების სიაში
                loadJS.scripts[s] = 1;
                //რადგან სკრიპტის ჩატვირთვა დასრულდა, ვშლით მას რიგიდან
                delete loadJS.queue[src];
            }
        }
       
        //შევამოწმოთ ჩატვირთულია თუ არა მოთხოვნილი სკრიპტი
        if (!loadJS.scripts[src]) {
            //სკრიპტი არ არის ჩატვირთული
            //შევამოწმოთ სკრიპტი ხომ არ იტვირთება (იმყოფება თუ არა რიგში)
            if (loadJS.queue[src] == undefined) {
               
                //ჩავამატოთ მისამართი რიგში, და შევქმნათ მასივი callback - ებისათვის
                loadJS.queue[src] = [];
               
                //შევქმნათ "script" ელემენტი და დავიწყოთ სკრიპტის ჩატვირთვა
                var script  = document.createElement('script');
                script.type = 'text/javascript';
                script.src  = src;
               
                //"_src" ატრიბუტი ხელოვნურია, და მისი მეშვეობით
                //"loadJS" ფუნქციის "src" პარამეტრის საწყის
                //მნიშვნელობას ვინახავთ თავიდან გაუგებრობების ასაცილებლად
                script._src = src;
               
                //"script" ელემენტის "onload" ივენთის გამოყენება "callback" ფუნქციების შესასრულებლად
                script.onload = function() {
                    //ფაილის ჩამოტვირთვა დასრულებულია, შევასრულოთ მისი შესაბამისი რიგში მყოფი callback - ები
                    finishLoad(this._src); //ზემოთ შექმნილი "_src" ატრიბუტის გამოყენება
                }
               
                //ჩავამატოთ ახლად შექმნილი "script" ელემენტი, დოკუმენტის head სექციაში
                document.head.appendChild(script);

            }
           
            //ამ მომენტისათვის მოთხოვნილი სკრიპტი გარანტირებულად ზის რიგში,
            //უკვე შესაძლებელია დავამატოთ შესაბამისი callback - ი
            loadJS.queue[src].push(callback);
           
        } else {
            //თუ ფაილი უკვე ჩატვირთულია პირდაპირ ვასრულებთ ჰენდლერს
            //"callback[1]" არის ფუნქცია, ხოლო "callback[0]" ობიექტი(კონტექსტი)
            !callback.length ? callback() : callback[1].call(callback[0]);
        }
       
    }
  4. დასრულდა თუ არა ფაილის ჩამოტვირთვა მარტივი დასადგენია, თუმცა აქ განსხვავებამ იჩინა თავი ბრაუზერებს შორის. რა თქმა უნდა IE – მ გაიბრწყინა ტრადიციულად;

    რაც შეეხება IE – ს, განსხვავება ამ შემთხვევაში ძალიან მარტივი აღმოჩნდა და გამომდინარე აქედან მარტივადაც მოგვარდა. განსხვავება კი “script” ელემენტის “onload” ივენთი აღმოჩნდა, განსხვავებით სხვა ბრაუზერებისგან IE – ს “onload” – ის ნაცვლად აქვს “onreadystatechange” ივენთი. ამ ივენთის დამუშავების სპეციფიკა კარგად არის ყველასათვის ნაცნობი XHR ობიექტის დამსახურებით.

    ერთადერთი რაც ამ არათავსებადობის აღმოსაფხვრელად გვესაჭიროება არის შემდეგი კოდი:

    1
    2
    3
    4
    5
    6
    7
    8
    //თუ ბრაზუერი IE - ა
    script.onreadystatechange = function() {
        //უნდა შევამოწმოთ ორი სტატუსი: "loaded" და "complete"
        if (/^loaded|complete$/i.test(this.readyState)) {
            //ფაილის ჩამოტვირთვა დასრულებულია, შევასრულოთ მისი შესაბამისი რიგში მყოფი callback - ები
            finishLoad(this._src); //ზემოთ შექმნილი "_src" ატრიბუტის გამოყენება
        };
    }

    სულ ეს არის და ეს :)

დასკვნა

იმედი მაქვს ამჯერადაც თქვენთვის საინტერესო თემას შევეხე და ეს მცირედი მაგალითი სასარგებლო იქნება ამ ბლოგის JavaScript – ით დაინტერესებული მკითხველისათვის.

ტეგები: , ,

4 Responses to “JavaScript ფაილების ჩატვირთვა საჭიროების მიხედვით”

  1. საინტერესოა და მეტად საინტერესოც. გაიხარე კაცი ხარ. კაკრას უკვე ამ საკითხზე ვფიქრობდი ეს დღეებია :)
    მადლობა

  2. მიშა says:

    კარგი სტატიაა.
    ეს მეთოდი არამარტო სკრიპტების ჩასატვირთად, არამედ სერვერთან კომუნიკაციისთვისაც, მონაცემთა გადასაცემადაც გამოდგება არა?
    ასე შეიძლება სხვა დომეინზე მყოფ სერვერსაც დაუკავშირდე, xhr-ის შემთვევაში ეგ აკრძალულია.
    რა მაინტერესებს, უკვე ჩატვირთულია თუ არა ჩვენით რომ არ ვამოწმოთ, ბრაუზერის ქეში ვერ მოუვლის მაგ ამბავს? ერთი სკრიპტი ორჯერ თუ მოითხოვე, მემგონი ბრაუზერები ერთხელ გადმოწერენ, არა? ნუ თუ ჰედერებით არ აკრძალე ქეში.

    ერთი შენიშვნა მექნება:

    ფუნქცია ყოველთვის არ სრულდება თავის საკუთარ კონტექსტში, ხშირად აუცილებელია ფუნქციის კონკრეტული ობიექტის კონტექსტში შესრულება. ამ პრობლემის თავიდან ასარიდებლად გამოვიყენებთ ჩვეულებრივ მასივს, რომლის პირველი ელემენტიც იქნება ობიექტი(ამ შემთხვევაში კონტექსტი), ხოლო მეორე პარამეტრი თავად callback ფუნქცია.

    ქოლბექისთვის მასივის გამოყენება პჰპ-ში აპრობირებული მეთოდია, მაგრამ ჯავასკრიპტში ამას უფრო “ანონიმური” ფუნქციის დახმარებით აკეთებენ:
    დავუშვათ გვინდა ქოლბექად გადავცეთ myobject-ის mymethod მეთოდი:

    loadJS(‘somefile.js’, function(){myobject.mymethod();});

    თუ გვინდა გადავცეთ myfunc ფუნქცია, რომელიც არ არის myobject-ის მეთოდი მაგრამ ჩვენ მაინც გვინდა myobject-ის კონტექსტში შესრულდეს:
    loadJS(‘somefile.js’, function(){mymethod.call(myobject);});

  3. მიშა

    დიდი მადლობა შეფასებისთვის :)

    შენიშვნას რაც შეეხება, მასივის გამოყენებაზე არანაირი აკრძალვა და შეზღუდვა არ არსებობს, ყოველჯერზე ანონიმური ფუნქციების წერა ცოტა არ იყოს მოსაბეზრებელია ხოლმე IMHO :)

    რა მაინტერესებს, უკვე ჩატვირთულია თუ არა ჩვენით რომ არ ვამოწმოთ, ბრაუზერის ქეში ვერ მოუვლის მაგ ამბავს? ერთი სკრიპტი ორჯერ თუ მოითხოვე, მემგონი ბრაუზერები ერთხელ გადმოწერენ, არა? ნუ თუ ჰედერებით არ აკრძალე ქეში.

    ამას რაც შეეხება, ბრაუზერი ვერ დაგვეხმარება ისეთ შემთხვევაში თუ ერთიმეორეს მიყოლებით რამდენჯერმე მოხდა ფაილის ჩატვირთვაზე მოთხოვნა და ეს ფაილები ჯერ კიდევ სკრიპტის queue – ში ზის… ანუ ჯერ ჩატვირთული არ არის და პროცესშია(შესაბამისად ბრაუზერის კეშშიც ვერ იქნება ჯერ).

    თან მეორე მომენტია, მაგ მიდგომით კონკრეტული სკრიპტის ქოლბექებს ერთად ვუყრი თავს და საკმარისია სკრიპტის ჩატვირთვის ივენთი წარმოიქმნას ყველაფერი ერთდროულად შესრულდება.

  4. მიშა says:

    “შენიშვნას რაც შეეხება, მასივის გამოყენებაზე არანაირი აკრძალვა და შეზღუდვა არ არსებობს”
    ცხადია :) გემოვნების ამბავია და დიდი მნიშვნელობაც არ აქვს, მაგრამ მე ცალსახად ანონიმური ფუნქციებით მირჩევნია, სპეციფიკაციის სიმარტივის გამო – “ფუნქცია და არაფერი ფუნქციის გარდა” :)

    “ამას რაც შეეხება, ბრაუზერი ვერ დაგვეხმარება ისეთ შემთხვევაში თუ ერთიმეორეს მიყოლებით რამდენჯერმე მოხდა ფაილის ჩატვირთვაზე მოთხოვნა და ეს ფაილები ჯერ კიდევ სკრიპტის queue – ში ზის”
    გასაგებია. ისე კარგი იმპლემენტაცია მაგასაც უნდა ითვალისწინებდეს და მაინც არ ტვირთავდეს 2-ჯერ ერთ რესურსს.
    ნებისმიერ შემთხვევაში, ბრაუზერის ქეშს თუ ენდე და ბევრჯერ თუ მოითხოვე ერთი ფაილი, , გატენი დოკუმენტს script ელემენტებით და არ იქნება ეგ კაი…

დატოვე კომენტარი:

ქართული კლავიატურა, ჩართვა/გამორთვა კლავიშით "~"