学习目标

本期开始我们学习Ajax。

AJAX

1. 概述

Web程序最初的目的就是将信息(数据)放到公共的服务器,让所有网络用户都可以通过浏览器访问。


在此之前,我们可以通过以下几种方式让浏览器发出对服务端的请求,获得服务端的数据:

地址栏输入地址,回车,刷新
特定元素的 href 或 src 属性
表单提交

这些方案都是我们无法通过或者很难通过代码的方式进行编程(对服务端发出请求并且接受服务端返回的响应), 如果我们可以通过 JavaScript 直接发送网络请求,那么 Web 的可能就会更多,随之能够实现的功能也会更多,至 少不再是“单机游戏”。

A JAX(Asynchronous JavaScript and XML),最早出现在 2005 年的 Google Suggest,是在浏览器端进行网络编 程(发送请求、接收响应)的技术方案,它使我们可以通过 JavaScript 直接获取服务端最新的内容而不必重新加载 页面。让 Web 更能接近桌面应用的用户体验。

说白了,A JAX 就是浏览器提供的一套 API(方法),可以通过 JavaScript 调用,从而实现通过代码控制请求与响应。实现 网络编程。

能力不够 API 凑。

2.快速上手

使用 A JAX 的过程可以类比平常我们访问网页过程

// 1. 创建一个 XMLHttpRequest 类型的对象 —— 相当于打开了一个浏览器
2   var xhr = new XMLHttpRequest()
3   // 2. 打开与一个网址之间的连接 —— 相当于在地址栏输入访问地址
4   xhr.open('GET', './time.php')
5   // 3. 通过连接发送一次请求 —— 相当于回车或者点击访问发送请求
6   xhr.send(null)
7   // 4. 指定 xhr 状态变化事件处理函数 —— 相当于处理网页呈现后的操作
8   xhr.onreadystatechange = function () {9   // 通过 xhr 的 readyState 判断此次请求的响应是否接收完成
10  if (this.readyState === 4) {11  // 通过 xhr 的 responseText 获取到响应的响应体
12  console.log(this)
13  }
14  }

练习:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>AJAX 的快速上手(发送请求)</title>
</head>
<body><script>// 涉及到 AJAX 操作的页面“不能”使用文件协议访问(文件的方式访问)// AJAX 是一套 API 核心提供的类型:XMLHttpRequest// 1. 安装浏览器(用户代理)//  xhr 就类似于浏览器的作用(发送请求接收响应)var xhr = new XMLHttpRequest()// 2. 打开浏览器 输入网址xhr.open('GET', 'http://day-11.io/time.php')// 3. 敲回车键 开始请求xhr.send()// 4. 等待响应// 5. 看结果</script>
</body>
</html>

快速上手(接受响应):

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>AJAX 的快速上手(接收响应)</title>
</head>
<body><script>var xhr = new XMLHttpRequest()// 如果需要补货第一个状态的变化,需要注意代码执行顺序的问题(不要出现来不及的情况)// xhr.onreadystatechange = function () {//   // 这个事件并不是只在响应时触发,状态改变就触发//   // console.log(1)//   console.log(this.readyState)// }xhr.open('GET', './time.php')xhr.send()// 因为客户端永远不知道服务端何时才能返回我们需要的响应// 所以 AJAX API 采用事件的机制(通知的感觉)xhr.onreadystatechange = function () {// 这个事件并不是只在响应时触发,XHR 状态改变就触发// console.log(1)if (this.readyState !== 4) return// 获取响应的内容(响应体)console.log(this.responseText)}// 因为响应需要时间,所以无法通过返回值的方式返回响应// var response = xhr.send()// console.log(response)</script>
</body>
</html>

2.1. readyState

由于 readystatechange 事件是在 xhr 对象状态变化时触发(不单是在得到响应时),也就意味着这个事件会被触发多次,所以我们有必要了解每一个状态值代表的含义:

