您现在的位置是:网站首页 > Ajax基础文章详情

Ajax基础

Ajax是一种在无需重新加载整个页面的情况下,能够更新部分网页的技术。它通过在后台与服务器进行少量数据交换,使网页实现异步更新,从而提升用户体验。

Ajax的核心概念

Ajax全称为Asynchronous JavaScript and XML(异步JavaScript和XML),但如今JSON已经取代XML成为主流数据格式。其核心在于使用XMLHttpRequest对象与服务器通信,实现局部刷新。

关键组成部分包括:

  • XMLHttpRequest对象
  • JavaScript/DOM
  • CSS
  • XML/JSON

XMLHttpRequest对象

这是Ajax的基础,现代浏览器都支持这个对象。创建一个XHR对象的基本方式:

const xhr = new XMLHttpRequest();

XHR对象有几种重要的方法和属性:

// 初始化请求
xhr.open('GET', 'api/data', true);

// 发送请求
xhr.send();

// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/json');

// 响应状态
xhr.status
xhr.statusText

// 响应数据
xhr.responseText
xhr.responseXML

发送请求的完整流程

一个典型的Ajax请求包含以下步骤:

const xhr = new XMLHttpRequest();

xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    const response = JSON.parse(xhr.responseText);
    document.getElementById('result').innerHTML = response.data;
  }
};

xhr.open('GET', 'https://api.example.com/data', true);
xhr.send();

readyState有5个可能的值:

  • 0: 请求未初始化
  • 1: 服务器连接已建立
  • 2: 请求已接收
  • 3: 请求处理中
  • 4: 请求已完成且响应已就绪

处理不同响应类型

现代Ajax应用主要处理三种响应类型:

  1. JSON响应:
xhr.responseType = 'json';
xhr.onload = function() {
  console.log(xhr.response);
};
  1. XML响应:
xhr.responseType = 'document';
xhr.onload = function() {
  const xmlDoc = xhr.responseXML;
  const items = xmlDoc.getElementsByTagName('item');
};
  1. 文本响应:
xhr.responseType = 'text';
xhr.onload = function() {
  console.log(xhr.responseText);
};

错误处理

完善的Ajax实现需要处理各种错误情况:

xhr.onerror = function() {
  console.error('请求出错');
};

xhr.ontimeout = function() {
  console.error('请求超时');
};

xhr.onabort = function() {
  console.warn('请求被取消');
};

POST请求示例

发送POST请求与GET有所不同:

const xhr = new XMLHttpRequest();
xhr.open('POST', 'api/save', true);
xhr.setRequestHeader('Content-Type', 'application/json');

xhr.onload = function() {
  if (xhr.status === 201) {
    alert('数据保存成功');
  }
};

const data = {
  name: '张三',
  age: 25
};

xhr.send(JSON.stringify(data));

跨域请求

当请求不同源的资源时,需要处理跨域问题。CORS(跨源资源共享)是现代浏览器支持的解决方案:

// 服务器需要设置响应头
// Access-Control-Allow-Origin: *

// 客户端可以这样检测是否支持CORS
if ('withCredentials' in new XMLHttpRequest()) {
  console.log('支持CORS');
}

对于简单请求,浏览器会自动处理;对于非简单请求,会先发送OPTIONS预检请求。

进度事件

XHR2规范新增了进度事件,可以监控上传和下载进度:

xhr.upload.onprogress = function(e) {
  if (e.lengthComputable) {
    const percentComplete = (e.loaded / e.total) * 100;
    console.log(percentComplete + '% uploaded');
  }
};

xhr.onprogress = function(e) {
  if (e.lengthComputable) {
    const percentComplete = (e.loaded / e.total) * 100;
    console.log(percentComplete + '% downloaded');
  }
};

超时设置

可以设置请求超时时间(毫秒):

xhr.timeout = 5000; // 5秒超时
xhr.ontimeout = function() {
  console.error('请求超时');
};

取消请求

在请求完成前可以取消:

const xhr = new XMLHttpRequest();
xhr.open('GET', 'api/data', true);
xhr.send();

// 取消请求
xhr.abort();

现代替代方案:Fetch API

虽然XHR是Ajax的基础,但现代JavaScript提供了更简洁的Fetch API:

fetch('api/data')
  .then(response => {
    if (!response.ok) {
      throw new Error('网络响应不正常');
    }
    return response.json();
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('请求失败:', error);
  });

Fetch基于Promise,语法更简洁,但需要注意:

  • 默认不会发送或接收cookies
  • 错误处理与XHR不同
  • 不支持超时设置(需要额外实现)

实际应用示例

一个完整的用户注册表单验证示例:

document.getElementById('register-form').addEventListener('submit', function(e) {
  e.preventDefault();
  
  const formData = new FormData(this);
  const xhr = new XMLHttpRequest();
  
  xhr.open('POST', 'api/register', true);
  
  xhr.onload = function() {
    const response = JSON.parse(xhr.responseText);
    if (xhr.status === 200) {
      showSuccessMessage(response.message);
    } else {
      showErrors(response.errors);
    }
  };
  
  xhr.onerror = function() {
    showErrorMessage('网络错误,请重试');
  };
  
  xhr.send(formData);
});

