[翻译]High Performance JavaScript(022)

第七章  Ajax  异步JavaScript和XML

 

    Ajax is a cornerstone of high-performance JavaScript. It can be used to make a page load faster by delaying the download of large resources. It can prevent page loads altogether by allowing for data to be transferred between the client and the server asynchronously. It can even be used to fetch all of a page's resources in one HTTP request. By choosing the correct transmission technique and the most efficient data format, you can significantly improve how your users interact with your site.

    Ajax是高性能JavaScript的基石。它可以通过延迟下载大量资源使页面加载更快。它通过在客户端和服务器之间异步传送数据,避免页面集体加载。它还用于在一次HTTP请求中获取整个页面的资源。通过选择正确的传输技术和最有效的数据格式,你可以显著改善用户与网站之间的互动。

 

    This chapter examines the fastest techniques for sending data to and receiving it from the server, as well as the most efficient formats for encoding data.

    本章考察从服务器收发数据最快的技术,以及最有效的数据编码格式。

 

Data Transmission  数据传输

 

    Ajax, at its most basic level, is a way of communicating with a server without unloading the current page; data can be requested from the server or sent to it. There are several different ways of setting up this communication channel, each with its own advantages and restrictions. This section briefly examines the different approaches and discusses the performance implications of each.

    Ajax,在它最基本的层面,是一种与服务器通讯而不重载当前页面的方法,数据可从服务器获得或发送给服务器。有多种不同的方法构造这种通讯通道,每种方法都有自己的优势和限制。本节简要地介绍这些不同方法,并讨论各自对性能的影响。

 

Requesting Data  请求数据

 

    There are five general techniques for requesting data from a server:

    有五种常用技术用于向服务器请求数据:

 

• XMLHttpRequest (XHR)
• Dynamic script tag insertion  动态脚本标签插入
• iframes
• Comet
• Multipart XHR                 多部分的XHR

 

    The three that are used in modern high-performance JavaScript are XHR, dynamic script tag insertion, and multipart XHR. Use of Comet and iframes (as data transport techniques) tends to be extremely situational, and won't be covered here.

    在现代高性能JavaScript中使用的三种技术是XHR,动态脚本标签插入和多部分的XHR。使用Comet和iframe(作为数据传输技术)往往是极限情况,不在这里讨论。

 

XMLHttpRequest

 

    By far the most common technique used, XMLHttpRequest (XHR) allows you to asynchronously send and receive data. It is well supported across all modern browsers and allows for a fine degree of control over both the request sent and the data received. You can add arbitrary headers and parameters (both GET and POST) to the request, and read all of the headers returned from the server, as well as the response text itself. The following is an example of how it can be used:

    目前最常用的方法中,XMLHttpRequest(XHR)用来异步收发数据。所有现代浏览器都能够很好地支持它,而且能够精细地控制发送请求和数据接收。你可以向请求报文中添加任意的头信息和参数(包括GET和POST),并读取从服务器返回的头信息,以及响应文本自身。以下是使用示例:

 

var url = '/data.php';
var params = [
  'id=934875',
  'limit=20'
];
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
  if (req.readyState === 4) {
    var responseHeaders = req.getAllResponseHeaders(); // Get the response headers.
    var data = req.responseText; // Get the data.
    // Process the data here...
  }
}
req.open('GET', url + '?' + params.join('&'), true);
req.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); // Set a request header.
req.send(null);
// Send the request.

    This example shows how to request data from a URL, with parameters, and how to read the response text and headers. A readyState of 4 indicates that the entire response has been received and is available for manipulation.

    此例显示了如何从URL请求数据,使用参数,以及如何读取响应报文和头信息。readyState等于4表示整个响应报文已经收并完可用于操作。

 

    It is possible to interact with the server response as it is still being transferred by listening for readyState 3. This is known as streaming, and it is a powerful tool for improving the performance of your data requests:

    readyState等于3则表示此时正在与服务器交互,响应报文还在传输之中。这就是所谓的“流”,它是提高数据请求性能的强大工具:

 