readyState 状态描述 说明
0 UNSENT 代理(XHR)被创建,但尚未调用 open() 方法
1 OPENED open() 方法已经被调用,建立了连接
2 HEADERS_RECEIVED send() 方法已经被调用,并且已经可以获取状态行和响应头。
3 LOADING 响应体下载中, responseText 属性可能已经包含部分数据。
4 DONE 响应体下载完成,可以直接使用 responseText 。

2.1.1. 时间轴

1    var xhr = new XMLHttpRequest()
2   console.log(xhr.readyState) 3   // => 0
4   // 初始化 请求代理对象
5
6   xhr.open('GET', 'time.php')
7   console.log(xhr.readyState) 8   // => 1
9   // open 方法已经调用,建立一个与服务端特定端口的连接
10
11  xhr.send() 12
13  xhr.addEventListener('readystatechange', function () {14  switch (this.readyState) {15  case 2:
16  // => 2
17  // 已经接受到了响应报文的响应头
18
19  // 可以拿到头
20  // console.log(this.getAllResponseHeaders())
21  console.log(this.getResponseHeader('server')) 22  // 但是还没有拿到体
23  console.log(this.responseText)
24  break
25
26  case 3:
27  // => 3
28  // 正在下载响应报文的响应体,有可能响应体为空,也有可能不完整
29
30  // 在这里处理响应体不保险(不可靠)
31  console.log(this.responseText)
32  break
33
34  case 4:
35  // => 4
36  // 一切 OK (整个响应报文已经完整下载下来了)
37
38  // 这里处理响应体
39  console.log(this.responseText)
40  break
41  }
42  })

通过理解每一个状态值的含义得出一个结论:一般我们都是在 readyState 值为 4 时,执行响应的后续逻辑。

1    xhr.onreadystatechange = function () {2   if (this.readyState === 4) { 3       // 后续逻辑......
4   }
5   }

readyState状态变化:
readystate.html:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>readyState</title>
</head>
<body><script>var xhr = new XMLHttpRequest()console.log(xhr.readyState)// => 0// 初始化 请求代理对象xhr.open('GET', 'time.php')console.log(xhr.readyState)// => 1// open 方法已经调用,建立一个与服务端特定端口的连接xhr.send()xhr.addEventListener('readystatechange', function () {switch (this.readyState) {case 2:// => 2// 已经接受到了响应报文的响应头// 可以拿到头// console.log(this.getAllResponseHeaders())console.log(this.getResponseHeader('server'))// 但是还没有拿到体console.log(this.responseText)breakcase 3:// => 3// 正在下载响应报文的响应体,有可能响应体为空,也有可能不完整// 在这里处理响应体不保险(不可靠)console.log(this.responseText)breakcase 4:// => 4// 一切 OK (整个响应报文已经完整下载下来了)console.log(this.responseText)break}})</script>
</body>
</html>

2.2. 遵循 HTTP

本质上 XMLHttpRequest 就是 JavaScript 在 Web 平台中发送 HTTP 请求的手段,所以我们发送出去的请求任然是
HTTP 请求,同样符合 HTTP 约定的格式:

// 设置请求报文的请求行
xhr.open('GET', './time.php')
// 设置请求头
xhr.setRequestHeader('Accept', 'text/plain')
// 设置请求体
xhr.send(null)xhr.onreadystatechange = function () {if (this.readyState === 4) {// 获取响应状态码
console.log(this.status)
// 获取响应状态描述
console.log(this.statusText)
// 获取响应头信息 console.log(this.getResponseHeader('Content‐Type')) // 指定响应头 console.log(this.getAllResponseHeader()) // 全部响应头
// 获取响应体
console.log(this.responseText) // 文本形式
console.log(this.responseXML) // XML 形式,了解即可不用了
}
}

参考链接:
https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest

https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest

3.具体用法

3.1. GET 请求

通常在一次 GET 请求过程中,参数传递都是通过 URL 地址中的  ? 参数传递。
var xhr = new XMLHttpRequest()
// GET 请求传递参数通常使用的是问号传参
// 这里可以在请求地址后面加上参数,从而传递数据到服务端
xhr.open('GET', './delete.php?id=1')
// 一般在 GET 请求时无需设置响应体,可以传 null 或者干脆不传
xhr.send(null)
xhr.onreadystatechange = function () { if (this.readyState === 4) {
console.log(this.responseText)
}
}// 一般情况下 URL 传递的都是参数性质的数据,而 POST 一般都是业务数据

