Why do browsers inefficiently make 2 requests here

2019-03-24 20:06发布

I noticed something odd regarding ajax and image loading. Suppose you have an image on the page, and ajax requests the same image - one would guess that ajax requests would hit the browser cache, or it should at least only make one request, the resulting image going to the page and the script that wants to read/process the image.

Surprisingly, I found that even when the javascript waits for the entire page to load, the image request still makes a new request! Is this a known bug in Firefox and Chrome, or something bad jQuery ajax is doing?

Here you can see the problem, open Fiddler or Wireshark and set it to record before you click "run":

<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<div id="something" style="background-image:url(http://jsfiddle.net/img/logo-white.png);">Hello</div>
<script>

jQuery(function($) {
    $(window).load(function() {
        $.get('http://jsfiddle.net/img/logo-white.png');
    })
});

</script>

Note that in Firefox it makes two requests, both resulting in 200-OK, and sending the entire image back to the browser twice. In Chromium, it at least correctly gets a 304 on second request instead of downloading the entire contents twice.

Oddly enough, IE11 downloads the entire image twice, while it seems IE9 aggressively caches it and downloads it once.

Ideally I would hope the ajax wouldn't make a second request at all, since it is requesting exactly the same url. Is there a reason css and ajax in this case usually have different caches, as though the browser is using different cache storage for css vs ajax requests?

9条回答
混吃等死
2楼-- · 2019-03-24 20:18

I want JS-accessible image

Have you tried to CSS using jQuery? It is pretty fun - you have full CRUD (Create, read, update, delete) CSS elements. For example do image resize on server side:

$('#container').css('background', 'url(somepage.php?src=image_source.jpg'
    + '&w=' + $("#container").width() 
    + '&h=' + $("#container").height() + '&zc=1');

Surprisingly, I found that even when the javascript waits for the entire page to load, the image request still makes a new request! Is this a known bug in Firefox and Chrome, or something bad jQuery ajax is doing?

It is blatantly obvious that this is not a browser bug.

The computer is deterministic and does what exactly you tell it to (not want you want it to do). If you want to cache images it is done in server side. Based on who handles caching it can be handled as:

  1. Server (like IIS or Apache) cache - typically caches things that are reused often (ex: 2ce in 5 seconds)
  2. Server side application cache - typically it reuses server custom cache or you create sprite images or ...
  3. Browser cache - Server side adds cache headers to images and browsers maintain cache

If it is not clear then I would like to make it clear : You don't cache images with javascript.

Ideally I would hope the ajax wouldn't make a second request at all, since it is requesting exactly the same url.

What you try to do is to preload images.

Once an image has been loaded in any way into the browser, it will be in the browser cache and will load much faster the next time it is used whether that use is in the current page or in any other page as long as the image is used before it expires from the browser cache.

So, to precache images, all you have to do is load them into the browser. If you want to precache a bunch of images, it's probably best to do it with javascript as it generally won't hold up the page load when done from javascript. You can do that like this:

function preloadImages(array) {
    if (!preloadImages.list) {
        preloadImages.list = [];
    }
    for (var i = 0; i < array.length; i++) {
        var img = new Image();
        img.onload = function() {
            var index = preloadImages.list.indexOf(this);
            if (index !== -1) {
                // remove this one from the array once it's loaded
                // for memory consumption reasons
                preloadImages.splice(index, 1);
            }
        }
        preloadImages.list.push(img);
        img.src = array[i];
    }
}

preloadImages(["url1.jpg", "url2.jpg", "url3.jpg"]);

Then, once they've been preloaded like this via javascript, the browser will have them in its cache and you can just refer to the normal URLs in other places (in your web pages) and the browser will fetch that URL from its cache rather than over the network.

Source : How do you cache an image in Javascript

Is there a reason css and ajax in this case usually have different caches, as though the browser is using different cache storage for css vs ajax requests?

enter image description here

Even in absence of information do not jump to conclusions!

One big reason to use image preloading is if you want to use an image for the background-image of an element on a mouseOver or :hover event. If you only apply that background-image in the CSS for the :hover state, that image will not load until the first :hover event and thus there will be a short annoying delay between the mouse going over that area and the image actually showing up.