req.onreadystatechange = function() {
  if (req.readyState === 3) { // Some, but not all, data has been received.
    var dataSoFar = req.responseText;
    ...
  }
  else if (req.readyState === 4) { // All data has been received.
    var data = req.responseText;
    ...
  }
}

    Because of the high degree of control that XHR offers, browsers place some restrictions on it. You cannot use XHR to request data from a domain different from the one the code is currently running under, and older versions of IE do not give you access to readyState 3, which prevents streaming. Data that comes back from the request is treated as either a string or an XML object; this means large amounts of data will be quite slow to process.

    由于XHR提供了高级别的控制,浏览器在上面增加了一些限制。你不能使用XHR从当前运行的代码域之外请求数据,而且老版本的IE也不提供readyState 3,它不支持流。从请求返回的数据像一个字符串或者一个XML对象那样对待,这意味着处理大量数据将相当缓慢。

 

    Despite these drawbacks, XHR is the most commonly used technique for requesting data and is still the most powerful. It should be the one you look to first.

    尽管有这些缺点,XHR仍旧是最常用的请求数据技术,也是最强大的。它应当成为你的首选。

 

POST versus GET when using XHR.  使用XHR时,应使用POST还是GET

 

    When using XHR to request data, you have a choice between using POST or GET. For requests that don't change the server state and only pull back data (this is called an idempotent action), use GET. GET requests are cached, which can improve performance if you're fetching the same data several times.

    当使用XHR请求数据时,你可以选择POST或GET。如果请求不改变服务器状态只是取回数据(又称作幂等动作)则使用GET。GET请求被缓冲起来,如果你多次提取相同的数据可提高性能。

 

    POST should be used to fetch data only when the length of the URL and the parameters are close to or exceed 2,048 characters. This is because Internet Explorer limits URLs to that length, and exceeding it will cause your request to be truncated.

    只有当URL和参数的长度超过了2'048个字符时才使用POST提取数据。因为Internet Explorer限制URL的长度,过长将导致请求(参数)被截断。

 

Dynamic script tag insertion  动态脚本标签插入

 

    This technique overcomes the biggest limitation of XHR: it can request data from a server on a different domain. It is a hack; instead of instantiating a purpose-built object, you use JavaScript to create a new script tag and set its source attribute to a URL in a different domain.

    该技术克服了XHR的最大限制:它可以从不同域的服务器上获取数据。这是一种黑客技术,而不是实例化一个专用对象,你用JavaScript创建了一个新脚本标签,并将它的源属性设置为一个指向不同域的URL。

 

var scriptElement = document.createElement('script');
scriptElement.src = 'http://any-domain.com/javascript/lib.js';
document.getElementsByTagName('head')[0].appendChild(scriptElement);

    But dynamic script tag insertion offers much less control than XHR. You can't send headers with the request. Parameters can only be passed using GET, not POST. You can't set timeouts or retry the request; in fact, you won't necessarily know if it fails. You must wait for all of the data to be returned before you can access any of it. You don't have access to the response headers or to the entire response as a string.

    但是动态脚本标签插入与XHR相比只提供更少的控制。你不能通过请求发送信息头。参数只能通过GET方法传递,不能用POST。你不能设置请求的超时或重试,实际上,你不需要知道它是否失败了。你必须等待所有数据返回之后才可以访问它们。你不能访问响应信息头或者像访问字符串那样访问整个响应报文。

 

    This last point is especially important. Because the response is being used as the source for a script tag, it must be executable JavaScript. You cannot use bare XML, or even bare JSON; any data, regardless of the format, must be enclosed in a callback function.

    最后一点非常重要。因为响应报文被用作脚本标签的源码,它必须是可执行的JavaScript。你不能使用裸XML,或者裸JSON,任何数据,无论什么格式,必须在一个回调函数之中被组装起来。

 

var scriptElement = document.createElement('script');
scriptElement.src = 'http://any-domain.com/javascript/lib.js';
document.getElementsByTagName('head')[0].appendChild(scriptElement);
function jsonCallback(jsonString) {
  var data = eval('(' + jsonString + ')');
  // Process the data here...
}

    In this example, the lib.js file would enclose the data in the jsonCallback function:

    在这个例子中,lib.js文件将调用jsonCallback函数组装数据:

 

