案例:倒计时

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>倒计时</title>
  6. </head>
  7. <body>
  8. <div id="box"></div>
  9. <script>
  10. //new Date() 获取客户端本地当前时间(不能拿它做重要依据,因为用户可以随意修改)
  11. /*
  12. * 倒计时抢购需要从服务器获取当前时间 AJAX
  13. * 问题:时间差(从服务器把时间给客户端,到客户端获取到这个信息,中间经历的时间就是时间差,而时间差是不可避免的,我们应尽可能减少这个误差)
  14. * - 从响应头获取时间(AJAX异步)
  15. * - 基于 HEAD 请求(只获取响应头信息)
  16. */
  17. let target = new Date('2019/09/14 13:27:00'),
  18. now = null,
  19. timer = null;
  20. // 从服务器获取时间:获取到时间后再做其他的事情
  21. function func(callback) {
  22. let xhr = new XMLHttpRequest;
  23. xhr.open('HEAD', 'json/data.json', true);
  24. xhr.onreadystatechange = function () {
  25. if (!/^(2|3)\d{2}$/.test(xhr.status)) return;
  26. if (xhr.readyState === 2) {
  27. now = new Date(xhr.getResponseHeader('Date'));
  28. callback && callback();
  29. }
  30. }
  31. xhr.send(null);
  32. }
  33. // 开启倒计时模式
  34. function computed() {
  35. let spanTime = target - now;
  36. if (spanTime <= 0) {
  37. // 到抢购时间:结束定时器
  38. clearInterval(timer);
  39. timer = null;
  40. box.innerHTML = "开抢~~";
  41. return;
  42. }
  43. let hours = Math.floor(spanTime / (60 * 60 * 1000));
  44. spanTime -= hours * 60 * 60 * 1000;
  45. let minutes = Math.floor(spanTime / (60 * 1000));
  46. spanTime -= minutes * 60 * 1000;
  47. let seconds = Math.floor(spanTime / 1000);
  48. box.innerHTML =
  49. `距离抢购还剩 ${hours<10?'0'+hours:hours}:${minutes<10?'0'+minutes:minutes}:${seconds<10?'0'+seconds:seconds}`;
  50. // 每一次计算完,我们需要让NOW在原来的基础上加上一秒(第一次从服务器获取到时间,后期直接基于这个时间自己加即可,不要每隔一秒重新从服务器拿)
  51. now = new Date(now.getTime() + 1000);
  52. }
  53. func(() => {
  54. // 已经从服务器获取时间了
  55. computed();
  56. timer = setInterval(computed, 1000);
  57. });
  58. </script>
  59. </body>
  60. </html>

封装AJAX库

AJAX库的封装.png

  1. /*
  2. * 支持的参数配置项
  3. * url
  4. * method:'GET'
  5. * data:null
  6. * dataType:'json'
  7. * async:true
  8. * cache:true
  9. * success:null
  10. * error:null
  11. * headers:null
  12. * timeout:null
  13. */
  14. ~ function () {
  15. function ajax(options) {
  16. return new init(options);
  17. }
  18. /* == AJAX处理的核心 == */
  19. let regGET = /^(GET|DELETE|HEAD|OPTIONS)$/i;
  20. let defaults = {
  21. url: '', // 请求的API接口地址
  22. method: 'GET', // 请求方式 GET / POST / DELETE / PUT / HEAD / OPTIONS
  23. data: null, // 传递给服务器的信息:支持格式 STRING 和 OBJECT,如果是 OBJECT,我们需要把其处理为 x-www-form-urlencoded 格式;GET 请求是把信息作为问号参数传递给服务器,POST 请求是放到请求主体中传递给服务器;
  24. dataType: 'JSON', // 把服务器返回结果处理成为对应的格式 JSON / TEXT / XML
  25. async: true, // 是否异步请求
  26. cache: true, // 只对 GET 请求有作用:设置为 FALSE,在 URL 的末尾加随机数来清除缓存
  27. timeout: null, // 超时时间
  28. headers: null, // 设置请求头信息(请求头信息不能是中文,所以我们需要为其编码)
  29. success: null, // 从服务器获取成功后执行 把获取的结果、状态信息、XHR 传递给它
  30. error: null // 获取失败后执行 把错误信息传递给它
  31. };
  32. function init(options = {}) {
  33. // 参数初始化:把传递的配置项替换默认的配置项
  34. this.options = Object.assign(defaults, options);
  35. this.xhr = null;
  36. this.send();
  37. }
  38. ajax.prototype = {
  39. constructor: ajax,
  40. version: 1.0,
  41. // 发送AJAX请求
  42. send() {
  43. let xhr = null,
  44. {url, method, async, data, cache, timeout, dataType, headers, success, error} = this.options;
  45. this.xhr = xhr = new XMLHttpRequest;
  46. // 处理DATA:如果是GET请求把处理后的DATA放在URL末尾传递给服务器
  47. data = this.handleData();
  48. if (data !== null && regGET.test(method)) {
  49. url += `${this.checkASK(url)}${data}`;
  50. data = null;
  51. }
  52. // 处理CACHE:如果是GET并且CACHE是FALSE需要清除缓存
  53. if (cache === false && regGET.test(method)) {
  54. url += `${this.checkASK(url)}_=${Math.random()}`;
  55. }
  56. xhr.open(method, url, async);
  57. // 超时处理
  58. timeout !== null ? xhr.timeout = timeout : null;
  59. // 设置请求头信息
  60. if (Object.prototype.toString.call(headers) === "[object Object]") {
  61. for (let key in headers) {
  62. if (!headers.hasOwnProperty(key)) break;
  63. xhr.setRequestHeader(key, encodeURIComponent(headers[key]));
  64. }
  65. }
  66. xhr.onreadystatechange = () => {
  67. let {
  68. status,
  69. statusText,
  70. readyState: state,
  71. responseText,
  72. responseXML
  73. } = xhr;
  74. if (/^(2|3)\d{2}$/.test(status)) {
  75. // 成功
  76. if (state === 4) {
  77. switch (dataType.toUpperCase()) {
  78. case 'JSON':
  79. responseText = JSON.parse(responseText);
  80. break;
  81. case 'XML':
  82. responseText = responseXML;
  83. break;
  84. }
  85. success && success(responseText, statusText, xhr);
  86. }
  87. return;
  88. }
  89. // 失败的
  90. typeof error === "function" ? error(statusText, xhr) : null;
  91. }
  92. xhr.send(data);
  93. },
  94. // 关于DATA参数的处理
  95. handleData() {
  96. let {
  97. data
  98. } = this.options;
  99. if (data === null || typeof data === "string") return data;
  100. // 只有DATA是一个对象,我们需要把它变为xxx=xxx&xxx=xxx这种格式字符串
  101. let str = ``;
  102. for (let key in data) {
  103. if (!data.hasOwnProperty(key)) break;
  104. str += `${key}=${data[key]}&`;
  105. }
  106. str = str.substring(0, str.length - 1);
  107. return str;
  108. },
  109. // 检测URL中是否存在问号
  110. checkASK(url) {
  111. return url.indexOf('?') === -1 ? '?' : '&';
  112. }
  113. };
  114. init.prototype = ajax.prototype;
  115. window._ajax = ajax;
  116. }();