第18章:01-使用XMLHttpRequest

1. 脚本话 HTTP

超文本传输协议(HTTP)规定了 Web 浏览器如何从 Web 服务器获取文档和向 Web 服务器提交表单内容,以及 Web 服务器如何响应这些请求和提交。通常,HTTP 并不在脚本的控制下,只是当用户单击链接、提交表单和输入 URL 时才会发生。

但是,用 JavaScript 脚本操纵 HTTP 是可行的。比如,用脚本设置 window 对象的 location 属性或调用表单对象的 submit() 方法时,都会初始化 HTTP 请求。但在这两种情况下,浏览器会重新加载页面,那么如何解决这个问题呢?

要解决这个问题需要使用到 Ajax 技术,Ajax 是一种使用脚本操作 HTTP 的 Web 应用框架。Ajax 应用的主要特点是使用脚本操纵 HTTP 和 Web 服务器进行数据交换,不会导致页面重载。

实现 Ajax 的方式有很多种,而这些底层的实现有时称为传输协议(transport)。例如,<img> 元素的 src 属性。当脚本设置这个属性为 URL时,浏览器会发起 HTTP GET 请求会从这个 URL 下载图片。

除此之外,还有 <script> 元素的 src 属性,并且可以跨域通信而不受限于同源策略。通常,使用基于 <script> 的Ajax 传输协议时,服务器的响应采用 JSON 编码的数据格式,因此这种 Ajax 传输协议也叫做 “JSONP”。

虽然在 <img><script> 传输协议之上能实现 Ajax 技术,但通常还有更简单的方式。所有浏览器都支持 XMLHttpRequest 对象,它定义了用脚本操纵 HTTP 的 API。


2. 使用 XMLHttpRequest

浏览器在 XMLHttpRequest 类上定义了它们的 HTTP API。这个类的每个实例都表示一个独立的请求/响应,并且这个对象的属性和方法允许指定请求细节和提取响应数据。


2.1 请求步骤

一个 HTTP 请求由4部分组成

  • HTTP 请求方法
  • 请求的 URL
  • 可选的请求头集合
  • 可选的请求主体(GET 请求是没有请求主体的)

第一步:实例化 XMLHttpRequest 对象

var request = new XMLHttpRequest();

第二步:调用 request 对象的 open() 方法,需指定请求的两个必需部分:方法和URL。

request.open("GET", "URL")   // URL:请求的 URL

open() 的第一个参数指定 HTTP 方法。这个字符串不区分大小写,但通常用大写字母来匹配 HTTP 协议。

第二个参数指定请求的 URL。


第三步:设置请求头(可选)

如果有请求头的话,需要设置它。列如,POST 请求需要通过 “Content-Type” 指定请求主题的 MIME 类型:

request.setRequestHeader("Content-Type", "text/plain");  // 设置请求内容为纯文本格式

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

function postMethod(url, body) {
  var request = new XMLHttpRequest();
  request.open("POST", url);
  request.setRequestHeader("Content-Type","text/plain");
  request.setRequestHeader("Content-Type", "text/html");
  request.setRequestHeader("Content-Type", "application/json");
  request.send(body);
}

postMethod("http://127.0.0.1:8888/msg", {name: 'cez'})

关于 MIME 知识点:xxxxxx(待定)


第四步:指定请求主体并向服务器发送它(可选)

// GET:没有请求主体
request.send(null); 
或
request.send();

// POST:通常有请求主体
request.send(msg);

GET 请求绝对没有主体,所以应该传递 null 或省略这个参数。


实例:

用 GET 方法请求一张图片

function getMethod(url) {
    var request = new XMLHttpRequest();
	request.open("GET", url);
	request.send(null);
}

getMethod("https://img-blog.csdnimg.cn/20201103231832286.png");


用 POST 方法发送纯文本给服务器,并忽略服务器返回的任何响应

function postMethod(url, msg) {
	var request = new XMLHttpRequest();
    request.open("POST", url);  // 用 POST 方法向服务器端发送脚本
    request.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");  // 请求内容将是纯文本,别和响应内容搞混了
    request.send(msg);  // 把 msg 作为请求主体发送
}

postMethod("http:127.0.0.1:8888/cez", "我是 ELZ...");

2.2 取得响应

服务器返回的 HTTP 响应包含3部分

  • 数字和文字组成的状态码,用来显示请求的成功或失败
  • 响应头集合
  • 响应主体

一个完整的 HTTP 响应由状态码、响应头集合和响应主体组成。这些都可以通过 XMLHttpRequest 对象的属性和方法使用:

  • statusstatusText 属性以数字和文本的形式返回 HTTP 状态码。
  • 使用 getResponseHeader()getAllResponseHeaders() 方法能查询响应头内容。为了安全起见,XMLHttpRequest 会自动处理 cookie:它会从 getAllResponseHeaders() 方法返回集合中过滤掉 cookie 值,而如果给 getResponseHeader() 传递 “Set-Cookie” 和 “Set-Cookie2” 则返回 null
  • responseText 属性以文本的形式返回响应主体。

前面列出的响应属性和方法需要在响应返回后才有效。为了在响应准备就绪时得到通知,必须监听 XMLHttpRequest 对象上的 readystatechange 事件。但为了理解这个事件类型,需要理解 readyState 属性。


readyState

readyState 是一个整数,它指定了 HTTP 请求的状态,它可能值如下:

常量含义
UNSENT0open() 尚未调用
OPENED1open() 已调用
HEADSERS_RECEIVED2接收到响应头信息
LOADING3接收到响应主体
DONE4响应完成

