原生js 同步&异步

同步、异步

代码的同步异步,标签的同步异步

代码的同步异步

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);

执行结果:
原生js 同步&异步_第1张图片
可以看出来,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是阻塞式同步加载,其他皆为异步加载。


<script src="./js/a.js">script>
<script src="./js/b.js">script>

<script src="./js/c.js" async>script>

<script src="./js/d.js" async defer>script>


举个小栗子:

main.html
<head>
	<script src="./js/a.js">script>
	<script src="./js/main.js" defer>script>
head>

<body>
	<div id="div0">div>
	<img src="./img/3-.jpg" alt="">
body>
a.js
console.log("aaa");

main.js
var div=document.getElementById("div0");
console.log(div);
var img=document.querySelector("img");
console.log(img.width);

如果加载main.js的标签script 中没加defer,就会报错,因为图片的加载时异步过程,此时图片还没有,哪来的属性width。
原生js 同步&异步_第2张图片
而如果加了defer,表示最后执行,则不会报错。
原生js 同步&异步_第3张图片

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、catch
loadImage("./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:
原生js 同步&异步_第4张图片
结果:
在这里插入图片描述
…从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对象给s
console.log(s);  //Promise
s.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是异步微任务。
画个图来理解下:
原生js 同步&异步_第5张图片
setTimeout和Promise换下顺序,执行结果也一样:
原生js 同步&异步_第6张图片
如果只有俩定时器的话:
原生js 同步&异步_第7张图片
注意:如果定时器设置了时间,也是宏任务,则会在时间到了后,将计时器的内容放到下一个任务列表最顶端。

4、来个小栗子把同步、异步的执行顺序练习一下:(面试可能性很大哦)

// 宏任务中的微任务先执行;微任务中的宏任务后执行
document.addEventListener("chilema",chileMaHandler);
console.log(1);  //1
function fn1(){
    fn2();
    console.log(2);  //4
}
function fn2(){
    console.log(3);  //3
}
console.log(4);  //2
function 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       

你可能感兴趣的:(线上,原生js,相关笔记)