《AJAX的原理》

253 阅读7分钟

一. 什么是AJAX

简单来说,AJAX就是用JS发请求,收响应

AJAX是浏览器上的一个功能。原本浏览器可以发请求,收响应,现在想把这个功能暴露出来给JS开发者。怎么做呢?浏览器在window上挂了一个XMLHttpRequest函数。window.XMLHttpRequest是一个构造函数,它可以构造出一个HttpRequest对象,JS通过这个对象来实现发请求,收响应。

二. 准备一个服务器

既然要发请求和收响应,就需要一个服务器接收响应。用server.js模拟。为了修改代码不需要重启,我们使用 node-dev 代替 node。运行:

node-dev server.js 8888

就会监听8888端口,一旦浏览器向8888端口发送请求,就会运行server.js文件一次。

//server.js
console.log("有个傻子发请求过来啦!路径(带查询参数)为:" + pathWithQuery);
 
if (path === "/index.html") {
    response.statusCode = 200;
    response.setHeader("Content-Type", "text/html;charset=utf-8");
    //把文件读成字符串的形式
    response.write(fs.readFileSync("public/index.html"));
    response.end();
 } else if (path === "/main.js") {
    response.statusCode = 200;
    response.setHeader("Content-Type", "text/javascript;charset=utf-8");
    response.write(fs.readFileSync("public/main.js"));
    response.end();
 } else if (path === "/style.css") {
    response.statusCode = 200;
    response.setHeader("Content-Type", "text/css;charset=utf-8");
    response.write(fs.readFileSync("public/style.css"));
    response.end();
 } else if (path === "/2.js") {
    response.statusCode = 200;
    response.setHeader("Content-Type", "text/javascript;charset=utf-8");
    response.write(fs.readFileSync("public/2.js"));
    response.end();
 } else if (path === "/3.html") {
    response.statusCode = 200;
    response.setHeader("Content-Type", "text/html;charset=utf-8");
    response.write(fs.readFileSync("public/3.html"));
    response.end();
 } else {
    response.statusCode = 404;
    response.setHeader("Content-Type", "text/html;charset=utf-8");
    response.write(`你访问的页面不存在`);
    response.end();
 }

用if else,根据请求的不同路径名,有不同的响应内容。每个if 可看成是一个路由。

我们先请求index.html路径,根据第一个if语句,服务器的响应内容是index.html文件。index.html里引用了main.js文件。

原来我们请求css和js文件的做法:在index.html里用link标签和script标签直接引入css和js。

现在我们使用AJAX请求。在main.js文件里请求。

三. 加载CSS

以前用<link rel="stylesheet" href="/style.css" />,现在用AJAX加载CSS。

四个步骤:

  1. 创建HttpRequest对象
  2. 调用对象的open方法(初始化对象)
  3. 监听对象的onload,onerror事件(加载成功怎么样,加载失败怎么样)
  4. 调用对象的send方法(发送请求)

代码实现:

//main.js
getCSS.onclick = () => {   //为了看清楚变化,点击按钮才会加载
    const request = new XMLHttpRequest();
    request.open("GET", "/style.css");
    request.onload = () => {
        console.log("request.response");
        console.log(request.response);
        //创建一个style标签
        const style = document.createElement("style");
        //填写style内容
        style.innerHTML = request.response;
        //插到head里
        document.head.appendChild(style);
  };
  request.onerror = () => {
        console.log("失败了");
  };
  request.send();
};

如果加载成功了,就会调用onload事件,查看开发者工具-Network,可以看到接收到的响应。打印出request.response,它就是响应的内容,字符串。

如果onload里什么都不写,CSS样式是没有效果的。所以需要三步:创建一个style标签,填写style内容,插到head里。

四. 加载JS

以前用 <script src="/2.js"></script> ,现在用AJAX加载JS。

还是和加载CSS同样的方法,四个步骤。