function showSuccessMessage(msg) {
  const msgDiv = document.createElement('div');
  msgDiv.className = 'alert success';
  msgDiv.textContent = msg;
  document.body.appendChild(msgDiv);
}

性能优化技巧

  1. 节流频繁的Ajax请求:
let isRequesting = false;

function fetchData() {
  if (isRequesting) return;
  
  isRequesting = true;
  // 发送请求...
  xhr.onload = function() {
    isRequesting = false;
    // 处理响应
  };
}
  1. 缓存响应数据:
const cache = {};

function getCachedData(url, callback) {
  if (cache[url]) {
    callback(cache[url]);
    return;
  }
  
  fetch(url)
    .then(res => res.json())
    .then(data => {
      cache[url] = data;
      callback(data);
    });
}
  1. 批量请求:
function batchRequests(urls) {
  return Promise.all(urls.map(url => 
    fetch(url).then(res => res.json())
  ));
}

安全性考虑

  1. CSRF防护:
// 从cookie中获取token
function getCSRFToken() {
  return document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1');
}

// 设置请求头
xhr.setRequestHeader('X-XSRF-TOKEN', getCSRFToken());
  1. 输入验证:
function sanitizeInput(input) {
  return input.replace(/<[^>]*>/g, '');
}

const userInput = sanitizeInput(document.getElementById('comment').value);

浏览器兼容性

虽然现代浏览器都支持XHR,但需要考虑老版本IE:

let xhr;
if (window.XMLHttpRequest) {
  xhr = new XMLHttpRequest();
} else {
  // IE6及更早版本
  xhr = new ActiveXObject("Microsoft.XMLHTTP");
}

调试技巧

  1. 使用console.time测量请求时间:
console.time('ajaxRequest');
xhr.onload = function() {
  console.timeEnd('ajaxRequest');
  // 处理响应
};
  1. 查看请求详情:
xhr.onload = function() {
  console.log('状态:', xhr.status);
  console.log('响应头:', xhr.getAllResponseHeaders());
  console.log('响应数据:', xhr.response);
};

高级应用:长轮询

实现服务器推送的简单方式:

function longPoll() {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', 'api/updates', true);
  
  xhr.onload = function() {
    if (xhr.status === 200) {
      processUpdates(JSON.parse(xhr.responseText));
    }
    // 无论成功与否都重新发起请求
    setTimeout(longPoll, 1000);
  };
  
  xhr.onerror = function() {
    setTimeout(longPoll, 5000);
  };
  
  xhr.send();
}

// 启动长轮询
longPoll();

文件上传示例

使用Ajax上传文件:

<input type="file" id="file-input">
<button id="upload-btn">上传</button>
document.getElementById('upload-btn').addEventListener('click', function() {
  const fileInput = document.getElementById('file-input');
  const file = fileInput.files[0];
  
  if (!file) return;
  
  const formData = new FormData();
  formData.append('file', file);
  
  const xhr = new XMLHttpRequest();
  xhr.open('POST', 'api/upload', true);
  
  xhr.upload.onprogress = function(e) {
    const percent = Math.round((e.loaded / e.total) * 100);
    console.log(`上传进度: ${percent}%`);
  };
  
  xhr.onload = function() {
    if (xhr.status === 200) {
      console.log('上传成功');
    }
  };
  
  xhr.send(formData);
});

与jQuery的比较

传统jQuery Ajax写法:

$.ajax({
  url: 'api/data',
  type: 'GET',
  dataType: 'json',
  success: function(data) {
    console.log(data);
  },
  error: function(xhr, status, error) {
    console.error(error);
  }
});

现代JavaScript已经很少需要jQuery来处理Ajax,原生XHR和Fetch API已经足够强大。

实际项目中的封装

一个实用的Ajax封装示例:

class AjaxClient {
  constructor(baseURL = '') {
    this.baseURL = baseURL;
  }

  request(method, endpoint, data = null) {
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.open(method, `${this.baseURL}${endpoint}`, true);
      
      xhr.setRequestHeader('Accept', 'application/json');
      if (data && method !== 'GET') {
        xhr.setRequestHeader('Content-Type', 'application/json');
      }

      xhr.onload = () => {
        if (xhr.status >= 200 && xhr.status < 300) {
          try {
            resolve(JSON.parse(xhr.responseText));
          } catch (e) {
            resolve(xhr.responseText);
          }
        } else {
          reject({
            status: xhr.status,
            statusText: xhr.statusText
          });
        }
      };

      xhr.onerror = () => {
        reject({
          status: xhr.status,
          statusText: xhr.statusText
        });
      };

      xhr.send(data ? JSON.stringify(data) : null);
    });
  }

  get(endpoint) {
    return this.request('GET', endpoint);
  }

  post(endpoint, data) {
    return this.request('POST', endpoint, data);
  }

  // 可以继续添加put、delete等方法...
}