jsonCallback({ "status": 1, "colors": [ "#fff", "#000", "#ff0000" ] });

    Despite these limitations, this technique can be extremely fast. The response is executed as JavaScript; it is not treated as a string that must be further processed. Because of this, it has the potential to be the fastest way of getting data and parsing it into something you can access on the client side. We compare the performance of dynamic script tag insertion with the performance of XHR in the section on JSON, later in this chapter.

    尽管有这些限制,此技术仍然非常迅速。其响应结果是运行JavaScript,而不是作为字符串必须被进一步处理。正因为如此,它可能是客户端上获取并解析数据最快的方法。我们比较了动态脚本标签插入和XHR的性能,在本章后面JSON一节中。

 

    Beware of using this technique to request data from a server you don't directly control. JavaScript has no concept of permission or access control, so any code that you incorporate into your page using dynamic script tag insertion will have complete control over the page. This includes the ability to modify any content, redirect users to another site, or even track their actions on this page and send the data back to a third party. Use extreme caution when pulling in code from an external source.

    请小心使用这种技术从你不能直接控制的服务器上请求数据。JavaScript没有权限或访问控制的概念,所以你的页面上任何使用动态脚本标签插入的代码都可以完全控制整个页面。包括修改任何内容、将用户重定向到另一个站点,或跟踪他们在页面上的操作并将数据发送给第三方。使用外部来源的代码时务必非常小心。

 

Multipart XHR  多部分XHR

 

    The newest of the techniques mentioned here, multipart XHR (MXHR) allows you to pass multiple resources from the server side to the client side using only one HTTP request. This is done by packaging up the resources (whether they be CSS files, HTML fragments, JavaScript code, or base64 encoded images) on the server side and sending them to the client as a long string of characters, separated by some agreed-upon string. The JavaScript code processes this long string and parses each resource according to its mime-type and any other "header" passed with it.

    这里介绍最新的技术,多部分XHR(MXHR)允许你只用一个HTTP请求就可以从服务器端获取多个资源。它通过将资源(可以是CSS文件,HTML片段,JavaScript代码,或base64编码的图片)打包成一个由特定分隔符界定的大字符串,从服务器端发送到客户端。JavaScript代码处理此长字符串,根据它的媒体类型和其他“信息头”解析出每个资源。

 

    Let's follow this process from start to finish. First, a request is made to the server for several image resources:

    让我们从头到尾跟随这个过程。首先,发送一个请求向服务器索取几个图像资源:

 

var req = new XMLHttpRequest();
req.open('GET', 'rollup_images.php', true);
req.onreadystatechange = function() {
  if (req.readyState == 4) {
    splitImages(req.responseText);
  }
};
req.send(null);

    This is a very simple request. You are asking for data from rollup_images.php, and once you receive it, you send it to the function splitImages.

    这是一个非常简单的请求。你向rollup_images.php要求数据,一旦你收到返回结果,就将它交给函数splitImages处理。

 

    Next, on the server, the images are read and converted into strings:

    下一步,服务器读取图片并将它们转换为字符串:

 

// Read the images and convert them into base64 encoded strings.
$images = array('kitten.jpg', 'sunset.jpg', 'baby.jpg');
foreach ($images as $image) {
  $image_fh = fopen($image, 'r');
  $image_data = fread($image_fh, filesize($image));
  fclose($image_fh);
    $payloads[] = base64_encode($image_data);
  }(译者注:疑原文有误,此括号多余
}
// Roll up those strings into one long string and output it.
$newline = chr(1); // This character won't appear naturally in any base64 string.
echo implode($newline, $payloads);

    This piece of PHP code reads three images and converts them into long strings of base64 characters. They are concatenated using a single character, Unicode character 1, and output back to the client.

    这段PHP代码读取三个图片,并将它们转换成base64字符串。它们之间用一个简单的字符,UNICODE的1,连接起来,然后返回给客户端。

 

    Once on the client side, the data is processed by the splitImages function:

    然后回到客户端,此数据由splitImage函数处理:

 