Technique #1 Load the image on the element's regular state, only shift it away with background position. Then move the background

position to display it on hover.

#grass { background: url(images/grass.png) no-repeat -9999px -9999px; }
#grass:hover { background-position: bottom left; }

Technique #2 If the element in question already has a background-image applied and you need to change that image, the above

won't work. Typically you would go for a sprite here (a combined background image) and just shift the background position. But if that isn't possible, try this. Apply the background image to another page element that is already in use, but doesn't have a background image.

#random-unsuspecting-element { 
    background: url(images/grass.png) no-repeat -9999px -9999px; }
#grass:hover { background: url(images/grass.png) no-repeat; }

The idea create new page elements to use for this preloading technique may pop into your head, like #preload-001, #preload-002, but that's rather against the spirit of web standards. Hence the using of page elements that already exist on your page.

查看更多
乱世女痞
3楼-- · 2019-03-24 20:19

This may be a shot in the dark, but here's what I think is happening.

According to, http://api.jquery.com/jQuery.get/

dataType
  Type: String
  The type of data expected from the server. 
  Default: Intelligent Guess (xml, json, script, or html).

Gives you 4 possible return types. There is no datatype of image/gif being returned. Thus, the browser doesn't test it's cache for the src document as it is being delivered a a different mime type.

查看更多
再贱就再见
4楼-- · 2019-03-24 20:26

The helpful folks at Mozilla gave some details as to why this happens. Apparently Firefox assumes an "anonymous" request could be different than normal, and for this reason it makes a second request and doesn't consider the cached value with different headers to be the same request.

https://bugzilla.mozilla.org/show_bug.cgi?id=1075297

查看更多
劫难
5楼-- · 2019-03-24 20:27

The server decides what can be cached and for how long. However, it again depends on the browser, whether or not to follow it. Most web browsers like Chrome, Firefox, Safari, Opera and IE follow it, though.

The point that I want to make here, is that your web sever might be configured to not allow your browser to cache the content, thus, when you request the image through CSS and JS, the browser follows your server's orders and doesn't cache it and thus it requests the image twice...

查看更多
等我变得足够好
6楼-- · 2019-03-24 20:31

Your fiddle tries to load a resource from another domain via ajax: cross domain request

I think I created a better example. Here is the code:

<img src="smiley.png" alt="smiley" />
<div id="respText"></div>

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
  $(window).load(function(){        
    $.get("smiley.png", function(){
      $("#respText").text("ajax request succeeded");
    });
  });
</script>

You can test the page here.

According to Firebug and the chrome network panel the image is returned with the status code 200 and the image for the ajax request is coming from the cache:

Firefox: Firebug

Chrome: chrome network panel

So I cannot find any unexpected behavior.

查看更多
聊天终结者
7楼-- · 2019-03-24 20:33

I use the newest Google Chrome and it makes one request. But in your JSFIDDLE example you are loading jQuery twice. First with JSFIDDLE and second in your code over script tag. Improved: JSFIDDLE

<div id="something" style="background-image:url('http://jsfiddle.net/img/logo-white.png');">Hello</div>

<script>
    jQuery(window).load(function() {
        jQuery.get('http://jsfiddle.net/img/logo-white.png');
    });

    // or

    jQuery(function($) {
        jQuery.get('http://jsfiddle.net/img/logo-white.png');
    });
</script>

jQuery(function($) {...} is called when DOM is ready and jQuery(window).load(...); if DOM is ready and every image and other resources are loaded. To put both together nested makes no sense, see also here: window.onload vs $(document).ready()

Sure, the image is loaded two times in 'Network' tab of the web inspector. First through your CSS and second through your JavaScript. The second request is probably cached.

UPDATE: But every request if cached or not is shown in this tab. See following example: http://jsfiddle.net/95mnf9rm/4/ There are 5 request with cached AJAX calls and 5 without caching. And 10 request are shown in 'Network' tab. When you use your image twice in CSS then it's only requested once. But if you explicitly make a AJAX call then the browser makes an AJAX call. As you want. And then maybe it's cached or not, but it's explicitly requested, isn't it?

查看更多
登录 后发表回答