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);
阻塞式同步加载,是异步变同步加载(异步–>同步)。
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。
而如果加了defer,表示最后执行,则不会报错。
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生成时,一旦执行了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
});
(注意:还不是太懂,先放放)
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);
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);
}
}
}
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");
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。
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); //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