function splitImages(imageString) {
  var imageData = imageString.split("/u0001");
  var imageElement;
  for (var i = 0, len = imageData.length; i < len; i++) {
    imageElement = document.createElement('img');
    imageElement.src = 'data:image/jpeg;base64,' + imageData[i];
    document.getElementById('container').appendChild(imageElement);
  }
}

    This function takes the concatenated string and splits it up again into three pieces. Each piece is then used to create an image element, and that image element is inserted into the page. The image is not converted from a base64 string back to binary data; instead it is passed to the image element using a data: URL and the image/jpeg mime-type.

    此函数将拼接而成的字符串分解为三段。每段用于创建一个图像元素,然后将图像元素插入页面中。图像不是从base64转换成二进制,而是使用data:URL并指定image/jpeg媒体类型。

 

    The end result is that three images have been passed to the browser as a single HTTP request. This could be done with 20 images or 100; the response would be larger, but it would still take only one HTTP request. It can also be expanded to other types of resources. JavaScript files, CSS files, HTML fragments, and images of many types can all be combined into one response. Any data type that can be handled as a string by JavaScript can be sent. Here are functions that will take strings for JavaScript code, CSS styles, and images and convert them into resources the browser can use:

    最终结果是:在一次HTTP请求中向浏览器传入了三张图片。也可以传入20张或100张,响应报文会更大,但也只是一次HTTP请求。它也可以扩展至其他类型的资源。JavaScript文件,CSS文件,HTML片段,许多类型的图片都可以合并成一次响应。任何数据类型都可作为一个JavaScript处理的字符串被发送。下面的函数用于将JavaScript代码、CSS样式表和图片转换为浏览器可用的资源:

 

function handleImageData(data, mimeType) {
  var img = document.createElement('img');
  img.src = 'data:' + mimeType + ';base64,' + data;
  return img;
}
function handleCss(data) {
  var style = document.createElement('style');
  style.type = 'text/css';
  var node = document.createTextNode(data);
  style.appendChild(node);
  document.getElementsByTagName('head')[0].appendChild(style);
}
function handleJavaScript(data) {
  eval(data);
}

    As MXHR responses grow larger, it becomes necessary to process each resource as it is received, rather than waiting for the entire response. This can be done by listening for readyState 3:

    由于MXHR响应报文越来越大,有必要在每个资源收到时立刻处理,而不是等待整个响应报文接收完成。这可以通过监听readyState 3实现:

 

var req = new XMLHttpRequest();
var getLatestPacketInterval, lastLength = 0;
req.open('GET', 'rollup_images.php', true);
req.onreadystatechange = readyStateHandler;
req.send(null);
function readyStateHandler{
  if (req.readyState === 3 && getLatestPacketInterval === null) {
    // Start polling.
    getLatestPacketInterval = window.setInterval(function() {
      getLatestPacket();
    }, 15);
  }
  if (req.readyState === 4) {
    // Stop polling.
    clearInterval(getLatestPacketInterval);
    // Get the last packet.
    getLatestPacket();
  }
}
function getLatestPacket() {
  var length = req.responseText.length;
  var packet = req.responseText.substring(lastLength, length);
  processPacket(packet);
  lastLength = length;
}

    Once readyState 3 fires for the first time, a timer is started. Every 15 milliseconds, the response is checked for new data. Each piece of data is then collected until a delimiter character is found, and then everything is processed as a complete resource.

    当readyState 3第一次发出时,启动了一个定时器。每隔15毫秒检查一次响应报文中的新数据。数据片段被收集起来直到发现一个分隔符,然后一切都作为一个完整的资源处理。

 

    The code required to use MXHR in a robust manner is complex but worth further study. The complete library can be easily be found online at http://techfoolery.com/mxhr/.

    以健壮的方式使用MXHR的代码很复杂但值得进一步研究。完整的库可参见http://techfoolery.com/mxhr/

 

    There are some downsides to using this technique, the biggest being that none of the fetched resources are cached in the browser. If you fetch a particular CSS file using MXHR and then load it normally on the next page, it will not be in the cache. This is because the rolled-up resources are transmitted as a long string and then split up by the JavaScript code. Since there is no way to programmatically inject a file into the browser's cache, none of the resources fetched in this way will make it there.

    使用此技术有一些缺点,其中最大的缺点是以此方法获得的资源不能被浏览器缓存。如果你使用MXHR获取一个特定的CSS文件然后在下一个页面中正常加载它,它不在缓存中。因为整批资源是作为一个长字符串传输的,然后由JavaScript代码分割。由于没有办法用程序将文件放入浏览器缓存中,所以用这种方法获取的资源也无法存放在那里。

 

    Another downside is that older versions of Internet Explorer don't support readyState 3 or data: URLs. Internet Explorer 8 does support both of them, but workarounds must still be used for Internet Explorer 6 and 7.

   另一个缺点是:老版本的Internet Explorer不支持readyState 3或data: URL。Internet Explorer 8两个都支持,但在Internet Explorer 6和7中必须设法变通。

 

    Despite these downsides, there are still situations in which MXHR significantly improves overall page performance:

    尽管有这些缺点,但某些情况下MXHR仍然显著提高了整体页面的性能:

 