ajax-get.html:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>AJAX发送GET请求并传递参数</title>
</head>
<body><ul id="list"></ul><script>var listElement = document.getElementById('list')// 发送请求获取列表数据呈现在页面// =======================================var xhr = new XMLHttpRequest()xhr.open('GET', 'users.php')xhr.send()xhr.onreadystatechange = function () {if (this.readyState !== 4) returnvar data = JSON.parse(this.responseText)// data => 数据for (var i = 0; i < data.length; i++) {var liElement = document.createElement('li')liElement.innerHTML = data[i].nameliElement.id = data[i].idlistElement.appendChild(liElement)liElement.addEventListener('click', function () {// TODO: 通过AJAX操作获取服务端对应数据的信息// 如何获取当前被点击元素对应的数据的ID// console.log(this.id)var xhr1 = new XMLHttpRequest()xhr1.open('GET', 'users.php?id=' + this.id)xhr1.send()xhr1.onreadystatechange = function () {if (this.readyState !== 4) returnvar obj = JSON.parse(this.responseText)alert(obj.age)}})}}// 给每一个 li 注册点击事件// 因为 li 后来动态创建,所以不能这样注册事件// for (var i = 0; i < listElement.children.length; i++) {//   listElement.children[i].addEventListener('click', function () {//     console.log(111)//   })// }// var xhr = new XMLHttpRequest()// // 这里任然是使用URL中的问号参数传递数据// xhr.open('GET', 'users.php?id=2')// xhr.send(null)// xhr.onreadystatechange = function () {//   if (this.readyState !== 4) return//   console.log(this.responseText)// }</script>
</body>
</html>

user.php:

<?phpheader('Content-Type: application/json');
/*** 返回的响应就是一个 JSON 内容(返回的就是数据)* 对于返回数据的地址一般我们称之为接口(形式上是 Web 形式)*/// `/users.php?id=1` => id 为 1 的用户信息$data = array(array('id' => 1,'name' => '张三','age' => 18),array('id' => 2,'name' => '李四','age' => 20),array('id' => 3,'name' => '二傻子','age' => 18),array('id' => 4,'name' => '三愣子','age' => 19)
);if (empty($_GET['id'])) {// 没有 ID 获取全部// 因为 HTTP 中约定报文的内容就是字符串,而我们需要传递给客户端的信息是一个有结构的数据// 这种情况下我们一般采用 JSON 作为数据格式$json = json_encode($data); // => [{"id":1,"name":"张三"},{...}]echo $json;
} else {// 传递了 ID 只获取一条foreach ($data as $item) {if ($item['id'] != $_GET['id']) continue;$json = json_encode($item); // => [{"id":1,"name":"张三"},{...}]echo $json;}
}

3.2. POST 请求

POST 请求过程中,都是采用请求体承载需要提交的数据。

1   var xhr = new XMLHttpRequest()
2   // open 方法的第一个参数的作用就是设置请求的 method
3   xhr.open('POST', './add.php')
4   // 设置请求头中的 Content‐Type 为 application/x‐www‐form‐urlencoded 5   // 标识此次请求的请求体格式为 urlencoded 以便于服务端接收数据
6   xhr.setRequestHeader('Content‐Type', 'application/x‐www‐form‐urlencoded') 7 // 需要提交到服务端的数据可以通过 send 方法的参数传递
8   // 格式:key1=value1&key2=value2
9   xhr.send('key1=value1&key2=value2')
10  xhr.onreadystatechange = function () {
11  if (this.readyState === 4) {
12  console.log(this.responseText) 13   }
14  }

