前言
在整个html中,加载有多种,js加载,css样式,img,音频、视频,但是只有js文件是阻塞式同步加载,其他全部是异步加载。
- 同步:JavaScript 是一门单线程语言,同一时间只能执行一个任务,按照代码顺序,从上往下执行,代码执行是同步并且阻塞的。
- 异步:不管主线程有没有在执行,重新启用了一个线程来操作;只要有一定时间去触发的都是异步过程(比如 setTimeout()、setInterval()、requestAnimationFrame() 、onload 、onerror等)。外部样式表、图片、音频、视频都属于异步加载。
<body>
<div id="div0"></div>
<img src="./img/2-.jpg">
<script>
var img=document.querySelector("img");
//因为图片属于异步加载,执行console.log时,图片还没有加载完成,所以width是0
console.log(img.width);//0
</script>
</body>
js 中的事件属于异步,只有当触发时才会执行。
document.onclick=function(){
console.log("aaa");
}
引入<script>标签时
- script 标签中,加载 js 文件的方式是阻塞式同步,即每一个js是加载完成后,并且执行完成才会去加载执行下一个标签
- async,表示 js 将会由阻塞式同步变成异步加载
- defer,当页面已完成加载后,才会执行该 js 脚本,相当于window.onload
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--默认引入js文件时,表示先加载a.js,并a.js全部加载执行完毕,才会加载b.js-->
<!--如果a.js文件太大,则会造成页面阻塞,导致下面的图片、css样式等无法加载-->
<script src="./js/a.js"></script>
<script src="./js/b.js"></script>
<link rel="stylesheet" href="./css/style.css">
<!-- async异步 script 将会由阻塞式同步变成异步加载 -->
<script src="./js/a.js" async></script>
<!--defer 当页面已完成加载后,才会执行该 js 脚本-->
<script src="./js/b.js" async defer></script>
</head>
回调地狱
当希望事件A执行完成后去执行事件B,事件B执行完成后执行事件C,事件C执行完成后执行事件D…,以此类推,这样的操作如果从上到下依次写出来是下面这样的,代码一层一层嵌套,看起来很庞大。这种结构的代码,则被称之为回调地狱。
var base = 100;
console.log(base);//第一次打印
var img = new Image();
img.src = "./img/3-.jpg";
img.onload = function () {
base += img.width;
console.log(base);//第三次打印
var img1 = new Image();
img1.src = "./img/4-.jpg";
img1.onload = function () {
base += img1.width;
console.log(base);//第四次打印
var img2 = new Image();
img2.src = "./img/5-.jpg";
img2.onload = function () {
base += img2.width;
console.log(base);//第五次打印
// 回调地狱
}
}
}
console.log(base);//第二次打印
为了有一个好的编码习惯,我们规定,所有的代码都必须写在函数中,上面的代码经过优化后如下:
为了更好的解决回调地狱的问题,我们可以使用 Promise(接着往下看)。
var base=100;
init();
function init(){
var img=new Image();
img.src="./img/3-.jpg";
img.onload=imgloadHandler;
}
function imgloadHandler(){
base+=this.width;
console.log(base);
if(this.src.indexOf("3-.jpg")>-1){
this.src="./img/4-.jpg";
}else if(this.src.indexOf("4-.jpg")>-1){
this.src="./img/5-.jpg";
}
}
Promise
- Promise 是一个对象,主要用于异步操作。
- Promise 参数中接受两个函数 resolve 和 reject 作为其参数。当异步任务顺利完成且返回结果值时,会调用 resolve 函数;当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject 函数。
- Promise 有三个状态:Pending-进行中、Fulfilled-成功、Rejected-失败。
- Promise 对象的状态改变,只有两种可能:从pending变为fulfilled、从pending变为rejected。这两种情况只要发生,状态就凝固了,不会再变了。
let p=new Promise(function(resolve,reject){
// resolve是成功时要执行的回调函数
// reject是失败时要执行的回调函数
let img =new Image();
img.src="./img/0107/2-.jpg";
img.onload=function(){
resolve(img);
}
img.onerror=function(){
reject(img.src+"地址错误");
}
})
//then里面可以直接写两个参数,第一个参数对应resolve,第二个参数对应reject
p.then(function(img){
console.log(img)
},function(msg){
console.log(msg)
})
//也可以将reject对应的操作写成.catch()
p.then(function(img){
// resolve调用
console.log(img)
}).catch(function(msg){
// reject调用
console.log(msg)
})
- 想要某个函数拥有promise功能,只需让其返回一个promise即可。
function loadImage(src){
return new Promise(function(resolve,reject){
let img=new Image();
img.src=src;
img.onload=function(){
resolve(img);
}
img.onerror=function(){
reject("地址错误");
}
})
}
loadImage("./img/0107/2-.jpg").then(function(img){
console.log(img) //<img src="./img/0107/2-.jpg">
}).catch(function(msg){
console.log(msg)
})
- then 方法中返回一个 Promise 对象,它可以链式调用(如果在这个then中没有返回任何内容时,就会将原promise的实例对象返回)。
- 一旦执行 resolve 或者 reject 后,将不会再继续触发这些函数。
- 链接调用中,只要有一个 then 方法出现错误,则只执行一次 reject。
function loadImage(src){
return new Promise(function(resolve,reject){
let img=new Image();
img.src=src;
img.onload=function(){
resolve(img);
resolve(img);//不执行
}
img.onerror=function(){
reject("地址错误");
}
})
}
var sum=100;
loadImage("./img/3-.jpg").then(function(img){
sum+=img.width;
return loadImage("./img/4-.jpg")
}).then(function(img){
sum+=img.width;
return loadImage("./img/5-.jpg")
}).then(function(img){
sum+=img.width;
return loadImage("./img/6-.jpg")
}).then(function(img){
sum+=img.width;
return loadImage("./img/7-.jpg")
}).then(function(img){
sum+=img.width;
console.log(sum); //打印出结果
}).catch(function(msg){
console.log(msg);
})
Promise 的实现原理(待完善):
class Promise_1{
resolve;
reject;
state="pending";
constructor(fn){
this.fn=fn;
}
then(_resolve,_reject){
this.resolve=_resolve;
this.reject=_reject;
//因为then方法为异步,这里使用setTimeout来执行
this.ids=setTimeout(()=>this.callfn(),0);
//返回Promise对象,进行链式调用
return this;
}
catch(_reject){
this.reject=_reject;
this.ids=setTimeout(()=>this.callfn(),0);
}
callfn(){
clearTimeout(this.ids);
if(this.state!=="pending") return;
this.fn(this.resolve,this.reject);
//临时用来区分状态
this.state="finished"
}
}
function loadImage(resolve,reject){
var img=new Image();
img.src="./img/3-.jpg";
img.onload=function(){
resolve(img);
}
img.onerror=function(){
reject("错误的");
}
}
function resolve(img){
console.log(img);
}
function reject(msg){
console.log(msg);
}
var p=new Promise_1(loadImage);
p.then(resolve,reject);
// p.then(resolve).catch(reject);
Promise 的实现原理(完善一):
class Promise1{
status="pending"
constructor(fn){
fn(this.resolve.bind(this),this.reject.bind(this));
}
resolve(res){
if(this.status!=="pending") return;
let ids = setTimeout(function(){
this.setVal("resolve",res);
clearTimeout(ids);
}.bind(this),0)
}
reject(err){
if(this.status!=="pending") return;
let ids = setTimeout(function(){
this.setVal("reject",err);
clearTimeout(ids);
}.bind(this),0)
}
then(_resolve,_reject){
this._resolve=_resolve;
this._reject=_reject;
return this;
}
catch(_reject){
this._reject=_reject;
}
setVal(_status,arg){
this.status=_status;
if(_status==="resolve" && this._resolve){
this._resolve(arg)
}else if(_status==="reject" && this._reject){
this._reject(arg)
}
}
}
Promise用法
如果then中没有返回任何内容时,就会将原promise的实例对象返回
function loadImage(src){
return new Promise(function(resolve,reject){
let img=new Image();
img.src=src;
img.onload=function(){
resolve(img);
}
img.onerror=function(){
reject("地址错误");
}
})
}
loadImage("./img/3-.jpg").then(function(img){
console.log("bbb");//bbb
console.log(img);//<img src="./img/3-.jpg">
// 如果在这个then中没有返回任何内容时,就会将原promise的实例对象返回
}).then(function(img){
console.log("aaa");//aaa
//因为原promise实例没有再执行resolve,所以就没有得到参数
console.log(img);//undefined
})
loadImage("./img/3-.jpg").then(function(img){
console.log(img);//<img src="./img/3-.jpg">
return loadImage("./img/4-.jpg");
// 因为返回了一个新的promise对象,因此下面的then触发新对象中resolve,所以还会有返回结果
}).then(function(img){
console.log(img);//<img src="./img/4-.jpg">
})
Promise.all ( )
- 是静态方法,这个方法返回一个新的promise对象;
- Promise.all方法常被用于处理多个promise对象的状态集合;
- 被带入的参数以逐一异步的方式执行,在所有的 promise 都 resolve 时才会调用 .then 中的成功回调。
let arr=[];
for(let i=3;i<80;i++){
arr.push(loadImage(`./img/${i}-.jpg`));
}
// all在这里是可以将所有的promise对象,以逐一异步执行的方式全部执行完成
Promise.all(arr).then(function(list){
// list是所有promise对象的then中resolve函数的参数集合
list.forEach(item=>console.log(item.src));
}).catch(function(err){
//如果上面有一个错误,则只返回该错误信息
})
function loadImage(src){
return new Promise(function(resolve,reject){
let img=new Image();
img.src=src;
img.onload=function(){
resolve(img);
}
img.onerror=function(){
reject("地址错误");
}
})
}
Promise.race ( )
当参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数返回该promise对象
let arr=[];
for(let i=3;i<80;i++){
arr.push(loadImage(`./img/${i}-.jpg`));
}
Promise.race(arr).then(function(img){
console.log(img);
// 上述所有Promise谁先把执行resolve,就显示该resolve带回来的参数
})
function loadImage(src){
return new Promise(function(resolve,reject){
let img=new Image();
img.src=src;
img.onload=function(){
resolve(img);
}
img.onerror=function(){
reject("地址错误");
}
})
}
Promise.resolve ( )
如果你不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该 value 以 Promise 对象形式使用。
Promise.resolve(10).then(function(num){
console.log(num);
})
//相当于
var p=new Promise(function(resolve,reject){
resolve(10);
})
p.then(function(num){
console.log(num);
})
Promise.reject ( )
返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法
Promise.reject("错误的").catch(function(msg){
console.log(msg)
});
//相当于
var p=new Promise(function(resolve,reject){
reject("错误的");
})
p.catch(function(msg){
console.log(msg);
})
Promise的同步与异步
promise中写在 then 中函数里的内容或者 catch 函数里的内容都是异步的
console.log("aaa");//第一次执行
// var a=0;
let p=new Promise(function(resolve,reject){
console.log("ccc");//这是同步的,第二次执行
resolve(10);
});
p.then(function(num){
console.log("ddd");//这是异步的,第四次执行
console.log(num);//10,第五次执行
// 异步,没有onload,他本身也是异步过程
// a=num;
});
console.log("bbb");//是同步的,第三次执行