本文共 13550 字,大约阅读时间需要 45 分钟。
同步、异步
代码的同步异步,标签的同步异步
代码的同步异步
1、代码的同步就是,从上往下顺序执行。
2、代码的异步就是,代码运行到此处,不等结果出来,就开始运行下面的内容。说明此处程序乃异步执行。举个小栗子:
var a = 3;var b = 4;var s = a + b;console.log("a + b=" + (a + b));document.onclick = function () { console.log("异步加载s=" + s);}var img = new Image();img.src = "./img/3-.jpg";img.onload = function () { console.log("img.width"+img.width);}console.log("同步加载s=" + s);
执行结果:
可以看出来,console.log(“同步加载s=” + s)语句先执行了。图片的预加载是异步过程;点击事件只有被触发后才会做出处理,也是异步过程。3、除了上面两种情况,只要有一定时间去触发的都是异步过程。
比如:计时器: setTimeout()、setInterval()requestAnimationFrame();img.onerror=function(){ //onerror}
4、回调地狱(纵深)
var base = 100;console.log(base); //1 首先执行var img = new Image();img.src = "./img/3-.jpg";img.onload = function () { base += img.width; console.log(base); //3 需要时间 var img1 = new Image(); img.src = "./img/4-.jpg"; img.onload = function () { base += img.width; console.log(base); //4 // ...... 回调地狱,程序纵深向编写 }}console.log(base); //2
所以,如果使用函数式编程,将代码写在函数中,使代码格局抽象化,根本不会有回调地狱的问题出现。
5、函数式编程
var base=1000;init();function init(){ var img=new Image(); img.src="./img/3-.jpg"; img.onload=imgloadHandler;}function imgloadHandler(){ base+=this.width; console.log(base); //this指向当前img /* console.log(this.src); //http://127.0.0.1:5500/class-review/20200210/img/3-.jpg console.log(this.src==="./img/3-.jpg"); //false 上一句打印的才是this.src if(this.src==="./img/3-.jpg"){ //进不来 this.src="./img/4-.jpg"; } */ if(this.src.indexOf("3-.jpg")>-1){ //src一旦更改,img.onload就被触发 this.src="./img/4-.jpg"; }else if(this.src.indexOf("4-.jpg")>-1){ // 这种方式就进来了 this.src="./img/5-.jpg"; }}console.log(base);6、Promise 解决回调地狱问题,下面有详细解释。
标签的同步异步
阻塞式同步加载,是异步变同步加载(异步–>同步)。
script标签的加载,是阻塞式同步加载:每一个js文件加载完成后,并且执行完后才执行下一个标签。 在整个html中,加载有多种,js加载是其中一种,还有link,img,音视频的加载;但是只有js是阻塞式同步加载,其他皆为异步加载。举个小栗子:
main.html
a.jsconsole.log("aaa");main.jsvar div=document.getElementById("div0");console.log(div);var img=document.querySelector("img");console.log(img.width);
如果加载main.js的标签script 中没加defer,就会报错,因为图片的加载时异步过程,此时图片还没有,哪来的属性width。
而如果加了defer,表示最后执行,则不会报错。Promise
Promise 解决回调地狱问题,将程序改成横向链式调用,用法都比较固定,面试很重要。
Promise 原理问题
1、Promise的三种状态(也有人这么分类:pending/reslove/reject)
(1)Pending 进行状态 (2)Fulfilled 成功状态 (3)Rejected 失败状态 这三种状态标明已经执行了什么,继而决定下一步。2、Promise对象用于表示一个异步操作的最终完成(或失败),及其结果值。
Promise对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象。
大师兄说的很有道理:“ Promise就是个函数代理,你传个函数进来,我给你代理。这个函数里面的异步操作如果成功了就调用resolve,失败了就调用reject,resolve和reject可以传参,参数可以在then方法里面拿到。”
再看看大师兄如何解释英语单词的:“ Promise,承诺,对吧,代理函数,承诺你一定会成功或者失败,只管承诺不会立即调用;resovle是处理,说明成功;reject拒绝,你异步操作失败了,我的诺言就是拒绝你。 ”
Promise 生成
如果Promise生成时,一旦执行了resolve或reject函数,就不会再触发其他函数。
resolve是成功时要执行的回调函数; reject是失败时要执行的回调函数。拿img图片加载作为示例:
1、Promise 生成,第一种是实例化对象方式var p=new Promise(function(resolve,reject){ var img=new Image(); img.src="./img/3-.jpg"; img.onload=function(){ // resolve函数可以带入一个参数 resolve(img); // 一旦执行resolve或者reject将不会再继续触发这些函数,所以下面的函数不会执行 // resolve(img); // reject("错误"); } img.onerror=function(){ // reject函数也可以带入一个参数 reject(img.src); }})//第一种写法,then里面写两个函数,并列p.then(function(img){ //第一个函数,resolve对应执行函数 console.log(img); },function(src){ //第二个函数,reject... console.log(src);})//第二种写法,then中写一个函数,然后外面再写一个函数p.then(function(img){ // resolve调用}).catch(function(src){ // reject调用})
打印一下:
2、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(img.src+"地址错误"); } });}var sum=0;//第一种写法,有返回一个新的Promise,可以继续写.then;与之成对的catch可以放到最后写一个,就很管用了。这种叫链式调用。loadImage("./img/3-.jpg").then(function(img){ sum+=img.width; return loadImage("./img/4-.jpg"); //如果有返回一个新的Promise对象,因此下面的then触发新对象中resolve,所以下面的then中有返回结果}).then(function(img){ sum+=img.width; return loadImage("./img/5-.jpg");}).then(function(img){ sum+=img.width; console.log(sum); }).catch(function(msg){ console.log(msg); //一旦异步操作失败,就执行onerror事件绑定的处理方法,调用reject函数,参数可以在catch中拿到。})如果加载成功,结果为6488;如果失败,能看到打印出了 img.src+"地址错误"。//第二种写法,如果没有返回值,则只能写一对then、catchloadImage("./img/3-.jpg").then(function(img){ console.log(img); //如果在这个then中没有返回任何内容时,就会将原Promise的实例对象返回}).catch(function(msg){ console.log(msg); //因为原promise实例已经执行resolve,所以就不会再执行reject});
Promise 封装
(注意:还不是太懂,先放放)
1、Promise封装 版1class Promise666 { resolve; reject; state = "pending"; constructor(fn) { this.fn = fn; // fn(this.resolve,this.reject); } then(_resolve, _reject) { this.resolve = _resolve; this.reject = _reject; this.ids = setTimeout(() => this.callfn(), 0); return this; } catch(_reject) { this.reject = _reject; this.ids = setTimeout(() => this.callfn(), 0); } callfn() { //实现resolve或reject函数被执行,就不再执行其他函数了的功能 clearTimeout(this.ids); if (this.state !== "pending") return; this.fn(this.resolve, this.reject); }}//跟以前一样function loadImage(src) { return new Promise666(function (resolve, reject) { let img = new Image(); img.src = src; img.onload = function () { resolve(img); } img.onerror = function () { reject(img.src + "地址错误"); } });}loadImage("./img/3-.jpg").then(function (img) { console.log(img);}).catch(function (msg) { console.log(msg);});//提取出来看看function loadImage1(resolve,reject){ var img=new Image(); img.src="./img/3-.jpg"; img.onload=function(){ resolve(img); } img.onerror=function(){ reject("错误ing"); }}function resolve(img){ console.log(img);}function reject(msg){ console.log(msg);}var p=new Promise666(loadImage1);p.then(resolve).catch(reject);
结果:
2、Promise封装 版2class Promise1 { _status = "pending"; constructor(fn) { fn(this.resolve.bind(this), this.reject.bind(this)); } resolve(result) { if (this.status !== "pending") return; var ids = setTimeout((function () { this.setValue("resolve", result); clearTimeout(ids); }).bind(this), 0); } reject(error) { if (this.status !== "pending") return; var ids = setTimeout((function () { this.setValue("reject", error); clearTimeout(ids); }).bind(this), 0); } then(fn1, fn2) { this.fn1 = fn1; this.fn2 = fn2; } catchs(fn2) { this.fn2 = fn2; } setValue(state, data) { this.status = state; if (state === "resolve" && this.fn1) { this.fn1(data); } else if (state === "reject" && this.fn2) { this.fn2(data); } }}
Promise 用法
1、静态方法 all 的使用
all 可以完成图片的预加载,还保证了图片加载顺序从上而下。
all 可以将所有的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("地址错误"); } })}let arr = [];for (let i = 3; i < 80; i++) { // 把Promise放进去 // arr.push(loadImage("./img/"+i+"-.jpg")); //或下面的方法也行 arr.push(loadImage(`./img/${ i}-.jpg`));}// console.log(arr); //能够看到arr数组里存放着77个Promise对象//all 在这里是可以将所有的promise对象,以逐一异步执行的方式全部执行完成。Promise.all(arr).then(function (list) { // console.log(list); //list是一个参数集合,包含所有img // list 是所有promise对象的then中resolve函数的参数集合 list.forEach(item => console.log(item.src));})
arr、list:
结果: …从3-.jpg一直到79-.jpg,逐一异步加载 2、race (有比赛的意味)let arr = [];for (let i = 3; i < 80; i++) { arr.push(loadImage(`./img/${ i}-.jpg`)); //看上面一个程序的,不在赘述loadImage}Promise.race(arr).then(function (img) { console.log(img); // 上述所有Promise谁先执行resolve,就显示该resolve带回来的参数})
3、resolve
Promise.resolve(100).then(function(num){ console.log(num); //10})//上面那句话的作用就等同于下面两句话var p=new Promise(function(resolve,reject){ resolve(200);})p.then(function(num){ console.log(num);})
4、reject
Promise.reject("错误1").catch(function(msg){ console.log(msg);});//等同于var p=new Promise(function(resolve,reject){ reject("错误2");})p.catch(function(msg){ console.log(msg);})
为什么能这样简化呢,注意一点:当初始化Promise时没有设置异步过程的话,是没有异步加载时间的;但其实呢,Promise中写在then、catch函数里的都是异步过程。
// 当使用Promise时,写在then、catch函数里的都是异步过程console.log("aaa");//同步let p = new Promise(function (resolve, reject) { console.log("ccc");//同步 resolve(10);})p.then(function (num) { console.log("num=" + num); // 异步,没有onload,本身也是异步过程})console.log("bbb"); //同步
执行结果:
所以,能将代码简化:console.log("aaa");//简化的过程Promise.resolve(10).then(function(num){ console.log("num=" + num); //异步})console.log("ccc");
执行结果:
async和await
1、async函数常见写法
async function abc(){ console.log("aaa");} abc();class Box{ async abc(){ }}var obj={ async abc:()=>{ }}
2、async函数用法
async函数执行以后会自动返回一个Promise对象。 async函数中return的结果需要通过这个函数返回的promise对象中的then里面的函数参数获取。async function abc(){ return 10;}var s=abc(); //将返回的Promise对象给sconsole.log(s); //Promises.then(function(value){ //then方法可以获取async函数返回的值 console.log(value); //10})//可以简化为async function abc(){ return 10;}abc().then(function(value){ //then方法可以获取async函数返回的值 console.log(value); //10})//就与下面实现的功能一样Promise.resolve(10).then(function(num){ console.log(num); })结果都是10。
3、async与await
await关键词可以在async中使用,但是await只能用于promise对象的前面。 async、 await配合可以让Promise完成阻塞式同步的作用。async function abc(){ console.log("aaa"); await Promise.resolve().then(function(){ //完成了阻塞式同步 console.log("bbb"); }) console.log("ccc");}abc();查看结果为:aaa bbb ccc
小小的总结以下:
async 函数执行后返回一个Promise对象; async 函数中的return结果需要使用then方法中函数的参数获取; await必须写在async函数中,await的作用是阻塞式同步,也就是执行到await时,做等待完成后,再继续向后执行; await后面必须是Promise。4、来个小栗子,依然是图片的预加载,看以下阻塞式同步的用法。
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("地址错误"); } })}async function abc(){ let arr=[]; for(let i=3;i<80;i++){ // 因为await 是一个阻塞式同步,因此加载完一张图片后,才循环到下一次 await loadImage(`./img/${ i}-.jpg`).then(function(img){ arr.push(img); }) } // console.log(arr); // return arr; arr.forEach(item => console.log(item.src));}abc();// var p=abc();// p.then(function(list){ // console.log(list);// });// console.log(p);
下面这种写法有点儿类似于上面async、await写法。
实际上,在没有async、await时,就使用下面这种方法,来完成阻塞式同步。但现在这么写的人不多。 生成器函数,yield可以形成断点,使程序停下来。function* fn() { for (let i = 3; i < 80; i++) { yield loadImage(`./img/${ i}-.jpg`); //loadImage看上面程序的 }}var f = fn();for (let item of f) { // console.log(item); // item是每次返回的promise结果 if (item.constructor !== Promise) break; item.then(function (img) { console.log(img.src); })}
结果是:阻塞式同步,一个接一个加载img。
eventLoop
1、触发事件是需要时间的,是异步;但程序本身是同步的。这就有一个问题,什么是异步,前面也有提到过他的概念,不过没有提到事件触发。异步:不是同步的都是异步。
document.addEventListener("nihao",nihaoHandler);console.log("aaa");document.dispatchEvent(new Event("nihao"));console.log("bbb");function nihaoHandler(){ console.log("ccc");}结果:aaa ccc bbb
2、函数式编程也是同步操作,不过执行顺序不同。
function fn1(fn){ console.log("aaa");}function fn2(fn){ fn(); console.log("bbb");}function fn3(){ console.log("ccc");}fn1(fn2(fn3));结果:ccc bbb aaa
3、以上两种情况都是同步过程。
异步操作有微任务,宏任务之分。微任务是将当前内容放在当前任务列的最底端,宏认为是将当前内容放在下一个任务列的最顶端。 微任务有: promise async await;宏任务有: setTimeout setInterval 。console.log("aaa"); //同步setTimeout(function(){ console.log("bbb"); //异步 宏任务},0);Promise.resolve().then(function(){ console.log("ccc"); //异步 微任务})console.log("ddd"); //同步结果是:aaa ddd ccc bbb
分析:aaa 、 ddd都是同步;setTimeout是异步宏任务,Promise是异步微任务。
画个图来理解下: setTimeout和Promise换下顺序,执行结果也一样: 如果只有俩定时器的话: 注意:如果定时器设置了时间,也是宏任务,则会在时间到了后,将计时器的内容放到下一个任务列表最顶端。4、来个小栗子把同步、异步的执行顺序练习一下:(面试可能性很大哦)
// 宏任务中的微任务先执行;微任务中的宏任务后执行document.addEventListener("chilema",chileMaHandler);console.log(1); //1function fn1(){ fn2(); console.log(2); //4}function fn2(){ console.log(3); //3}console.log(4); //2function chileMaHandler(){ fn1(); console.log(5); //5}document.dispatchEvent(new Event("chilema"));new Promise(function(resolve,reject){ console.log(6); //6 setTimeout(function(){ console.log(7); //10 resolve(); },0);}).then(function(){ console.log(8); //11}).then(function(){ console.log(9); //12})setTimeout(function(){ console.log(10); //13 Promise.resolve().then(function(){ console.log(11); //14 })},0);new Promise(function(resolve,reject){ console.log(12); //7 setTimeout(function(){ console.log(13); //15 },0); resolve();}).then(function(){ setTimeout(function(){ console.log(14); //16 }); Promise.resolve().then(function(){ console.log(15); //9 }); console.log(16); //8})结果:1 4 3 2 5 6 12 16 15 7 8 9 10 11 13 14
转载地址:https://blog.csdn.net/weixin_43297321/article/details/104247166 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!