例子:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>AJAX发送POST请求</title><style>#loading {display: none;position: fixed;top: 0;left: 0;right: 0;bottom: 0;background-color: #555;opacity: .5;text-align: center;line-height: 300px;}#loading::after {content: '加载中...';color : #fff;}</style>
</head>
<body><div id="loading"></div><table border="1"><tr><td>用户名</td><td><input type="text" id="username"></td></tr><tr><td>密码</td><td><input type="password" id="password"></td></tr><tr><td></td><td><button id="btn">登录</button></td></tr></table><script>// 找一个合适的时机,做一件合适的事情var btn = document.getElementById('btn')// 1. 获取界面上的元素 valuevar txtUsername = document.getElementById('username')var txtPassword = document.getElementById('password')var loading = document.getElementById('loading')btn.onclick = function () {loading.style.display = 'block'var username = txtUsername.valuevar password = txtPassword.value// 2. 通过 XHR 发送一个 POST 请求var xhr = new XMLHttpRequest()xhr.open('POST', 'login.php')// !!! 一定注意 如果请求体是 urlencoded 格式 必须设置这个请求头 !!!xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')// xhr.send('username=' + username + '&password=' + password)xhr.send(`username=${username}&password=${password}`)// 3. 根据服务端的反馈 作出界面提示xhr.onreadystatechange = function () {if (this.readyState !== 4) returnconsole.log(this.responseText)loading.style.display = 'none'}}</script>
</body>
</html>

同步与异步

关于同步与异步的概念在生活中有很多常见的场景,举例说明。

同步:一个人在同一个时刻只能做一件事情,在执行一些耗时的操作(不需要看管)不去做别的事,只是等  待异步:在执行一些耗时的操作(不需要看管)去做别的事,而不是等待

xhr.open() 方法第三个参数要求传入的是一个 bool 值,其作用就是设置此次请求是否采用异步方式执行,默认
为 true ,如果需要同步执行可以通过传递 false 实现:

console.log('before ajax')
var xhr = new XMLHttpRequest()
// 默认第三个参数为 true 意味着采用异步方式执行
xhr.open('GET', './time.php', true)
xhr.send(null)
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
// 这里的代码最后执行
console.log('request done')
}
}
console.log('after ajax')

如果采用同步方式执行,则代码会卡死在 xhr.send() 这一步:

console.log('before ajax')
var xhr = new XMLHttpRequest()
// 同步方式
xhr.open('GET', './time.php', false)
// 同步方式 执行需要 先注册事件再调用 send,否则 readystatechange 无法触发
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
// 这里的代码最后执行
console.log('request done')
}
}
xhr.send(null)
console.log('after ajax')

演示同步异步差异。
一定在发送请求 send() 之前注册 readystatechange (不管同步或者异步):
为了让这个事件可以更加可靠(一定触发),一定是先注册。

了解同步模式即可,切记不要使用同步模式。

至此,我们已经大致了解了 AJAX 的基本 API 。

练习:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Document</title>
</head>
<body><script>// console.time('abc')// for (var i = 0; i < 100000000; i++) {}// console.timeEnd('abc')// console.log('begin request')var xhrAsync = new XMLHttpRequest()// open 方法的第三个参数是 async 可以传入一个布尔值,默认为 truexhrAsync.open('GET', 'time.php', true)console.time('async')xhrAsync.send()console.log(xhrAsync.responseText)// console.log('end request')console.timeEnd('async')// 同步模式 ajax 操作会有等待的情况// 区别在于 send 方法会不会出现等待情况// console.log('begin request')var xhrSync = new XMLHttpRequest()// open 方法的第三个参数是 async 可以传入一个布尔值,默认为 truexhrSync.open('GET', 'time.php', false)console.time('sync')xhrSync.send()console.log(xhrSync.responseText)// console.log('end request')console.timeEnd('sync')</script>
</body>
</html>

time.php:

<?php// for ($i = 0; $i < 100000; $i++) {
//   echo time();
// }echo time();

同步模式注册事件时机问题

先来做一个练习:

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>同步模式的AJAX</title></head><body><script>var xhr=new XMLHttpRequest()xhr.open('GET','time.php',false)xhr.send()xhr.onreadystatechange=function() {console.log(this.readyState)}</script></body>
</html>


你会发现,之前open参数为true的时候会打印2、3、4,现在为false却完全不打印了。