//main.js
getJS.onclick = () => {
    const request = new XMLHttpRequest();
    request.open("GET", "/2.js");
    request.onload = () => {
        //创建script标签
        const script = document.createElement("script");
        //填写script内容
        script.innerHTML = request.response;
        //插到body里
        document.body.appendChild(script);
    };
    request.onerror = () => {};
    request.send();
};
//2.js
console.log("我是2.js");

五. 加载html

以前我们不用AJAX,可以通过link和script引用css和js文件。但是以前我们不会加载3.html。

//3.html
<div style="border:1px solid red;width:300px;height:300px;">
  动态内容
</div>

加载成功,会显示一个div。

//main.js
getHTML.onclick = () => {
    const request = new XMLHttpRequest();
    request.open("GET", "/3.html");
    request.onload = () => {
        //创建一个div
        const div = document.createElement("div");
        //填写div的内容 request.response就是一个字符串,请求的那个内容
        div.innerHTML = request.response;
        //插到body里
        document.body.appendChild(div);
    };
    request.onerror = () => {};
    request.send();
};

//index.html
<!DOCTYPE html>
<head>
  <title>ajax</title>
  <!--<link rel="stylesheet" href="/style.css" />-->
</head>
<body>
  <h1>AJAX demo</h1>
  <p>
    <button id="getCSS">请求CSS</button>
    <button id="getJS">请求JS</button>
    <button id="getHTML">请求HTML</button>
  </p>
  <script src="/main.js"></script>
  <!--<script src="/2.js"></script>-->
</body>

六. onreadystatechange 事件

只要 readyState 属性发生变化,就会调用相应的处理函数。

一个请求对象的readyState属性代表了一个请求的一生:有五种返回值:

  • 0:一个请求被创建了 const request=new XMLHttpRequest()
  • 1:调用了open方法
  • 2:调用了send方法
  • 3:正在下载响应体
  • 4:全部下载完成

专业前端会将onload,onerror事件改为 onreadystatechange 事件。因为onerror事件不够用,如果我故意写错请求的路径,会发现浏览器会加载服务器中判断是未知路径的响应内容。而不会调用onerror事件,也就是说,onerror没用。

getCSS.onclick = () => {
  const request = new XMLHttpRequest();
  request.open("GET", "/style.css");
  request.onreadystatechange = () => {
    console.log(request.readyState);
    if (request.readyState === 4) {
      console.log("下载完成");
      if (request.status >= 200 && request.status < 300) {
        //创建一个style标签
        const style = document.createElement("style");
        //填写style内容
        style.innerHTML = request.response;
        //插到head里
        document.head.appendChild(style);
      } else {
        alert("请求失败");
      }
    }
  };
  request.send();
};

我们只关心响应体下载完成后,也就是request.readyState===4。但是下载完成了,可能是请求成功,把正确的响应体下载完成。也有可能是请求失败,把404页面下载完成。也就是说,如果故意写错请求路径,会把服务器里的最后一个else语句里的响应体响应回来,还是会打印出下载完成。那怎么区分成功和失败呢?所以在下载完成的情况下,要判断request的状态码status,如果是200-300,就说明请求成功。否则,就说明请求失败,用alert弹出提示。

这里,request.status是指请求得到的响应的状态码。

七. 加载XML

AJAX一开始就是为了加载XML的,有一个request.responseXML方法,可以直接返回这个DOM对象。

getXML.onclick = () => {
  const request = new XMLHttpRequest();
  request.open("GET", "/4.xml");
  request.onreadystatechange = () => {
    if (request.readyState === 4) {
      if (request.status >= 200 && request.status < 300) {
        console.log(request.responseXML);
        const dom = request.responseXML;
        const text = dom.getElementsByTagName("warning")[0].textContent;
        console.log(text.trim());
      }
    }
  };
  request.send();
};
//4.xml
<?xml version="1.0" encoding="UTF-8"?>
<message>
    <warning>
         Hello World
    </warning>
</message>

八. 加载JSON

1. 什么是JSON?