// 使用示例
const api = new AjaxClient('https://api.example.com');
api.get('/users')
  .then(users => console.log(users))
  .catch(error => console.error(error));

与前端框架的集成

在现代前端框架中使用Ajax:

  1. React示例:
function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    fetch('/api/users')
      .then(res => res.json())
      .then(data => {
        setUsers(data);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>加载中...</div>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
  1. Vue示例:
new Vue({
  el: '#app',
  data: {
    posts: [],
    isLoading: false
  },
  created() {
    this.fetchPosts();
  },
  methods: {
    fetchPosts() {
      this.isLoading = true;
      const xhr = new XMLHttpRequest();
      xhr.open('GET', '/api/posts', true);
      xhr.onload = () => {
        this.posts = JSON.parse(xhr.responseText);
        this.isLoading = false;
      };
      xhr.send();
    }
  }
});

测试Ajax请求

使用Jest测试Ajax代码:

// 模拟XHR
const mockXHR = {
  open: jest.fn(),
  send: jest.fn(),
  setRequestHeader: jest.fn(),
  readyState: 4,
  status: 200,
  responseText: '{"data": "test"}'
};

global.XMLHttpRequest = jest.fn(() => mockXHR);

test('fetchData成功返回数据', done => {
  fetchData((data) => {
    expect(data).toEqual({data: 'test'});
    done();
  });
  
  // 触发onload事件
  mockXHR.onload();
});

function fetchData(callback) {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', 'api/data', true);
  xhr.onload = function() {
    if (xhr.status === 200) {
      callback(JSON.parse(xhr.responseText));
    }
  };
  xhr.send();
}

性能监控

监控Ajax请求性能:

const startTimes = {};

function startRequestTracking(requestId) {
  startTimes[requestId] = performance.now();
}

function endRequestTracking(requestId) {
  const duration = performance.now() - startTimes[requestId];
  console.log(`请求 ${requestId} 耗时 ${duration.toFixed(2)}ms`);
  // 可以发送到监控系统
  sendToAnalytics({
    type: 'ajax',
    id: requestId,
    duration: duration
  });
}

// 使用示例
const reqId = 'user_data_' + Date.now();
startRequestTracking(reqId);

fetch('api/users')
  .then(() => endRequestTracking(reqId));

浏览器缓存策略

控制Ajax请求的缓存行为:

// 添加时间戳防止缓存
function noCacheUrl(url) {
  return url + (url.includes('?') ? '&' : '?') + '_=' + Date.now();
}

// 或者使用Fetch API的cache选项
fetch('api/data', {
  cache: 'no-store' // 或 'reload', 'no-cache', 'force-cache', 'only-if-cached'
});

数据压缩

减少传输数据量:

// 服务器设置响应头
// Content-Encoding: gzip

// 客户端可以检测是否支持压缩
xhr.setRequestHeader('Accept-Encoding', 'gzip, deflate');

二进制数据传输

使用Ajax处理二进制数据:

xhr.responseType = 'arraybuffer';

xhr.onload = function() {
  const arrayBuffer = xhr.response;
  if (arrayBuffer) {
    const byteArray = new Uint8Array(arrayBuffer);
    // 处理二进制数据...
  }
};

实时通信替代方案

虽然Ajax可以实现简单的实时通信,但对于真正的实时应用,考虑:

  1. WebSocket:
const socket = new WebSocket('wss://example.com/ws');

socket.onmessage = function(event) {
  console.log('收到消息:', event.data);
};
  1. Server-Sent Events (SSE):
const eventSource = new EventSource('api/events');

eventSource.onmessage = function(e) {
  console.log('事件数据:', e.data);
};

移动端优化

针对移动网络的Ajax优化:

  1. 减少请求大小:
// 只请求必要字段
fetch('api/users?fields=id,name,avatar')
  1. 使用数据压缩

  2. 实现离线缓存:

// 使用Service Worker缓存响应
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request))
  );
});

历史API与Ajax结合

实现无刷新页面切换:

// 加载内容
function loadPage(url) {
  fetch(url)
    .then(res => res.text())
    .then(html => {
      document.getElementById('content').innerHTML = html;
      // 修改历史记录
      history.pushState({url}, '', url);
    });
}

// 处理后退/前进
window.onpopstate = function(event) {
  if (event.state) {
    loadPage(event.state.url);
  }
};

认证与授权

处理需要认证的请求:

// 基本认证
xhr.open('GET', 'api/protected', true);
xhr.setRequestHeader('Authorization', 'Basic ' + btoa('username:password'));

// Bearer token
xhr.setRequestHeader('Authorization', 'Bearer ' + authToken);

内容安全策略(CSP)

当启用CSP时,可能需要调整:

<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; connect-src 'self' api.example.com;">

性能瓶颈分析

常见Ajax性能问题及解决方案:

  1. 过多小请求 → 批量请求
  2. 大响应数据

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步