• Pages that contain a lot of resources that aren't used elsewhere on the site (and thus don't need to be cached), especially images

  网页包含许多其他地方不会用到的资源(所以不需要缓存),尤其是图片

 

• Sites that already use a unique rolled-up JavaScript or CSS file on each page to reduce HTTP requests; because it is unique to each page, it's never read from cache unless that particular page is reloaded

  网站为每个页面使用了独一无二的打包的JavaScript或CSS文件以减少HTTP请求,因为它们对每个页面来说是独一的,所以不需要从缓存中读取,除非重新载入特定页面

 

    Because HTTP requests are one of the most extreme bottlenecks in Ajax, reducing the number needed has a large effect on overall page performance. This is especially true when you are able to convert 100 image requests into a single multipart XHR request. Ad hoc testing with large numbers of images across modern browsers has shown this technique to be 4 to 10 times faster than making individual requests. Run these tests for yourself at http://techfoolery.com/mxhr/.

    由于HTTP请求是Ajax中最极端的瓶颈之一,减少其需求数量对整个页面性能有很大影响。尤其是当你将100个图片请求转化为一个MXHR请求时。Ad hoc在现代浏览器上测试了大量图片,其结果显示出此技术比逐个请求快了4到10倍。你可以自己运行这个测试:http://techfoolery.com/mxhr/

 

Sending Data  发送数据

 

    There are times when you don't care about retrieving data, and instead only want to send it to the server. You could be sending off nonpersonal information about a user to be analyzed later, or you could capture all script errors that occur and send the details about them to the server for logging and alerting. When data only needs to be sent to the server, there are two techniques that are widely used: XHR and beacons.

    有时你不关心接收数据,而只要将数据发送给服务器。你可以发送用户的非私有信息以备日后分析,或者捕获所有脚本错误然后将有关细节发送给服务器进行记录和提示。当数据只需发送给服务器时,有两种广泛应用的技术:XHR和灯标。

 

XMLHttpRequest

 

    Though primarily used for requesting data from the server, XHR can also be used to send data back. Data can be sent back as GET or POST, as well as in any number of HTTP headers. This gives you an enormous amount of flexibility. XHR is especially useful when the amount of data you are sending back exceeds the maximum URL length in a browser. In that situation, you can send the data back as a POST:

    虽然XHR主要用于从服务器获取数据,它也可以用来将数据发回。数据可以用GET或POST方式发回,以及任意数量的HTTP信息头。这给你很大灵活性。当你向服务器发回的数据量超过浏览器的最大URL长度时XHR特别有用。这种情况下,你可以用POST方式发回数据:

 

var url = '/data.php';
var params = [
  'id=934875',
  'limit=20'
];
var req = new XMLHttpRequest();
req.onerror = function() {
  // Error.
};
req.onreadystatechange = function() {
  if (req.readyState == 4) {
    // Success.
  }
};
req.open('POST', url, true);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setRequestHeader('Content-Length', params.length);
req.send(params.join('&'));

    As you can see in this example, we do nothing if the post fails. This is usually fine when XHR is used to capture broad user statistics, but if it's crucial that the data makes it to the server, you can add code to retry on failure:

    正如你在这个例子中看到的,如果失败了我们什么也不做。当我们用XHR捕获登陆用户统计信息时这么做通常没什么问题,但是,如果发送到服务器的是至关重要的数据,你可以添加代码在失败时重试:

 

