toFrankie / blog

种一棵树,最好的时间是十年前。其次,是现在。
20 stars 1 forks source link

四、Ajax 之 GET、POST 请求方式 #208

Open toFrankie opened 1 year ago

toFrankie commented 1 year ago

上一篇中,概要地介绍了 XHR 对象的使用,本文将详细地介绍使用 XHR 对象发送请求的两种方式 GETPOST

一、GET 请求

GET 请求是最常见的请求类型,最常用于向服务器查询某些信息,它适用于当 URL 完全指定请求资源,当请求对服务器没有任何副作用以及当服务器的响应是可缓存的的情况下。

  1. 数据发送

使用 GET 方式发送请求时,数据被追加到 open() 方法中的 URL 的末尾。 数据以 ? 问号开始,属性名和属性值之间用 = 等号连接,键值对之间使用 & 分隔。使用 GET 方式发送的数据常常被称为查询字符串。

xhr.open('GET', 'example.php?name1=value1&name2=value2', true)
  1. 编码

由于 URL 无法识别特殊字符,所以如果数据中包含特殊字符(如中文),则需要使用 encodeURIComponent() 进行编码。

const url = 'example.php?name=' + encodeURIComponent('越前君')
// url 被编码成:example.php?name=%E8%B6%8A%E5%89%8D%E5%90%9B
xhr.open('GET', url, true)

注意:encodeURIComponent() 只是六种编解码方法的一种,关于它们的详细信息,请点击这里

  1. 写个函数来编码
function addURLParam(url, name, value) {
  url += url.indexOf('?') > -1 ? '&' : '?'
  url += encodeURIComponent(name) + '=' + encodeURIComponent(value)
  return url
}

let url = 'example.php'
url = addURLParam(url, 'name1', 'Frankie')
url = addURLParam(url, 'name2', 'Mandy')

xhr.open('GET', url, true)
  1. 缓存

在 GET 请求中,为了避免缓存的影响,可以向 URL 添加一个随机数或者时间戳。

xhr.open('GET', 'example.php?' + Number(new Date()), true)
// 或者
xhr.open('GET', 'example.php?' + Math.random(), true)
  1. 封装 GET 方法
function get(url, data, callback) {
  let xhr

  // 创建 xhr 对象
  if (window.XMLHttpRequest) {
    xhr = new window.XMLHttpRequest()
  } else {
    xhr = new ActiveXObject('Microsoft.XMLHTTP')
  }

  // 监听请求完成
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
      typeof callback === 'function' && callback(xhr.responseText)
    }
  }

  // 设置超时
  xhr.ontimeout = function () {
    typeof callback === 'function' && callback('The request was timed out!')
  }
  xhr.timeout = 30000

  // 编码特殊字符
  for (const key in data) {
    url += url.indexOf('?') > -1 ? '&' : '?'
    url += encodeURIComponent(key) + '=' + encodeURIComponent(data[key])
  }

  // 添加时间戳,防止缓存
  url += (url.indexOf('?') > -1 ? '&' : '?') + Number(new Date())

  // 建立、发送请求
  xhr.open('GET', url, true)
  xhr.send(null)
}

// 发起 GET 请求
get('example.php', { name: 'Frankie', age: '20', sex: '男' }, function (res) {
  // callback statements...
})

二、POST 请求

使用频率仅次于 GET 请求的是 POST 请求通常用于向服务器发送应该被保存的数据。POST 方法常用于 HTML 表单。它在请求主体中包含额外数据且这些数据常存储到服务器上的数据库中。相同 URL 的重复 POST 请求从服务器得到的响应可能不同,同时不应该缓存使用这个方法的请求。

POST 请求应该把数据作为请求的主体提交,而 GET 请求传统上不是这样。POST 请求的主体可以包含非常多的数据,而且格式不限。在 open() 方法第一个参数位置传入 'POST' (大小写不限制,通常习惯大写),就可以初始化一个 POST 请求了。

xhr.open('POST', 'example.php', true)
  1. 设置请求头

发送 POST 请求的第二步就是向 send() 方法传入某些数据。由于 XHR 最初的设计是为了处理 XML,因此可以在此传入 XML DOM 文档,传入的文档经序列化之后将作为请求主体被提交到服务器。亦可以在此传入任何想发送到服务器的字符串

默认情况下,服务器对 POST 请求和提交 Web 表单的请求并不会一视同仁。因此,服务器端必须有程序来读取发送过来的原始数据,并从中解析出有用的部分。不过,可以使用 XHR 来模仿表单提交:首先将 Content-Type 头部信息设置为 application/x-www-form-urlencoded,也就是表单提交时的内容类型。

xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')

如果不设置 Content-Type,发送给服务器的数据就不会出现在 $POST 超级全局变量中。这时要访问同样的数据,需借助 $HTTP_RAW_POST_DATA

如果对相同的头调用多次 setRequestHeader(),新值不会取代之前指定的值。相反,HTTP 请求将包含这个头的多个副本或者这个头将指定多个值。

  1. 发送主体

接下来要以适当的格式创建一个字符串,并使用 send() 方法发送。 POST 数据的格式与查询字符串格式相同,键和值之间使用 = 等号连接,键值对之间用 & 分隔,如下:

xhr.send('name=Frankie&age=20')
  1. 编码和缓存

由于使用 POST 方式传递数据时,需要设置请求头 content-type,这一步骤已经能勾自动对特殊字符(如中文)进行编码,所以就不需要使用 encodeURIComponent() 方法了。POST 请求主要用于数据提交,相同 URL 的重复 POST 请求从服务器得到的响应可能不同,所以不应该缓存使用 POST 方法的请求。

  1. 性能

GET 对发送信息的数量有限制,一般在 2000 个字符。与 GET 请求相比,POST 请求消耗的资源要更多一些。从性能的角度来看,以发送相同的数据计算,GET 请求的速度最多可 POST 请求的两倍。

  1. 封装 POST 方法
function post(url, data, callback) {
  let xhr

  // 创建 xhr 对象
  if (window.XMLHttpRequest) {
    xhr = new window.XMLHttpRequest()
  } else {
    xhr = new ActiveXObject('Microsoft.XMLHTTP')
  }

  // 监听请求完成
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
      typeof callback === 'function' && callback(xhr.responseText)
    }
  }

  // 设置超时
  xhr.ontimeout = function () {
    typeof callback === 'function' && callback('The request was timed out!')
  }
  xhr.timeout = 30000

  // 序列化 data
  let strData = ''
  for (const key in data) {
    strData += '&' + key + '=' + data[key]
  }
  strData = strData.substr(1)

  // 初始化请求
  xhr.open('POST', url, true)

  // 设置请求头
  xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')

  // 发送请求
  xhr.send(strData)
}

// 发起 POST 请求
post('example.php', { name: 'Frankie', age: '20', sex: '男' }, function (res) {
  // callback statements...
})

三、将 GET 和 POST 封装一下

function ajax(opt) {
  // 请求 url
  let url = opt.url ? opt.url : ''
  // 默认异步
  const isAsync = typeof opt.async !== 'undefined' ? !!opt.async : true
  // 默认 GET 方法
  const method = opt.method && opt.method.toUpperCase() === 'POST' ? 'POST' : 'GET'
  // 成功回调
  const successCallback = typeof opt.success === 'function' ? opt.success : null
  // 失败回调
  const failCallback = typeof opt.fail === 'function' ? opt.fail : null

  let xhr

  // 创建 xhr 对象
  if (window.XMLHttpRequest) {
    xhr = new window.XMLHttpRequest()
  } else {
    xhr = new ActiveXObject('Microsoft.XMLHTTP')
  }

  // 监听请求完成
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        successCallback && successCallback(xhr.responseText)
      } else {
        failCallback && failCallback(xhr.responseText)
      }
    }
  }

  // 设置超时(同步不能设置超时)
  if (isAsync) {
    xhr.ontimeout = function () {
      console.error('The request was timed out!')
    }
    xhr.timeout = opt.timeout ? opt.timeout : 30000
  }

  if (method === 'POST') {
    // POST 处理
    let strData = ''
    for (var key in opt.data) {
      strData += '&' + key + '=' + opt.data[key]
    }
    strData = strData.substr(1)
    xhr.open(method, url, isAsync)
    if (opt.headers) {
      for (const key2 in opt.headers) {
        xhr.setRequestHeader(key2, opt.headers[key2])
      }
    }
    xhr.send(strData)
  } else {
    // GET 处理
    for (var key in opt.data) {
      url += url.indexOf('?') > -1 ? '&' : '?'
      url += encodeURIComponent(key) + '=' + encodeURIComponent(opt.data[key])
    }
    url += (url.indexOf('?') > -1 ? '&' : '?') + Number(new Date())
    xhr.open(method, url, isAsync)
    xhr.send()
  }
}

// 发起请求
ajax({
  url: './example.php',
  method: 'POST',
  data: {
    name: 'Frankie',
    age: 20
  },
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  success: function (res) {
    console.log('request success: ', res)
  },
  fail: function (err) {
    console.log('request fail: ', err)
  }
})

下一篇:Ajax 之响应解码

The end.