原因:

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>同步模式的AJAX</title></head><body><script>var xhr=new XMLHttpRequest()xhr.open('GET','time.php',false)xhr.send()//等到请求响应的过程全部完成再继续console.log(xhr.readyState)//这时候已经等于4了xhr.onreadystatechange=function() {//这时候注册onreadystatechange已经没意义了,它在一个值变成另外一个值的时候才会触发。console.log(this.readyState)}</script></body>
</html>

这就是注册时机太晚。所以要想onreadystatechange触发,必须在send之前。即:

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>同步模式的AJAX</title></head><body><script>var xhr=new XMLHttpRequest()xhr.open('GET','time.php',false)xhr.onreadystatechange=function() {console.log(this.readyState)}xhr.send()//等到请求响应的过程全部完成再继续console.log(xhr.readyState)</script></body>
</html>

那为什么设置为true就能打印出4了呢?是因为true表示异步,send方法就是发一个信号,完成是需要一些时间的,此时注册事件同步进行,发送完,事件也执行完了,所以能打印出4.

3.4. 响应数据格式

提问:如果希望服务端返回一个复杂数据,该如何处理?

关心的问题就是服务端发出何种格式的数据,这种格式如何在客户端用 JavaScript 解析

3.4.1. XML

一种数据描述手段 老掉牙的东西,简单演示一下,不在这里浪费时间,基本现在的项目不用了。

淘汰的原因:数据冗余太多

xml.html:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>AJAX 请求 XML 格式的数据</title>
</head>
<body><script>var xhr = new XMLHttpRequest()xhr.open('GET', 'xml.php')xhr.send()xhr.onreadystatechange = function () {if (this.readyState !== 4) return// this.responseXML 专门用于获取服务端返回的 XML 数据,操作方式就是通过 DOM 的方式操作// 但是需要服务端响应头中的 Content-Type 必须是 application/xmlconsole.log(this.responseXML.documentElement.children[0].innerHTML)console.log(this.responseXML.documentElement.getElementsByTagName('name')[0])}</script>
</body>
</html>

xml.php:

<?php
header('Content-Type: application/xml');
?>
<?xml version="1.1" encoding="utf-8"?>
<person><name>张三</name><age>18</age><gender>男</gender>
</person>

3.4.2. JSON

也是一种数据描述手段,类似于 JavaScript 字面量方式

服务端采用 JSON 格式返回数据,客户端按照 JSON 格式解析数据。

不管是 JSON 也好,还是 XML,只是在 A JAX 请求过程中用到,并不代表它们之间有必然的联系,它们只是 数据协议罢了

3.5. 处理响应数据渲染

模板引擎:

artTemplate:https://aui.github.io/art-template/

模板引擎实际上就是一个 API,模板引擎有很多种,使用方式大同小异,目的为了可以更容易的将数据渲染到 HTML中。

3.6 兼容方案

XMLHttpRequest 在老版本浏览器(IE5/6)中有兼容问题,可以通过另外一种方式代替

var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')

3.7 关于进程和线程

进程与线程:
进程
进行中的程序
线程
线程就是拿着剧本(代码)去演戏的演员,专业上是CPU最小执行单元

总结

本期学习到此结束。

前端学习从入门到高级全程记录之45 (ajax---1)相关推荐

  1. 前端学习从入门到高级全程记录之1 (HTML基础知识)

    本次学习目标 掌握HTML的基本知识,能够写出简单的页面. 1. 开发工具 1.浏览器(chrome(谷歌浏览器),IE浏览器,火狐浏览器,QQ浏览器等等). 2.代码编辑器(sublime,HBui ...

  2. 前端学习从入门到高级全程记录之41 (PHP基础Ⅳ)

    学习目标 本期接上期内容继续学习php基础知识. HTTP 1. 概要 1.1. 定义 HTTP(HyperText Transfer Protocol,超文本传输协议)最早就是计算机与计算机之间沟通 ...

  3. 前端学习从入门到高级全程记录之16(CSS高级技巧)

    本期目标 本期我们继续学习一些CSS的高级技巧. 1.CSS W3C 统一验证工具 CssStats 是一个在线的 CSS 代码分析工具,可以分析你写的代码到底好不好,哪里出错. 网址是: http: ...

  4. 前端学习从入门到高级全程记录之8 (PS基本使用综合案例)

    本期学习目标 本期我们将学习PS的基本使用并且运用以前所学的CSS和HTML的技术来完成一个综合案例. 1.Photoshop基本使用 早在第一期我就让大家去安装PS,不知道安装了没有,没有安装的尽快 ...

  5. 前端学习从入门到高级全程记录之12 (CSS高级技巧)

    学习目标 本期主要学习字体,内容较少.下一期我们将做一个京东的项目,用到的知识将会非常多,非常的有综合性. 1.Web字体 字体格式 不同浏览器所支持的字体格式是不一样的,我们有必要了解一下有关字体格 ...

  6. 前端学习从入门到高级全程记录之11 (云道页面例子后续)

    本期目标 本期学习的主要目标是:1.前几期"云道页面例子"的完善.2.CSS高级技巧的学习 1.云道页面完善 在前2期的云道案例中,我们完成了一半的布局,后面的内容需要用到定位等知 ...

  7. 前端学习从入门到高级全程记录之35(jQuery②)

    学习目标 本期继续学习jQuery,引入的jQuery文件用的还是上一期的. 1.jQuery操作样式 1.1 css操作 功能:设置或者修改样式,操作的是style属性  设置单个样式 //nam ...

  8. 前端学习从入门到高级全程记录之13 (京东项目一)

    学习目的 本期将会学习一个京东项目,综合知识非常多.首先我们要先了解一下这个项目的知识. 1.京东项目(一) 项目名称:京东网 项目描述:京东首页公共部分的头部和尾部制作,京东首页中间部分. 项目背景 ...

  9. 前端学习从入门到高级全程记录之39 (PHP基础Ⅱ)

    学习目标 本期我们将继续学习php的相关知识,在了解了上一期的PHP的一些基本语法和方法,接下来就是实战做做小例子.如果还不会配置php的运行环境的,可以参考我的上一期内容.如果上一期内容看不懂的,可 ...

最新文章

  1. python如何强制结束主线程_强制结束线程
  2. jsp springmvc 视图解析器_springMVC配置jsp/html视图解析器
  3. Kanas.net Framework 入门介绍
  4. 1007 素数对猜想(C语言)
  5. coco 数据集_Tensorflow对COCO目标检测数据预处理
  6. Ubuntu14.04编译Opencv3.1错误:下载ippicv,解决方案
  7. 第二次项目冲刺(Beta阶段)--第五天
  8. 信息论基础(学习笔记整理)
  9. cad自动填写页码lisp_图框文件名称自动填写 - AutoLISP/Visual LISP 编程技术 - CAD论坛 - 明经CAD社区 - Powered by Discuz!...
  10. 复制EXCEL单元格的值到SpreadJs单元格中,会多加一个可见的空格和一个不可见的0宽度空格的解决方法(ie11)
  11. 【论文解读】目标检测之RFBnet模型
  12. iOS开发学习之大牛们的博客
  13. CSS为字体添加过度色
  14. 《工程伦理与学术道德》之《导论》
  15. word恢复到安装时的状态?
  16. replaceAll()如何同时替换多个不同的字符串(或多个符号)
  17. 读彼得林奇的成功投资有感
  18. 通过各种实践活动 培养学生道德品质
  19. JavaScript获取时间戳的坑
  20. tensorflow入门教程(三十五)facenet源码分析之MTCNN--人脸检测及关键点检测

热门文章

  1. 计算机图形学——直线光栅化
  2. 跟着狂神老师配置Dubbo
  3. CRC32原理及实现学习
  4. 8880 e7 v2配什么主板_英特尔® 至强® 处理器 E7-8880 v2
  5. 贝壳创始人左晖去世,一周前才成地产新首富
  6. Xshell7如何查看登录密码
  7. 常用CAD快捷键命令大全
  8. 逻辑AND()运算符,带C语言示例
  9. 设计模式----工厂
  10. vsan虚拟化和共享存储服务器,恰逢其时:当VSAN遇到浪潮云海桌面一体机