ajax同步异步
ajax是可同步可异步的一种技术。
在ajax对象调用open方法的时候,其实有第3个参数,值是布尔值,代表是否异步,默认为true。
当将第3个参数设置为false的时候,这次请求就变成了同步请求:
var xhr = new XMLHttpRequest;
// 省略了第三个参数:布尔值 - 代表当前请求是否异步 - 默认是true
// xhr.open('get','http://localhost:8888/test/first', true);
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status>=200 && xhr.status<300){
var res = xhr.responseText;
console.log(res);
}
}
}
// 将第三个参数换成false - 当前请求也就从异步变成同步了
xhr.open('get', 'http://localhost:8888/test/first', false);
xhr.send()
console.log(111);
注意:要提前监听。建议使用异步,
ajax封装
// 封装
function ajax(obj){
// 判断url
// 判断是否传入
if(obj.url === undefined){ // 没有传入
// console.log('请求地址不能为空');
// return
// 抛出自定义错误
throw new Error('请求地址不能为空') // 报错
}
// 传入了 - 判断类型
if(Object.prototype.toString.call(obj.url) != '[object String]'){
throw new Error('请求地址错误')
}
// 判断请求方式
if(obj.method === undefined){
obj.method = 'get'
}
// 限定请求方式必须是get或post
if(obj.method.toLowerCase() != 'get' && obj.method.toLowerCase() != 'post'){
throw new Error('请求方式只能是get或post')
}
// 将参数作为可选项 - 就在函数中判断,参数是否传入了
// 不传入的时候,给obj设置默认的参数的值
if(obj.isAsync === undefined){
obj.isAsync = true
}
// 判断类型
if(Object.prototype.toString.call(obj.isAsync) != '[object Boolean]'){
throw new Error('isAsync必须是布尔值')
}
// obj.data作为可选项
if(!obj.data){
obj.data = null
}
// 判断success和error
if(obj.success === undefined){
obj.success = function(){}
}
if(Object.prototype.toString.call(obj.success) != '[object Function]'){
throw new Error('success必须是函数')
}
if(obj.error === undefined){
obj.error = function(){}
}
if(Object.prototype.toString.call(obj.error) != '[object Function]'){
throw new Error('error必须是函数')
}
// 判断data - 如果参数较多,可以允许data以对象形式传入 - 转换成字符串放在send中
var data = '' // 不管传入的是字符串还是对象,最终要在地址后面或send中使用的数据
// 判断obj.data是否传入
if(obj.data != undefined){ // 如果数据传入了
// 判断类型
if(Object.prototype.toString.call(obj.error) === '[object String]'){
// 判断字符串数据中是否包含 =
if(!obj.data.includes('=')){
throw new Error('data数据是非法的!')
}
data = obj.data
}else if(Object.prototype.toString.call(obj.error) === '[object Object]'){
// 将对象转成字符串赋值给data变量
var arr = []
for(var key in obj.data){
arr.push(key + '=' + obj.data[key])
}
data = arr.join('&')
}else{
// 既不是字符串也不是对象
throw new Error('data必须是字符串或对象')
}
// 判断请求方式是否是get
if(obj.method.toLowerCase() === 'get'){
obj.url += '?' + data
}
}
var xhr = new XMLHttpRequest;
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status>=200 && xhr.status<300){
var res = xhr.responseText;
obj.success(res)
}else{
obj.error()
}
}
}
xhr.open(obj.method, obj.url, obj.isAsync);
if(obj.method.toLowerCase() === 'post' && data){
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
xhr.send(data)
return
}
xhr.send()
}
抛出自定义错误:
throw new Error(错误描述)
ajax请求文件
ajax除了可以请求服务器提供好的接口,还可以请求文件:任意文件 - 将文件中的内容读取出来 - 当前发送ajax的文件,必须是由http协议打开的。
ajax请求文件需要使用get请求,必须使用服务器打开当前页面。
ajax请求的文件类型中有两种比较特殊:
-
xml文件
var xhr = new XMLHttpRequest; xhr.open('get','./test.xml'); xhr.send() xhr.onreadystatechange = function(){ if(xhr.readyState === 4){ if(xhr.status>=200 && xhr.status<300){ // 如果ajax请求的是xml文件 - 接收响应的数据 - xhr.responseXML // 获取到的是一个文档 - 类似于我们DOM中的document var res = xhr.responseXML // console.log(res); // 可以像操作dom一样去操作响应的xml文档 var eles = res.getElementsByTagName('goods') // console.log(eles); // 遍历伪数组获取到每个数据 for(var i=0;i<eles.length;i++){ // console.log(eles[i]); var goodsnameTag = eles[i].getElementsByTagName('goodsname')[0] // console.log(goodsnameTag); console.log(goodsnameTag.innerHTML); } } } }
xml文件的语法:
一定要有一个文档声明:
<?xml version="1.0" encoding="utf-8"?>
xml文件中的标签都是自定义的 - 不能使用xml关键字作为标签名
一定要有一个唯一的根标签
标签可以有属性 - 属性的值一定是字符串
-
json文件
var xhr = new XMLHttpRequest; xhr.open('get','test.json'); xhr.send() xhr.onreadystatechange = function(){ if(xhr.readyState === 4){ if(xhr.status>=200 && xhr.status<300){ var res = xhr.responseText; // res = JSON.parse(res) res = eval(res) console.log(res); } } }
eval函数可以将一个字符串当做js代码执行。
json文件的语法:
json文件中必须是 一个 object/array
json文件中的object的键,必须加双引号
如果值是字符串,值也必须使用双引号
数组或对象的最后一项后面,不允许加逗号
完善ajax封装:
function ajax(obj){
if(obj.url === undefined) throw new Error('请求地址不能为空')
if(Object.prototype.toString.call(obj.url) != '[object String]') throw new Error('请求地址错误')
if(obj.method === undefined) obj.method = 'get'
if(obj.method.toLowerCase() != 'get' && obj.method.toLowerCase() != 'post') throw new Error('请求方式只能是get或post')
if(obj.isAsync === undefined) obj.isAsync = true
if(Object.prototype.toString.call(obj.isAsync) != '[object Boolean]') throw new Error('isAsync必须是布尔值')
if(!obj.data) obj.data = null
if(obj.success === undefined) obj.success = function(){}
if(Object.prototype.toString.call(obj.success) != '[object Function]') throw new Error('success必须是函数')
if(obj.error === undefined) obj.error = function(){}
if(Object.prototype.toString.call(obj.error) != '[object Function]') throw new Error('error必须是函数')
var data = ''
if(obj.data != undefined){
if(Object.prototype.toString.call(obj.data) === '[object String]'){
if(!obj.data.includes('=')) throw new Error('data数据是非法的!')
data = obj.data
}else if(Object.prototype.toString.call(obj.data) === '[object Object]'){
var arr = []
for(var key in obj.data){
arr.push(key + '=' + obj.data[key])
}
data = arr.join('&')
}else throw new Error('data必须是字符串或对象')
if(obj.method.toLowerCase() === 'get') obj.url += '?' + data
}
if(obj.dataType === undefined) obj.dataType = 'json'
if(obj.dataType.toLowerCase() != 'json' && obj.dataType.toLowerCase() != 'xml' && obj.dataType.toLowerCase() != 'text') throw new Error('dataType只接受json、text、xml')
var xhr = new XMLHttpRequest;
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status>=200 && xhr.status<300){
var res = '';
switch(obj.dataType.toLowerCase()){
case 'json':
res = xhr.responseText
res = JSON.parse(res)
break;
case 'xml':
res = xhr.responseXML
break;
case 'text':
res = xhr.responseText
break;
}
obj.success(res)
}else obj.error()
}
}
xhr.open(obj.method, obj.url, obj.isAsync);
if(obj.method.toLowerCase() === 'post' && data){
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
xhr.send(data)
return
}
xhr.send()
}
/// 调用模板 //
/*
ajax({
url: '', // 请求地址 - 必填项
method: '', // 请求方式 - 可选项,默认get
data: '', // 请求主体 - 可选项 - 允许字符串/object
isAsync: true, // 是否异步 - 可选项 - 默认true
dataType: 'json', // 希望返回的数据格式 - 可选项 - 默认json - 允许json/xml/text
success: res => {}, // 请求成功后要执行的函数 - 可选项 - 默认是一个匿名函数 - 会将请求回来的数据放在函数中作为参数
error: function(){} // 请求失败会执行的函数 - 可选项 - 默认是一个匿名函数
})
*/
上下文调用模式
也叫作借用函数。
任何函数天生带有3个方法:
bind - 复制函数,返回一个新的函数,并将原函数中的this改变
bind语法:
函数.bind() // 复制函数 - 返回一个新的函数,新函数跟原函数一模一样
例:
function fn(){
console.log(11);
}
var newFn = fn.bind() // 返回一个新函数
console.log(newFn); // 新函数跟原来的函数长的一模一样
console.log(fn === newFn); // 但是不是同一个
此时,这两个函数中的this是一样的
fn()
newFn()
改变this的语法:
函数.bind(this执行的对象) // 复制出来的新函数里面的this,就是参数 - 新的对象
例:
function fn(){
console.log(11);
console.log(this);
}
var obj = {
name: '张三',
age: 12
}
var newFn = fn.bind(obj) // 将fn复制一份成newfn,且newFn中的this代表obj
fn()
newFn()
使用场景:
// 使用场景
document.onclick = function(){
// 原本的this代表window - 定时器中的this
setTimeout( function(){console.log(this);} )
// 定时器的函数是复制出来的新函数,且将其中的this改成了外面的this - document
setTimeout( (function(){console.log(this);}).bind(this) )
}
call - 调用函数,调用过程中可以将原来函数中的this改成新的this
调用语法:
函数.call()
例:
function fn(){
console.log(22);
console.log(this);
}
fn()
console.log('------------------------------');
// 使用call来调用这个函数
fn.call()
调用的时候顺便改变this的语法:
函数.call(对象) // 此时函数被调用了,但是其中的this被改成了对象
例:
function fn(){
console.log(22);
console.log(this);
}
fn()
console.log('------------------------------');
var obj = {
name: '张三',
age: 22
}
// 调用fn并将其中的this改成obj
fn.call(obj)
如果函数中有参数,那call方法在调用函数的时候,也应该传入参数:
函数.call(改变后的this指向, 函数的实参1, 函数的实参2, ...)
call在调用函数的时候,从call的第2个参数开始,跟fn的形参对应。
例:
function fn(a, b){
console.log(a + b);
console.log(this);
}
fn(1, 2)
console.log('-------------------------------------');
fn.call(null, 2, 3) // 没有改this但是给fn传参数了
// 改this变给fn传参
var obj = {
name: '张三',
age: 22
}
fn.call(obj, 3, 6)
使用场景:
精准的检测数据类型:Object.prototype.toString.call(新的数据)
将伪数组转成数组:Array.prototype.slice.call(伪数组)
可以简写为:
{}.toString.call(新的数据)
[].slice.call(伪数组)
apply - 调用函数,调用过程中可以将原来函数中的this改成新的this
调用函数语法:
函数.apply()
例:
function fn(){
console.log(333);
console.log(this);
}
fn()
console.log('---------------------------------------------');
// apply调用
fn.apply()
调用并改this的语法:
函数.apply(对象) // 函数被调用了,且其中的this被改成了对象
例:
function fn(){
console.log(333);
console.log(this);
}
fn()
console.log('---------------------------------------------');
var obj = {
name: '张三',
age: 12
}
fn.apply(obj)
如果原函数中需要参数,apply在调用的时候也需要传入实参:
函数.apply(对象, [实参1, 实参2, ...]) // apply只有两个参数,第二个参数一定是一个数组,
//数组中的每个元素跟原函数中的形参对应
例:
function add(a, b){
console.log(a + b);
console.log(this);
}
add(1, 2)
console.log('--------------------------------------------');
add.apply(null, [2, 3]) // 没有改this,并传入实参 - this还是window
var obj = {
name: '张三',
age: 12
}
// 调用并改this,并传入实参
add.apply(obj, [5, 3])