JavaScript Object Notation ,JS对象标记语言。是一门独立的语言。 JSON只支持六种数据类型:(没有函数,变量等)

  1. string,只支持双引号
  2. number
  3. bool:true,false
  4. null
  5. object
  6. array

可查看json.org官网

2. window.JSON

是一个全局对象

  • JSON.parse:

    将符合JSON语法规范的字符串转换成JS对应类型的数据(可 以是对象, 数字,布尔值等)。

    如果不符合JSON语法,会抛出一个Error对象。一般用try catch捕获错误。

    把要写的代码放进try,如果没有错误,给obj赋值;如果捕获到错误了,就打印出error,给obj一个保底值。因为我们并不能保证parse里的一定符合字符串规范,所以一般都要用try catch。

  • JSON.stringify:

    把JS数据转换成符合JSON语法规范的字符串

3. 加载JSON

用JSON.parse获取到响应的内容,然后对它进行操作。所有request.response都是所获取到的文件里的内容,类型是字符串。

getJSON.onclick = () => {
  const request = new XMLHttpRequest();
  request.open("GET", "/5.json");
  request.onreadystatechange = () => {
    if (request.readyState === 4) {
      if (request.status >= 200 && request.status < 300) {
        console.log(request.response);
        const obj = JSON.parse(request.response); //把符合JSON语法的字符串转换成对象或其他东西
        myName.innerText = obj.name;
      }
    }
  };
  request.send();
};
//index.html
<body>
  <h1>AJAX hello <span id="myName"></span></h1>
  <p>
    <button id="getJSON">请求JSON</button>
  </p>
  <script src="/main.js"></script>
</body>
//5.json
{
  "name": "anqi",
  "age": 18
}

点击按钮,就会在页面的hello 后边显示json文件定义的对象的name属性值。

九. 模拟分页

新建一个db目录,有三个文件,page1.json,2,3。分别是有十个对象数据的数组。想要实现:在浏览器页面里展示page1.json的内容,然后点击页面里的按钮,可以继续加载page2.json和page3.json的内容。

  1. 先是加载page1.json,不用ajax。
//server.js
 if (path === "/index.html") {
    response.statusCode = 200;
    response.setHeader("Content-Type", "text/html;charset=utf-8");
    //把文件读成字符串的形式
    let string = fs.readFileSync("public/index.html").toString();
    //在html里设一个占位符,然后从数据库获取这个数据,替代占位符
    const page1 = fs.readFileSync("db/page1.json");
    const array = JSON.parse(page1);
    const result = array.map(item => `<li>${item.id}</li>`).join("");
    string = string.replace("{{page1}}", `<ul id='ul'>${result}</ul>`);
    response.write(string);
    response.end();
  } 
//index.html
<div>
    {{page1}}
</div>

做法:在index.html里写一个div标签,放一个占位符。在服务器代码里,如果请求了index.html,先以字符串形式读出page1.json的内容,然后用string.replace方法,把页面里的占位符替换成page1。这样,页面就会显示page1里定义的数组。

  1. AJAX加载其余分页:
//main.js
let n = 2;
getPAGE.onclick = () => {
  const request = new XMLHttpRequest();
  request.open("GET", `/page${n}.json`);
  request.onreadystatechange = () => {
    if (request.readyState === 4) {
      if (request.status >= 200 && request.status < 300) {
        const arr = JSON.parse(request.response);
        arr.forEach(x => {
          const li = document.createElement("li");
          li.innerText = x.id;
          ul.appendChild(li);
        });
        n += 1;
      }
    }
  };
  request.send();
};

同上,点击按钮,触发AJAX的事件,如果请求成功,先用JSON.parse获取到响应的内容,这里是一个数组。然后DOM操作,把内容渲染到页面,对数组中的每个元素,创建li元素,插入文本,把li插到ul中,在页面显示。为了再次点击按钮,继续加载下一个分页,用变量n控制加载哪个文件里的数据。每次事件结束,n自动+1。