定时器相关
setInterval/clearInterval
设置/取消定时器,举例:
let i = 0;
let timer = setInterval(function() {
if (i > 3) clearInterval(timer);
console.log(i++);
}, 1000)
// 每秒输出一次i,当i大于3时取消定时器
setTimeout/clearTimeout
设置/取消异步延时代码,举例:
setTimeout(function() {
console.log(1);
}, 1000)
// 1秒后输出1
ajax
Asynchronous JavaScript And XML,实质就是从服务器上读取文件,因此需要有服务器的环境下进行,这里在tomcat
中测试。
编写ajax
1.创建一个ajax对象
var oAjax = new XMLHttpRequest();
上面那个有唯一的问题就是不兼容IE6
及以下,如果是IE6就用:
var oAjax = new ActiveXObject("Microsoft.XMLHTTP");
所以兼容性判断时:
if (window.XMLHttpRequest) { //如果不加window在IE6下还是会报错,看下面注解
var oAjax = new XMLHttpRequest();
} else {
var oAjax = new ActiveXObject("Microsoft.XMLHTTP");
}
注:
在JS中所有全局变量和一些关键字等都是window
下的属性,比如定义一个:var a = 1
,那么a
已经定义,在console
里结果是1,此时如果用window.a
会发现结果和a
一样都是1。
而变量作为一个属性去调用的话,就算没定义,结果也不会报错,而是显示undefined
,比如前面很多标签的style
属性没定义,但是我们直接在浏览器console
里可以看到没设置值的属性结果不会报错,而是显示undefined
。再比如没定义a
之前在console
里输入a
会报错说没定义过,但是输入window.a
会发现结果是undefined
。
上面兼容性判断要加window
,就是因为在IE6
下他会把这个当成一个变量值,所以如果不加window就会显示没定义,结果就报错了
2.连接服务器
Ajax对象.open('方法', '文件路径', 是否异步传输)
其三个参数中,方法有GET
和POST
,文件路径可以是绝对相对,第三个参数异步则true
,同步则false
,举例:
oAjax.open('get', 'aaa.txt', true);
3.发送请求
Ajax对象.send();
4.接收返回信息
Ajax对象.onreadystatechange=function(){
if(oAjax.readyState == 4){ //内容解析完成
if(oAjax.status == 200){ //文件读取成功
…
}
}
}
Ajax对象属性
(1)Ajax对象下有readyState
值,用来监控请求情况:
0 (未初始化)还未调用open()方法
1 (载入)已调用send()方法,正在发送请求
2 (载入完成)send()完成,已收到全部响应内容
3 (解析)正在解析响应内容,比如解析HTTPS发过来的加密内容
4 (完成)响应内容解析完成,可以在客户端调用了
(2)Ajax对象下还有status
值,代表请求状态码,比如200是成功,参考http请求状态码即可。
(3)responseText
,表示返回结果的内容
简单Ajax实例
(读取本地aaa.txt
内容并显示)
html中:
js中:
function ajax_fun() {
if (XMLHttpRequest) {
var oAjax = new XMLHttpRequest(); //创建Ajax对象,除IE6及以下
} else {
var oAjax = new ActiveXObject("Microsoft.XMLHTTP"); //IE6及以下
}
oAjax.open('get', 'aaa.txt?t=' + new Date().getTime(), true); //建立连接,且阻止缓存
oAjax.send(); //发送请求
oAjax.onreadystatechange = function() { //接收后执行内容
if (oAjax.readyState == 4) { //响应解析完成
if (oAjax.status == 200) { //文件读取成功,上面解析完成并不代表成功读取,可能会有文件不存在等情况
alert(oAjax.responseText); //显示读取的文本内容
}
}
}
}
注:
可以看出原生的ajax
语法过于麻烦,而JQuery
框架为我们提供了一套语法更加简洁的ajax
封装接口,方便我们编写ajax
请求
编码
注意本地文件的编码和HTML页面的编码要相符,否则将会出现乱码
缓存/阻止缓存
一般ajax默认读取文件会缓存,所以在服务器文件更新后,用ajax读取的文件可能并不会即使更新,这在像股票之类的要求实时性高的页面里是很致命的,所以就需要阻止缓存,方法有很多,主要是为了让服务器感觉到接收请求的不同,比如常见的就是在读取的url后面加上会变化的参数,举例:
'url?t='+new Date().getTime()
因为getTime()
获取的数据时1970年1月1号到现在所距离的毫秒数,所以每次访问时请求的参数值基本不可能一样,从而也就达到了阻止缓存的效果
Promise
语法优势
Primoise对象,可以实现链式调用,使用起来更加灵活,并且可以解决回调地狱的问题,例如下面代码:
asyncfun1(function(success1) {
asyncfun2(success1, function(success2) {
asyncfun3(success2, function(success3) {
...
}, failCallback3())
}, failCallback2())
}, failCallback1())
通过Promise
对象改写就可以变成:
asyncfun1().then(function(success1) {
return asyncfun2(success1)
})
.then(function(success2) {
return asyncfun3(success2)
})
.then(function(success3) {
...
})
.catch(failCallback() {
...
})
可以看出使用Promise
对象编写后的代码有以下优点:
- 由于返回的是
Promise
对象,因此可以在异步任务完成后,后面自己指定何时进行回调操作 - 代码无需嵌套,避免回调地狱
- 统一失败异常处理
当然使用async
/await
将更好的优化代码,但其本质也是基于Promise
对象实现的(async
声明的函数返回的是一个Promise
对象)
定义Promise对象
new Promise((resolve, reject) => {
...
resolve()
...
reject()
})
.then(value => {
...
})
.catch(reason => {
...
})
可以看出Promise
对象需要传入一个同步回调函数(不等待,立即执行),而该函数里会传入两个异步回调函数(会先放入回调队列,不会立即执行):resolve
和reject
,分别代表在成功和失败时执行的回调(本质是调用onResolved
和onRejected
方法)。实例化的Promise
对象通过then
执行成功回调,catch
捕捉失败回调
Promise状态
状态变化:pending(等待)->resolve(成功)/reject(失败)
只有通过resolve
/reject
发送通知时才改变状态,否则一直处于等待状态,并且状态改变是不可逆的,比如:
new Promise((resolve, reject) => {
resolve("success");
reject("failed");
})
因为已经先进入了成功状态,所以也就不会进入后面的失败状态了
then链式调用机制
- 返回的是异常:会被
catch
捕捉 - 返回的是
Promise
对象:对该对象的处理——then
处理resolve
,catch
处理reject
- 返回的是非异常且非
Promise
对象:返回值被当做下一个then
的传入值(默认是成功状态)
举例:
new Promise((resolve, reject) => {
resolve(1)
}).then(value => { // value接收到promise对象
console.log(value);
return 2
}).then(value => { // value接收到2
console.log(value);
}).then(value => { // value没接收到东西
console.log(value);
return new Promise((resolve, reject) => {
resolve(3)
})
}).then(value => { // value接收到promise对象
console.log(value);
throw 5
}).catch(reason => { // reason接收到异常5
console.log(reason);
})
// 结果:
// 1
// 2
// undefined
// 3
// 5
注:
Promise
还提供了resolve
和reject
的api方便调用,例如:
new Promise((resolve, reject) => {
resolve(xxx)
})
可以等价于:
Promise.resolve(xxx)
注2:
由于then
会不停的链式调用下去,如果想要中断的话,可以返回return new Promise(() => {})
,因为此时返回的是Promise
对象,并且没有调用成功和失败的回调,所以后面的then
也就不会执行了
finally
异步结果不论成功失败都一定会执行,举例:
new Promise((resolve, reject) => {
resolve("success");
}).then(data => {
...
}).finally(() => {
...
})
Promise提供API
Promise.resolve()/Promise.reject()
成功和失败通知
Promise.all()
全部操作都成功则返回所有结果状态,传入一个数组里面存放所有异步操作
Promise.allSettled()
全部不管成不成功都返回所有结果状态,传入一个数组里面存放所有异步操作
Promise.race()
哪个先出结果状态,就返回哪个,其他的就不管了,传入一个数组里面存放所有异步操作
自定义Promise实现
通过上面的介绍,我们可以自己简单实现一个Promise
对象,代码如下:
class MyPromise {
// 三种状态:等待/成功/失败
static _PENDING = 0;
static _RESOLVE = 1;
static _REJECT = -1;
constructor(excutor) {
// 初始化等待状态
this._status = this._myClass._PENDING;
// 存储所有任务队列,格式:[{onResolved(){}, onRejected(){}}, ...]
this._callbacks = [];
// 存放数据
this._data = null;
this._checkFun(excutor);
try {
// 回调绑定当前对象
excutor(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
return this._myClass.reject(e);
}
}
// 当前类
get _myClass() {
return this.constructor;
}
// 判断三种状态
get _wait() {
return this._status === this._myClass._PENDING;
}
get _success() {
return this._status === this._myClass._RESOLVE;
}
get _failed() {
return this._status === this._myClass._REJECT;
}
// 函数判断
_checkFun(func) {
if (!(func instanceof Function)) throw Error("请传入一个函数");
}
// 当前类判断
_checkThis(_this) {
return _this instanceof this._myClass;
}
// 由于状态改变不可逆,只有等待状态下才能转到成功状态,失败状态同理
resolve(res) {
if (!this._wait) return;
this._status = this._myClass._RESOLVE;
this._data = res;
// 执行队列中的成功任务
if (this._callbacks.length > 0)
setTimeout(() => this._callbacks.forEach(oCallback => oCallback.onResolved(res)));
}
reject(reason) {
if (!this._wait) return;
this._status = this._myClass._REJECT;
this._data = reason;
// 执行队列中的失败任务
if (this._callbacks.length > 0)
setTimeout( () => this._callbacks.forEach(oCallback => oCallback.onRejected(reason)));
}
// then方法,只有成功和失败状态才能执行对应的回调,否则将任务添加到队列当中
then(onResolved, onRejected) {
onResolved = onResolved instanceof Function ? onResolved : res => res;
onRejected = onRejected instanceof Function ? onRejected : reason => { throw reason; };
const self = this;
// 返回一个新的Promise对象
return new (this._myClass)((resolve, reject) => {
function handle(callback) {
try {
const result = callback(self._data);
if (result instanceof self._myClass) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (error) {
reject(error);
}
}
if (self._wait)
self._callbacks.push({
onResolved() {
handle(onResolved);
},
onRejected() {
handle(onRejected);
}
});
else if (self._success) setTimeout(() => handle(onResolved));
else if (self._failed) setTimeout(() => handle(onRejected));
});
}
// catch方法,处理成功则返回成功回调
catch(onRejected) {
return this.then(null, onRejected);
}
// finally方法:除了等待状态都会执行,并返回当前对象
finally(callback) {
if (this._wait) return this;
this._checkFun(callback);
try {
callback();
return this;
} catch (e) {
return this._myClass.reject(e);
}
}
// 成功回调静态方法,如果本身是当前类,则执行then,否则生成一个Promise对象并自动转换到成功状态
static resolve(res) {
return new MyPromise((resolve, reject) => {
if (res instanceof MyPromise) res.then(resolve, reject);
else resolve(res);
});
}
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
}
// 测试代码
let p = new MyPromise((resolve, reject) => {
resolve("success");
})
.then(res => {
console.log(res, 1);
return MyPromise.resolve("2");
})
.then(res => {
console.log(res, 2);
return "3";
})
.then(res => {
console.log(res, 3);
throw Error("error3");
})
.catch(reason => {
console.log(reason, 4);
throw Error("error4");
})
.then(res => {
console.log(res, 5);
})
.then(res => {
console.log(res, 6);
})
.finally(() => {
console.log("end");
})
.then(res => {
console.log(res, 7);
})
.catch(reason => {
console.log(reason, 8);
});
console.log("start...");
// start...
// success 1
// 2 2
// 3 3
// Error: error3 ... 4
// end
// Error: error4 ... 8
上面的测试代码,把MyPromise
改成Promise
结果也是一样的。这里只实现了主要的resolve
/reject
/then
/catch
/finally
功能,其他API其实也都是基于这些基本功能实现,就不一一实现了。
更多实现参考:
https://www.cnblogs.com/yjhua/articles/8549834.html
https://www.cnblogs.com/jerryleeplus/p/12122231.html
async/await
实际上就是对Promise
操作的封装,使得我们编写异步代码像写同步代码一样。其中使用async
关键字声明的函数返回是一个Promise
对象,await
关键字相当于then
的包装,必须在声明了async
关键字的函数里才能够使用,举例:
async function test() {
let val = await ...;
// 后面的语句必须等await执行完才能执行
console.log(val);
}
上面例子等价如下:
new Promise((resolve, reject) => {
...
resolve();
}).then(val => {
console.log(val);
})
多个await
并行实现
- 使用
Promise.all
保证内容都执行完毕,举例:
async function test() {
let res = await Promise.all([p1(), p2(), ...]);
// ...
}
- 分别使用
await
,举例:
async function test() {
let res1 = await p1();
let res2 = await p2();
// ...
}
浏览器任务调度机制
- 定时器会被放进定时器模块直接开始计时,时间到了以后则放入宏任务队列(一般有最小时间限制,4ms左右,因此即使设置为0,实际上也会稍微等待一小会才会到时间放入宏任务队列),如果多个定时器,那么谁时间先到就先放入宏任务队列,然后先执行
- Promise内容会直接执行,但是状态改变的通知(
resolve
/reject
)会放入微任务队列 - 主线程会先将同步队列的内容全部执行完,然后开始轮询微任务队列并执行,最后再轮询宏任务队列并执行,期间执行完每个任务后都会重新依次遍历同步队列->微任务队列->宏任务队列
可以结合下面的例子来理解:
setTimeout(() => {
console.log("settimeout");
}, 0)
new Promise((resolve, reject) => {
console.log("promise");
resolve("resolve");
}).then(data => {
console.log(data);
})
console.log("main");
// promise
// main
// resolve
// settimeout
由于Promise里的内容和外面的内容一样是同步的,而其发布的成功/失败处理请求(resolve/reject)是异步的,所以then/catch的处理处于微任务队列,而setTimeout处于宏任务队列,因此得到了上面的最终结果。再来看下面示例:
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("settimeout");
resolve("resolve");
}, 0)
console.log("promise");
}).then(data => {
console.log(data);
})
console.log("main");
// promise
// main
// settimeout
// resolve
上面发布的微任务是在将执行宏任务时创建的,此时宏任务已经相当于同步执行,所以setTimeout在微任务之前输出。如果上面两个都懂了,下面这个也差不多:
setTimeout(function(){
console.log("timeout1");
}, 0);
console.log("main1");
for (let i=0; i< 10000000; i++);
new Promise(resolve => {
console.log("promise");
setTimeout(function() {
resolve();
console.log("timeout2");
})
}).then(data => {
console.log("then");
})
console.log("main2");
// main1
// promise
// main2
// timeout1
// timeout2
// then
其他
异步加载图片
function loadImg(src, resolve, reject) {
let img = new Image();
img.src = src;
img.onload = resolve;
img.onerror = reject;
}
loadImg("xxx.jpg",
() => {
console.log("success");
},
() => {
console.log("failed");
})
Promise异步加载script
function loadScript(src) {
return new Promise((resolve, reject) => {
let script = document.createElement("script");
script.src = src;
script.onload = () => resolve(script);
script.onerror = reject;
document.body.appendChild(script);
});
}
loadScript("http://xxx.js").then(script => {
console.log(script);
console.log("加载成功");
}).catch(() => {
console.log("加载失败");
})
Promise封装setTimeout
let timeout = time => new Promise(resolve => setTimeout(resolve, time))
timeout(1000)
.then(() => {
console.log(111);
})
Promise封装setInterval
let interval = (time, callback) => new Promise(resolve => {
let inter = setInterval(() => callback(inter, resolve), time);
})
interval(1000, (inter, resolve) => {
console.log(111);
clearInterval(inter);
resolve();
})
.then(() => {
console.log("success");
})
Promise封装sleep函数
function sleep(time) {
return new Promise(resolve => {
setTimeout(() => resolve(), time);
})
}
async function test() {
await sleep(2000);
console.log(111);
}
test();
注:
Promise
封装的sleep
函数必须通过异步方式调用,并且同步代码也需要卸载异步函数里,如果想要实现同步的sleep
函数,可以通过指定时间结束来实现,举例:
function sleep(n) {
let date = Date.now() + n;
while (date > Date.now());
}
sleep(1000)
Promise封装异步队列
function queue(num) {
let p = Promise.resolve();
num.map(v => {
p = p.then(_ => {
console.log(v);
});
});
}
function p1() {
return new Promise(resolve => {
setTimeout(() => {
console.log("p1");
resolve();
}, 1000);
});
}
function p2() {
return new Promise(resolve => {
setTimeout(() => {
console.log("p2");
resolve();
}, 1000);
});
}
console.log(111);
queue([1,2,3,4,5, p1(), p2()])
console.log(222);
异步类
类里如果封装了then
方法,也可以当成异步Promise
对象来使用,举例:
class Test {
constructor(name) {
this.name = name;
console.log("init");
}
then(resolve, reject) {
console.log(this.name);
resolve("success");
}
}
async function test() {
let res = await new Test("aaa");
console.log(res);
}
test();
// init
// aaa
// success
WebSocket
是一种与后台新的交互方式,相较于之前的ajax,其无需遵循必须前台请求后台,后台再返回内容给前台的模式;通过WebSocket
,双方可以建立通信接口(类似Socket
通信的方式),从而前后台都能够互相主动向对方通信。代码示例:
websocket
使用场景
- 解决一些曾经需要用
ajax
轮询监听后台的场景 - 一些实时场景,如:聊天室、弹幕等
websocket封装
二进制读取+心跳检测+断线重连
class BaseWs {
constructor(url) {
this.url = url;
this.init();
}
init() {
try {
this._stopHeart();
this.ws = new WebSocket(this.url);
this.ws.onopen = this.openCallback.bind(this);
this.ws.onmessage = this.recvCallback.bind(this);
this.ws.onclose = this.closeCallback.bind(this);
this.ws.onerror = this.errorCallback.bind(this);
} catch (e) {
console.log(e);
// 断线重连
setTimeout(() => this.restart(), 3000);
}
}
// 重启
restart() {
this.init();
}
send(data) {
// 当连接状态时才允许发送信息,否则重连
if (this._isOpen()) this.ws.send(data);
else this.restart();
}
close(e) {
this._stopHeart();
this.ws.close();
}
// 连接成功状态
_isOpen() {
return this.ws.readyState === 1;
}
// 启动心跳检测
_startHeart() {
if (this.heart) this._stopHeart();
this.heart = setInterval(() => {
this.send(1);
}, 1000);
}
// 停止心跳检测
_stopHeart() {
if (this.heart === null) return;
clearInterval(this.heart);
this.heart = null;
}
// 连接成功后启动计时器进行心跳检测
openCallback(e) {
this._startHeart();
}
// 接收消息读取,并调用recv回调
recvCallback(msg) {
let reader = new FileReader();
let _this = this;
reader.onload = function (event) {
_this.recv(reader.result);
};
reader.readAsText(event.data);
}
// 读取信息回调
recv(data) {}
closeCallback(e) {
this._stopHeart();
}
errorCallback(e) {
console.log(e);
this.close();
}
}
nginx配置websocket支持及连接测试
连接测试
安装wscat:
npm install -g wscat
输入命令测试是否连通:
wscat -c ws://xxx
nginx配置
参考:https://www.jianshu.com/p/a8e884228a16