理论上,每次 readyState 属性改变都会触发 readystatechange 事件。实际中,当 readyState 改变为 0 或 1 时可能没有触发这个事件。


实例:演示如何通过监听 readystatechange 事件来获取响应的内容。

function getMethod(url) {
  var request = new XMLHttpRequest();
  request.open("GET", url);
  request.onreadystatechange = function () {
    console.log(`---readyState: ${request.readyState}---`);
    console.log(`status: ${request.status}`);
    console.log(`statusText: ${request.statusText}`);
    console.log(`responseHeader: ${request.getResponseHeader("Content-Type")}`);
    console.log(`AllResponseHeaders: ${request.getAllResponseHeaders()}`);
    console.log(`responseText: ${request.responseText}`);
  };
  request.send(null);
}

getMethod("http://127.0.0.1:8888");  // 通过 node.js 搭的本地服务器,这里就不展开了

从结果来看,只有当 readyState 改变为 2 、 3或4 时才会触发 readystatechange 这个事件。


2.2.1 同步响应

由于其本身的性质,异步处理 HTTP 响应是最好的方式。然而,XMLHttpRequest 也支持同步响应。如果把 false 作为第三个参数传递给 open(),那么 send() 方法将阻塞直到请求完成。在这种情况下,不需要使用事件处理程序:一旦 send() 返回,仅需要检查 XMLHttpRequest 对象的 statusresponseText 属性。

// 发起同步的HTTP GET请求以获取指定URL的内容
function getMethod(url) {
    var request = new XMLHttpRequest();
    request.open("GET", url, false);   // 传递 false 实现同步
    request.send(null);   // 发送请求
    
    if (request.status !== 200) throw new Error(request.statusText);
    return request.responseText;
}

getMethod("http://127.0.0.1:8888")   // Hello World

注意:客户端 JavaScript 是单线程的,当 send() 方法阻塞时,它通常会导致整个浏览器 UI 冻结。如果连接的服务器响应速度慢,那么会很影响用户体验。


2.2.2 响应解码

服务器响应的内容类型有多种。比如“text/plain”、“text/xml”、“application/json” 等。不同类型通过不同属性来获取。

function getMethod(url, callback) {
  var request = new XMLHttpRequest();
  request.open("GET", url);
  
  request.onreadystatechange = function () {
    if (request.readyState === 4 && request.status === 200) {
      // 获得响应的类型
      var type = request.getResponseHeader("Content-Type");

      if (type.indexOf("xml") !== -1 && request.responseXML) {
        // XML 类型
        callback(request.responseXML);
      } else if (type.indexOf("application/json") !== -1 && request.responseText) {
        // json 类型
        callback(JSON.parse(request.responseText));
      } else {
        // 其它类型,如纯文本格式内容
        callback(request.responseText);
      }
    }
  };
  
  request.send(null);
}

如果服务器发送 XML 或 XHTML 文档作为其响应内容,可以通过 responseXML 属性获得一个解析形式的 XML 文档,这个属性值是一个 Document 对象。

getMethod("http://127.0.0.1:8888", function (result) {
  console.log(result);
});

// 设置服务器响应内容的类型为 "text/xml"
app.use((req, res, next) => {
  res.header("Content-Type", "text/xml")
  next();
  });
// 服务器响应内容
app.get('/', function(req, res) {
  res.send("<note><to>George</to><from>John</from><body>Don't forget the meeting!</body></note>");
})


如果服务器想发送诸如对象或数值这样的结构化数据作为其响应内容,它应该传输 JSON 编码的字符串数据。当接收它时,可以把 responseText 属性传递给 JSON.parse()

getMethod("http://127.0.0.1:8888", function (result) {
  console.log(result);
});

// 服务器响应内容
app.get('/', function(req, res) {
  res.json({"name": "cez"})
})



如果服务器发送字符串作为其响应内容,可以通过 responseText 属性来获取。

getMethod("http://127.0.0.1:8888", function (result) {
  console.log(result);
});

// 设置服务器响应内容的类型为 "text/plain"
app.use((req, res, next) => {
  res.header("Content-Type", "text/plain")
  next();
  });
// 服务器响应内容
app.get('/', function(req, res) {
  res.send("Hello World!!!");
})



假设服务器发送 XML 文档作为其响应内容,而我们计划把它当做纯文本对待,该如何实现呢?

function getMethod(url, callback) {
    // some code...
    request.overrideMimeType("text/plain;charset=utf-8");
    request.send(null);
}

// 服务器响应内容
app.get('/', function(req, res) {
  res.send("<note><to>George</to><from>John</from><body>Don't forget the meeting!</body></note>");
})

上面代码中,在 send() 方法之前通过使用 overrideMimeType() 方法,这将使 XMLHttpRequest 忽略 “Content-Type” 头而使用我们指定的类型。从而不把响应内容作为 XML 文档处理,而是作为纯文本格式处理。


注意

我们可能希望特殊编码的另一个响应类型是 “application/javascript” 或 “text/javascript”。这样就能使用 XMLHttpRequest 请求 JavaScript 脚本,然后使用全局 eval() 执行这个脚本。但是,在这种情况下根本不需要使用 XMLHttpRequest 对象,因为 <script> 元素本身就能操纵 HTTP 脚本的能力完全可以实现加载并执行脚本。并且 <script> 元素能发起跨域请求,而 XMLHttpRequest API 则禁止。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值