原生js 同步&异步
发布日期:2022-02-08 04:20:59 浏览次数:4 分类:技术文章

本文共 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封装 版1

class 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封装 版2

class 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:简介 前后端分离&Ajax&node服务
下一篇:jsonp简介

发表评论

最新留言

很好
[***.229.124.182]2024年04月22日 01时55分09秒