function xhrPost(url, params, callback) {
  var req = new XMLHttpRequest();
  req.onerror = function() {
    setTimeout(function() {
      xhrPost(url, params, callback);
    }, 1000);
  };
  req.onreadystatechange = function() {
    if (req.readyState == 4) {
      if (callback && typeof callback === 'function') {
        callback();
      }
    }
  };
  req.open('POST', url, true);
  req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  req.setRequestHeader('Content-Length', params.length);
  req.send(params.join('&'));
}

    When using XHR to send data back to the server, it is faster to use GET. This is because, for small amounts of data, a GET request is sent to the server in a single packet. A POST, on the other hand, is sent in a minimum of two packets, one for the headers and another for the POST body. A POST is better suited to sending large amounts of data to the server, both because the extra packet won't matter as much and because of Internet Explorer's URL length limit, which makes long GET requests impossible.

    当使用XHR将数据发回服务器时,它比使用GET要快。这是因为对少量数据而言,向服务器发送一个GET请求要占用一个单独的数据包。另一方面,一个POST至少发送两个数据包,一个用于信息头。另一个用于POST体。POST更适合于向服务器发送大量数据,即因为它不关心额外数据包的数量,又因为Internet Explorer的URL长度限制,它不可能使用过长的GET请求。

 

Beacons  灯标

 

    This technique is very similar to dynamic script tag insertion. JavaScript is used to create a new Image object, with the src set to the URL of a script on your server. This URL contains the data we want to send back in the GET format of key-value pairs. Note that no img element has to be created or inserted into the DOM.

    此技术与动态脚本标签插入非常类似。JavaScript用于创建一个新的Image对象,将src设置为服务器上一个脚本文件的URL。此URL包含我们打算通过GET格式传回的键值对数据。注意并没有创建img元素或者将它们插入到DOM中。

 

var url = '/status_tracker.php';
var params = [
  'step=2',
  'time=1248027314'
];
(new Image()).src = url + '?' + params.join('&');

 

    The server takes this data and stores it; it doesn't have to send anything back to the client, since the image isn't actually displayed. This is the most efficient way to send information back to the server. There is very little overhead, and server-side errors don't affect the client side at all.

    服务器取得此数据并保存下来,而不必向客户端返回什么,因此没有实际的图像显示。这是将信息发回服务器的最有效方法。其开销很小,而且任何服务器端错误都不会影响客户端。

 

    The simplicity of image beacons also means that you are restricted in what you can do. You can't send POST data, so you are limited to a fairly small number of characters before you reach the maximum allowed URL length. You can receive data back, but in very limited ways. It's possible to listen for the Image object's load event, which will tell you if the server successfully received the data. You can also check the width and height of the image that the server returned (if an image was returned) and use those numbers to inform you about the server's state. For instance, a width of 1 could be "success" and 2 could be "try again."

    简单的图像灯标意味着你所能做的受到限制。你不能发送POST数据,所以你被URL长度限制在一个相当小的字符数量上。你可以用非常有限的方法接收返回数据。可以监听Image对象的load事件,它可以告诉你服务器端是否成功接收了数据。你还可以检查服务器返回图片的宽度和高度(如果返回了一张图片)并用这些数字通知你服务器的状态。例如,宽度为1表示“成功”,2表示“重试”。

 

    If you don't need to return data in your response, you should send a response code of 204 No Content and no message body. This will prevent the client from waiting for a message body that will never come:

    如果你不需要为此响应返回数据,那么你应当发送一个204 No Content响应代码,无消息正文。它将阻止客户端继续等待永远不会到来的消息体:

 

var url = '/status_tracker.php';
var params = [
  'step=2',
  'time=1248027314'
];
var beacon = new Image();
beacon.src = url + '?' + params.join('&');
beacon.onload = function() {
  if (this.width == 1) {
    // Success.
  }
  else if (this.width == 2) {
    // Failure; create another beacon and try again.
  }
};
beacon.onerror = function() {
  // Error; wait a bit, then create another beacon and try again.
};

    Beacons are the fastest and most efficient way to send data back to the server. The server doesn't have to send back any response body at all, so you don't have to worry about downloading data to the client. The only downside is that it you are limited in the type of responses you can receive. If you need to pass large amounts of data back to the client, use XHR. If you only care about sending data to the server (with possibly a very simple response), use image beacons.

    灯标是向服务器回送数据最快和最有效的方法。服务器根本不需要发回任何响应正文,所以你不必担心客户端下载数据。唯一的缺点是接收到的响应类型是受限的。如果你需要向客户端返回大量数据,那么使用XHR。如果你只关心将数据发送到服务器端(可能需要极少的回复),那么使用图像灯标。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值