Skip to content

自实现之Ajax库 - XMLHttpRequest实现 #1

@littlewin-wang

Description

@littlewin-wang

自入行前端一来各种项目里有很多 Ajax 应用案例,大多数情况下都考虑了用 axios 等库来实现需求,直到打包的时候才 WTF,为什么一个简简单单的 Ajax 需求要耗费我几十 KB 的打包空间?

本文先以兼容性较好的 XMLHttpRequest (Level 2) 标准入手,一步步打造符合自己使用需求的Ajax库。

背景知识

首先,请大家阅读上述两篇文章,掌握XMLHttpRequest的常用属性和方法。
接下来的阅读我将默认您已经会用XMLHttpRequest啦。

兼容性

1749021644-569b9fb8d3b4a_articlex

一些后期需要 polyfill 的功能点

  1. IE8/IE9、Opera Mini 完全不支持xhr对象
  2. IE10/IE11部分支持,不支持 xhr.responseType为json
  3. 部分浏览器不支持设置请求超时,即无法使用xhr.timeout
  4. 部分浏览器不支持xhr.responseType为blob

实现思路

配置参数

首先确定需要参数化的一些配置属性

  • url: 目标地址, 必须
  • headers: 请求头属性对象 - {header_name: header_value, ...}
  • body:
    • string化的参数序列 ('application/x-www-form-urlencoded'较为常用,备选)
    • FormData (未设置 Content-Type 的默认值)
  • method: 'GET', 'POST', etc.
  • cors: If your using cross-origin, you will need this true for IE8-9

回调函数参数

确定回调函数参数

  • statusCode
    • null or 0,考虑异常
  • response
    • 根据 Content-Type 输出对象
    • 或者中断后输出 "Abort"
    • 或者超时后输出 "Timeout"
    • 或者其他错误输出 "Error"

代码

// xhr合法属性
var reqfields = [
  'responseType', 'withCredentials', 'timeout', 'onprogress'
]

const ajax = function (params, callback) {
  var headers = params.headers || {}
    , body = params.body
    , method = params.method || (body ? 'POST' : 'GET')
    , called = false

  // xhr对象generate
  var req = getRequest(params.cors)

  // 回调函数规范输出
  function cb(statusCode, responseText) {
    return function () {
      if (!called) {
        callback(req.status === undefined ? statusCode : req.status,
                 req.status === 0 ? "Error" : (req.response || req.responseText || responseText),
                 req)
        called = true
      }
    }
  }

  req.open(method, params.url, true)

  var success = req.onload = cb(200)
  req.onreadystatechange = function () {
    if (req.readyState === 4) success()
  }
  req.onerror = cb(null, 'Error')
  req.ontimeout = cb(null, 'Timeout')
  req.onabort = cb(null, 'Abort')

  if (body) {
    // 却省设置 FormData => 'application/x-www-form-urlencoded' 
    if (!window.FormData || !(body instanceof window.FormData)) {
      setDefault(headers, 'Content-Type', 'application/x-www-form-urlencoded')
    }
  }

  // xhr对象属性配置
  for (var i = 0, len = reqfields.length, field; i < len; i++) {
    field = reqfields[i]
    if (params[field] !== undefined)
      req[field] = params[field]
  }

  // xhr对象header配置
  for (var field in headers)
    req.setRequestHeader(field, headers[field])

  req.send(body)

  return req
}

function getRequest(cors) {
  // IE 8 and 9 polifill
  if (cors && window.XDomainRequest && !/MSIE 1/.test(navigator.userAgent))
    return new XDomainRequest
  if (window.XMLHttpRequest)
    return new XMLHttpRequest
}

function setDefault(obj, key, value) {
  obj[key] = obj[key] || value
}

Demo

ajax({
  method: 'GET',
  responseType: 'json',
  url: 'https://cnodejs.org/api/v1/topics',
  headers: {
    'Content-Type': 'application/json'
  }
}, function (code, responseText) {
  console.log(code)
  console.log(responseText)
})
{
"success": true,
"data": [ ... ] // 40 Items
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions