Jsonp 和 Ajax 有个毛关系啊!!!
这一点非常重要啊
大概是jQuery把Jsonp放到了ajax API里面, 所以很多人总认为JSONP是AJAX的一种
但是, 一定要记住
JSONP和AJAX没有半毛钱关系
JSONP和AJAX没有半毛钱关系
JSONP和AJAX没有半毛钱关系
JSONP和AJAX没有半毛钱关系
JSONP和AJAX没有半毛钱关系
JSONP和AJAX没有半毛钱关系
这样应该就能记住了吧。。。。
JSONP
JSONP利用的是script发送请求的原理
说白了就是我们利用script标签可以通过向src中填的地址发送get请求这个特点, 创建一个动态的script标签, 然后用这个标签向服务器发送一个请求的一种方法
假设
请求方: frank.com的前端程序员(浏览器)
响应方: jack.com的后端程序员(服务器)
- 请求方创建script, src指向响应方, 同时传一个查询参数 ?callback=xxx
- 响应方根据查询参数callback, 构造形如
xxx.call(undefined, '数据')
这样的响应 - 浏览器接收到响应, 就会执行xxx.call(undefined, ‘数据’)
- 那么请求方就知道了他要的数据
这就是 JSONP
约定:
- callbackName -> callback
- xxx -> 随机数,
完整的JSONP请求如下
let script = document.createElement('script')
let functionName = 'ss' + parseInt(Math.random()*100000)
//接收到响应后我们要执行的代码, result就是我们接收到的响应数据
window[functionName] = function(result) {
...
}
//发送请求的地址和参数
script.src = 'http://jack.com:8002/pay?callback=' + functionName
//script便签只有在页面中才会执行, 所以我们把他添加到body下
document.body.appendChild(script)
//当成功得到响应并执完代码后, 销毁动态script标签, 表示此次http通讯完成
script.onload = function(e){
e.currentTarget.remove()
}
//当响应失败时, 同样销毁动态script标签, 表示此次http通讯完成
script.onerror = funciont(e){
e.currentTarget.remove()
}
用jQuery则更简单
$.ajax({
url: 'http://jack.com:8002/pay',
dataType: 'Jsonp',
success: function(response) {
...
}
})
Jsonp为什么不支持POST请求?
Jsonp通过动态创建script来实现的
script只能发送get请求, 没办法发送post请求
AJAX
AJAX (async JavaScript and XML)异步的 JavaScript 和 XML
首先我们来总结一下, 我们在html中可以通过哪些方法发送请求
- 用 form 可以发送请求, 但是会刷新或者新开页面
- 用 a 可以发 get 请求, 但也会刷新页面或新开页面
- 用 img 可以发送 get 请求, 但是只能以图片的形式展开
- 用 link 可以发送 get 请求, 但是只能以 CSS 和 favicon 的形式展示
- 用 script 可以发送 get 请求, 但是只能以脚本的形式运行, 这个就是JSONP的原理
那么, 有什么方法可以实现以下需求呢
- 发送 get、post、put、delete 任意一种请求
- 想用什么形式展示就用什么形式展示
微软牛逼了
IE5 的时候, 微软在JS中引入了ActiveX对象, 可以让JS直接发起HTTP请求, 这个技术在当时可算是一个牛逼炸了的功能, 这让我们可以很方便的在浏览器发送HTTP请求
随后Mozilla、safari、Opera等当时浏览器界的大佬们也抄袭了微软, 弄了一个XMLHttpRequest对象, 并被纳入W3C标准
AJAX产生了
Jesse James Garrett 将如下技术取名 AJAX: 异步的 JavaScript 和 XML
- 使用 XMLHttpRequest 发请求
- 服务器返回 XML 格式的字符串(现在主要使用 JSON 格式)
- JS解析 XML 格式字符串(现在基本上都是解析 JSON 格式字符窜), 并更新局部页面
这里有一点需要强调, 服务器返回的响应体是字符串, 并不是什么对象或者其他乱七八糟的东西, 就是字符串
如何使用XMLHttpRequest
最简单的AJAX请求
let xhr = new XMLHttpRequest() //创建一个AJAX请求对象
xhr.open('GET', '/xxx') //初始化HTTP请求, 包括但不限于用什么方法, url
xhr.send() //发送HTTP请求
xhr.onreadystatechagne()
这个方法是监听readystate改变的方法, 很好用
readyState的五种状态
对于readyState的五种状态的描述或者说定义,很多Ajax书(英文原版)中大都语焉不详
比较理想的解释方法应该以“状态:任务(目标)+过程+表现(或特征)”的表达模式来对这几个状态进行定义
readyState状态 | 状态说明 |
---|---|
(0)未初始化 | 此阶段确认XMLHttpRequest对象是否创建,并为调用open()方法进行未初始化作好准备。值为0表示对象已经存在,否则浏览器会报错--对象不存在。 |
(1)载入 | 此阶段对XMLHttpRequest对象进行初始化,即调用open()方法,根据参数(method,url,true)完成对象状态的设置。并调用send()方法开始向服务端发送请求。值为1表示正在向服务端发送请求。 |
(2)载入完成 | 此阶段接收服务器端的响应数据。但获得的还只是服务端响应的原始数据,并不能直接在客户端使用。值为2表示已经接收完全部响应数据。并为下一阶段对数据解析作好准备。 |
(3)交互 | 此阶段解析接收到的服务器端响应数据。即根据服务器端响应头部返回的MIME类型把数据转换成能通过responseBody、responseText或responseXML属性存取的格式,为在客户端调用作好准备。状态3表示正在解析数据。 |
(4)完成 | 此阶段确认全部数据都已经解析为客户端可用的格式,解析已经完成。值为4表示数据解析完毕,可以通过XMLHttpRequest对象的相应属性取得数据。 |
由于 readystate 有这么多状态, 所以我们可以通过监听 readystate 的状态的改变, 获取改变后的readystate值, 并利用这个值判断请求是否发送成功
所以我们完善一下上面的AJAX请求
let xhr = new XMLHttpRequest()
//监听readystate改变
xhr.onreadystatechange = () => {
//readyState变为4说明响应已完毕
if(xhr.readyState === 4) {
//http状态码为2xx说明接收响应成功
if(xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.responseText)
//http状态码大于等于400说明接收响应失败
} else if(xhr.status >= 400) {
console.log('error')
}
}
xhr.open('GET', '/xxx') //初始化HTTP请求, 包括但不限于用什么方法, url
xhr.send() //发送HTTP请求
}
通常response返回的都是一个符合JSON格式的字符串
JSON
JSON(JavaScript Object Natation)是一种轻量级的数据交换语言, 是JavaScript的子集, 这里要注意, JSON不是编程语言, 所以没有变量, 引用, 判断, 循环等语法
JSON 没有 functon和 undefined
JSON的字符串必须是"
JSON表示对象, key必须以双引号括起来
{"name": "Adam"}
JSON的格式举例
“abv” //字符串
123 //数字
null //null
{
“name”: "Adam"
} //对象
["a", "b", "c"] //数组
有兴趣研究JSON的请点这里, 反正JSON所有的内容也就5分钟就学完了
还是上面的请求
let xhr = new XMLHttpRequest()
//监听readystate改变
xhr.onreadystatechange = () => {
//readystate变为4说明响应已完毕
if(xhr.readyState === 4) {
//http状态码为2xx说明接收响应成功
if(xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.responseText)
//http状态码大于等于400说明接收响应失败
} else if(xhr.status >= 400) {
console.log('error')
}
}
xhr.open('GET', '/xxx') //初始化HTTP请求, 包括但不限于用什么方法, url
xhr.send() //发送HTTP请求
}
假设我们发送请求后, 服务器给我们返回的是 JSON 格式的字符串
{
"note": {
"to": "Eve",
"from: "Adam",
"heading": "Hello",
"content": "hi"
}
}
上面的是字符串啊, 记住是字符串啊!!!!!!!
所以我们需要用JSON.parse()
将符合 JSON 格式的字符串转换成JavaScript对应的值, 上面的响应内容将会转化成对象.
这里又有个关于JSON的误区, 很多人认为JSON.parse()
转换回来的都是对象, 这个其实是错的, 只不过大多数后端传回来的 JSON 格式字符串都是符合对象的写法, 所以将字符串转换后才是对象, 如果响应回来的字符串符合数组格式, 那么转换回来的就是数组
let Object = JSON.parse(xhr.responseText)
所以完整的AJAX请求如下
let xhr = new XMLHttpRequest()
//监听readystate改变
xhr.onreadystatechange = () => {
//readyState变为4说明响应已完毕
if(xhr.readyState === 4) {
//http状态码为2xx说明接收响应成功
if(xhr.status >= 200 && xhr.status < 300) {
let Object = JSON.parse(xhr.responseText)
console.log(Object)
//http状态码大于等于400说明接收响应失败
} else if(xhr.status >= 400) {
console.log('error')
}
}
xhr.open('GET', '/xxx') //初始化HTTP请求, 包括但不限于用什么方法, url
xhr.send() //发送HTTP请求
}
XMLHttpRequest设置请求头
上面的例子是最典型的AJAX请求, 但是有些场景, 我们需要对请求头的内容进行设置
XMLHttpRequest.setRequestHeader() 是设置HTTP请求头部的方法。此方法必须在 open()
方法和 send()
之间调用。如果多次对同一个请求头赋值,只会生成一个合并了多个值的请求头。
语法
myReq.setRequestHeader(header, value)
参数
header
属性的名称。
value
属性的值。
XMLHttpRequest设置请求体
XMLHttpRequest.send() 方法用于发送 HTTP 请求。如果是异步请求(默认为异步请求),则此方法会在请求发送后立即返回;如果是同步请求,则此方法直到响应到达后才会返回。XMLHttpRequest.send() 方法接受一个可选的参数,其作为请求主体;如果请求方法是 GET 或者 HEAD,则应将请求主体设置为 null。
语法
void send();
void send(ArrayBuffer data);
void send(ArrayBufferView data);
void send(Blob data);
void send(Document data);
void send(DOMString? data);
void send(FormData data);
如果发送的数据是Document对象,需要在发送之前将其序列化。当发送一个Document对象时,Firefox 3之前的版本都是使用utf-8编码发送请求的;FireFox 3则使用由body.xmlEncoding
指定的编码格式正确的发送文档,但如果未指定编码格式,则使用utf-8编码格式发送。
如果是一个nsIInputStream接口,它必须与nsIUploadChannel的setUploadStream()方法兼容。在这种情况下,将 Content-Length的头部添加到请求中,它的值则使用nsIInputStream接口的available()方法获取。任何报头包括在数据流顶部的都会被当做报文主体。所以,应该在发送请求即调用send()方法之前使用setRequestHeader()
方法设置 Content-Type头部来指定数据流的MIME类型。
发送二进制内容的最佳方法(如上传文件)是使用一个与send()方法结合的 ArrayBufferView 或者Blobs
案例: GET
const xhr = new XMLHttpRequest();
xhr.open('GET', '/server', true);
xhr.onload = function () {
// 请求结束后,在此处写处理代码
};
xhr.send(null);
// xhr.send('string');
// xhr.send(new Blob());
// xhr.send(new Int8Array());
// xhr.send({ form: 'data' });
// xhr.send(document);
案例: POST
const xhr = new XMLHttpRequest();
xhr.open("POST", '/server', true);
//发送合适的请求头信息
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {//Call a function when the state changes.
if(xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
// 请求结束后,在此处写处理代码
}
}
xhr.send("foo=bar&lorem=ipsum");
// xhr.send('string');
// xhr.send(new Blob());
// xhr.send(new Int8Array());
// xhr.send({ form: 'data' });
// xhr.send(document);
同源策略与跨域
之前见到一个问题, 为什么表单可以跨域而AJAX不能跨域请求呢?
这是因为原页面用form提交到另一个域之后, 原页面的脚本无法获取新页面的内容, 所以浏览器认为是安全的。而AJAX是可以读取响应内容的, 因此浏览器不能允许你这样做。
这个问题就引出了我们这一段的主题, 同源策略和跨域
将跨域前要先说一下同源策略
同源策略
假设我们的域名为http://www.baidu.com:80
通常情况下我们只能在 http://www.baidu.com:80 这个域中向 http://www.baidu.com:80 这个域的其他路径发送AJAX请求, 如果我们给 http://www.qq.com:80 这个域发送AJAX请求, 浏览器就会拒绝这个请求, 这就是浏览器的同源策略
同源策略只对AJAX有效
什么样才叫同源呢?
只有协议+域名+端口一模一样的才是同源
同源
http://www.baidu.com:80/index 和 http://www.baidu.com:80/main.js 是同源
非同源
- http://www.baidu.com:80 和 http://www.baidu.com:8080 不是同源
- http://www.baidu.com:80 和 https://www.baidu.com:8080 不是同源
- http://baidu.com:80 和 http://www.baidu.com:8080 不是同源
跨域
就是请求发送方向不是同源的接收方发送请求
如何发送跨域请求
- JSONP 可以发送跨域请求
- CORS跨域
CORS跨域
在后台的响应头中设置Access-Control-Allow-Origin
即可, 假设我们的地址是 http://adam.com:80 我们可以告诉浏览器, 我们允许 http://frank.com:8080 这个域给我们发送跨域AJAX请求, 我们可以在响应头中这么设置
response.setHeader('Access-Control-Allow-Origin', 'http://frank.com:8080')
这样 http://frank.com:8080 这个域就可以给我们发送跨域AJAX请求了
今天的主题讲完啦, 我们下面运用今天的知识来自己封装一个AJAX
封装一个AJAX
第一版就是最简单的封装, 接收五个参数
window.jQuery = function(node){
let nodes = {
0: node,
length: 1
}
return {
addClass: function(){
}
}
}
window.jQuery.ajax = function(url, method, body, successFn, failFn) {
let xhr = new XMLHttpRequest()
xhr.open(method, url)
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
successFn.call(undefined, xhr.responseText)
}else if(xhr.status >= 400) {
failFn.call(undefined, xhr)
}
}
}
xhr.send(body)
}
window.$ = window.jQuery
我们在第一版的基础上一行把参数改为接收一个对象
window.jQuery = function(node){
let nodes = {
0: node,
length: 1
}
return {
addClass: function(){
}
}
}
window.jQuery.ajax = function({url, method, body, successFn, failFn, headers}) {
let xhr = new XMLHttpRequest()
xhr.open(method, url)
for(let key in headers) {
xhr.setRequestHeader(key, headers[key])
}
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
successFn.call(undefined, xhr.responseText)
}else if(xhr.status >= 400) {
failFn.call(undefined, xhr)
}
}
}
xhr.send(body)
}
window.$ = window.jQuery
在第二版的基础上用promise封装一下, 让我们可以通过then来调用成功和失败后的函数
window.jQuery = function(node){
let nodes = {
0: node,
length: 1
}
return {
addClass: function(){
}
}
}
window.jQuery.ajax = function({url, method, body, headers}) {
return new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest()
xhr.open(method, url)
for(let key in headers) {
xhr.setRequestHeader(key, headers[key])
}
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
resolve.call(undefined, xhr.responseText)
}else if(xhr.status >= 400) {
reject.call(undefined, xhr)
}
}
}
xhr.send(body)
})
}
window.$ = window.jQuery