jQuery的$.ajax
方法和AngularJs的$http
服务处理方式默认是不一样的,所以对于相同的请求,后端的得到的数据格式不一样,下面总结这两种方法请求的差异以及如何自定义请求格式。
为了前后端交互,更好的查看http协议和数据,后端用Node.js和express,并用中间件处理发送的数据
定义发送的数据为:
var data = {
department: '前端',
member: ['John Wood', 'Lily Jones', 'Tom Smith'],
tasks: {
html: 2,
js: 3
}
};
jQuery的GET请求
js代码为
$.ajax({
type: 'GET',
url: '/ajax/data',
data: data,
success: function(data) {
console.log(data.result);
}
});
控制台查看请求为
查询字符串为:
解析前:
department=%E5%89%8D%E7%AB%AF&member%5B%5D=John+Wood&member%5B%5D=Lily+Jones&member%5B%5D=Tom+Smith&tasks%5Bhtml%5D=2&tasks%5Bjs%5D=3
浏览器解析后:
department:前端
member[]:John Wood
member[]:Lily Jones
member[]:Tom Smith
tasks[html]:2
tasks[js]:3
可看到空格用+号代替,而数组和对象都解析为键值对的形式,所以后端需根据正确的方式去解析才能得到正确的数据格式,通过express的qs模块解析出来结果为:
格式基本正确,但数字变为了字符串,所以为了得到正确的格式需要再进行判断或自己手写解析方法。
AngularJs的GET请求
js代码为
$http({
method: 'GET',
url: '/ajax/data',
params: data,
})
.success(function(data) {
console.log(data.result);
});
控制台查看请求为:
基本和jQuery一样,除了Accept略微不同。
解析前:
department=%E5%89%8D%E7%AB%AF&member=John+Wood&member=Lily+Jones&member=Tom+Smith&tasks=%7B%22html%22:2,%22js%22:3%7D
浏览器解析后:
department:前端
member:John Wood
member:Lily Jones
member:Tom Smith
tasks:{"html":2,"js":3}
可看到AngularJs在处理数组和对象时方式和jQuery不一样,数组扩展为键值对形式,但键名没有[],对象直接转换为JSON,通过express的qs模块解析出来结果为:
数组能正确解析,但对象是JSON字符串,所以需要进一步解析。
jQuery的POST请求
js代码为
$.ajax({
type: 'POST',
url: '/ajax/data',
data: data,
success: function(data) {
console.log(data.result);
}
});
控制台查看请求为
查询字符串为:
解析前:
department=%E5%89%8D%E7%AB%AF&member%5B%5D=John+Wood&member%5B%5D=Lily+Jones&member%5B%5D=Tom+Smith&tasks%5Bhtml%5D=2&tasks%5Bjs%5D=3
浏览器解析后:
department:前端
member[]:John Wood
member[]:Lily Jones
member[]:Tom Smith
tasks[html]:2
tasks[js]:3
可以发现jQuery的默认内容类型为application/x-www-form-urlencoded,所以查询字符串都编码为键值对的形式,而urlencoded属于w3c标准和RFC不一样,所以空格编码为+而不是%20。后端通过express以及bodyParser模块的urlencoded方法解析的结果为
可看出结果与实际差别较大。
AngularJs的POST请求
js代码为
$http({
method: 'POST',
url: '/ajax/data',
data: data,
})
.success(function(data) {
console.log(data.result);
});
控制台查看请求为
解析前:
{"department":"前端","member":["John Wood","Lily Jones","Tom Smith"],"tasks":{"html":2,"js":3}}
浏览器解析后:
department: "前端"
member: ["John Wood", "Lily Jones", "Tom Smith"]
tasks: {html: 2, js: 3}
可以发现AngularJs的默认内容类型为application/json,所以请求报文的正文是以JSON的形式发送的,而后端通过express以及bodyParser模块的json方法解析的结果为:
和传输的数据格式一模一样。
数据的传输需要前后端以相同的方式进行才能保证得到正确的数据,通过了解jQuery和AngularJs的编码方式就可以在后端以对应的方式解析数据,同时可以根据需要改变框架默认的编码方式,同时制定对应的Content-Type,帮助后端识别数据类型,以满足需求
jQuery发送JSON
对于GET请求,jQuery总是会把data转换键值对形式,如果data为字符串,则以字符串的形式直接附在URL后,如果是对象则进行序列化,如果为number、null或undefined则忽略,所以以GET发送对象时,可以手动调用JSON.stringify把对象的值转换为字符串,在后端再调用JSON.parse解析。
对于POST请求,指定Content-Type,并传输对应格式,可以让数据得到正确解析,如下例子:
$.ajax({
type: 'POST',
url: '/ajax/data',
data: JSON.stringify(data),
contentType: 'application/json;charset=UTF-8',
success: function(data) {
console.log(data.result);
}
});
后端解析结果:
AngularJs以urlencoded编码
对于GET请求,Angular处理方式与jQuery不一样,它总是先把params参数转换为对象,再进行编码,所以不能把params直接赋值为字符串,通常我们可以利用它的编码特征,把参数设为{p:{}}
的形式,在后端得到p的值再解析JSON为对象,如果一定需要像jQuery的形式,AngularJs1.5提供了一个服务$httpParamSerializerJQLike
,示例如下:
$http({
method: 'GET',
url: '/ajax/data',
params: data,
paramSerializer: '$httpParamSerializerJQLike',
})
.success(function(data) {
console.log(data.result);
});
查询字符串为:
department:前端
member[]:John Wood
member[]:Lily Jones
member[]:Tom Smith
tasks[html]:2
tasks[js]:3
和jQuery编码的方式一样。
对于POST请求,把Content-Type
指定为application/x-www-form-urlencoded; charset=UTF-8
,对于简单的对象可以自己手动转换,如把data
赋值为name=wind&age=20
,对于复杂对象也可以调用$httpParamSerializerJQLike服务(需要先依赖注入),示例如下:
$http({
method: 'GET',
url: '/ajax/data',
//data: 'name=wind&age=20', //手动编码
data: $httpParamSerializerJQLike(data),
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
})
.success(function(data) {
console.log(data.result);
});
编码的结果和上面相同,当然这时后端需要对应方法解析,nodejs里bodyParser模块的urlencoded方法不能满足要求。上述设置是针对单个请求而言的,如果想直接改变 http服务的默认方式,则需要对 http进行配置:
angular.module('myApp', [])
.config(function($httpProvider) {
$httpProvider.defaults.headers.
post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
})
也可以在运行时通过$http对象的defaults属性对这些默认值进行修改:
$http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
发送multipart/form-data
通过multipart/form-data类型可以发送文件、图片,在表单元素中,可以设定enctype="multipart/form-data"
,此时表单内容类型就为multipart/form-data,也可在JS中创建手动formData对象:
var formData = new FormData();
formData.append('name', 'wind');
formData.append('name', 'Jack');
formData.append('age', 20);
jQuery示例:
$.ajax({
type: 'POST',
url: '/ajax/data',
data: formData,
contentType: false,
processData: false,
success: function(data) {
console.log(data.result);
}
});
把contentType设为false则浏览器会自动的加上正确的Content-Type类型,如果手动写上的话可能浏览器无法识别,processData设为false让jQuery不进行处理,以原始数据发送,查看请求报文如下:
通过nodejs的multiparty模块解析的结果为:
AngularJs示例:
$http({
method: 'POST',
url: '/ajax/data',
data: formData,
headers: {
'Content-Type': undefined
}
})
.success(function(data) {
console.log(data.result);
});
AngularJs里把Content-Type设为undefined,表示由浏览器默认处理,请求报文除了boundary值不同基本相同与jQuery相同。
文件上传
对于文件,input的dom对象有一个files的数组,因此可把input.files[0]添加到formData对象中,一个例子:
$('#file').change(function() {
var formData = new FormData();
var file = this.files[0];
formData.append('file', file);
$.ajax({
type: 'POST',
url: '/ajax/data',
data: formData,
contentType: false,
processData: false,
success: function(data) {
console.log(data);
}
});
});
附:express4路由处理代码
'use strict';
const express = require('express');
const router = express.Router();
const multiparty = require('multiparty');
const util = require('util');
const fs = require('fs');
module.exports = router;
router.get('/', (req, res) => res.render('ajax'));
router.get('/data', (req, res) => {
let data = req.query;
console.log(util.inspect(data, {colors: true}));
res.send({result: 'success'});
});
router.post('/data', (req, res) => {
if (req.headers['content-type'].indexOf('multipart/form-data') > -1) {
let form = new multiparty.Form({
uploadDir: './tmp',
});
form.parse(req, function(err, fields, files) {
console.log(util.inspect({fields: fields, files: files}, {colors: true, depth: null}));
if (files.file) {
let uploadPath = files.file[0].path;
let desPath = `./tmp/${files.file[0].originalFilename}`;
fs.renameSync(uploadPath, desPath);
}
});
res.send({result: 'success'});
} else {
let data = req.body;
console.log(util.inspect(data, {colors: true}));
res.send({result: 'success'});
}
});