文章目录
- 前言
- 一、Promise
- 1.1、介绍Promise(认识异步)
- 1.2、Promise的基本使用
- 1.2.1、认识Promise的三个状态
- 1.2.2、Promise中回调函数原理(参数为函数缘由)
- 1.2.3、Promise的then()方法
- 解决实际问题:回调地狱(解决过多函数回调相互嵌套问题)
- 1.3、Promise对象的catch()方法
- 1.3.1、替换then()中的第二个回调函数
- 1.3.2、catch()捕捉到之后的then()状态
- 1.3.3、捕捉异常另一种方式(推荐):throw+catch()
- 1.4、Promise对象的finally()使用
- 1.5、Promise.resolve()与Promise.reject()
- 1.5.1、介绍两种方法以及Promise实例作为参数的情况(两种)
- 1.5.2、在两个方法中传入具有then()方法的对象
- 1.6、Promise.all():与传入的对象状态相关
- 1.7、Promise.race()和Promise.allSettled()
- 1.8、Promise的注意事项
- 实际应用:异步加载图片
- 二、Class类
- 2.1、Class的基本使用
- 2.1.1、构造函数
- 2.1.2、函数定义(两种情况)
- 2.2.2、Class的两种定义形式(声明与表达形式)
- 2.2、class中的实例属性
- 2.3、静态方法与静态属性
- 2.3.1、静态方法与静态属性的基本定义
- 2.3.2、this对象介绍(静态方法、构造函数与普通函数)
- 2.4、私有属性与方法
- 2.5、extends继承
- 2.5.1、初识extends继承(三个结论)
- 2.5.2、继承后子类调用父类普通方法或构造函数中的的this
- 2.5.3、重写(覆盖)父类的属性与方法
- 2.6、super用途(对象、函数)
- 2.6.1、作为函数调用
- 2.6.2、作为对象使用
- 注意事项
- 2.5.3、重写(覆盖)父类的属性与方法
- 2.6、super用途(对象、函数)
- 2.6.1、作为函数调用
- 2.6.2、作为对象使用
- 注意事项
前言
本篇博客是关于javascript的ES6中Promise以及Class,若文章中出现相关问题,请指出!
所有博客文件目录索引:博客目录索引(持续更新)
一、Promise
1.1、介绍Promise(认识异步)
Promise
是异步操作的解决方案。其也可以用来解决过多函数回调相互嵌套问题。
同步,即为等待结果的操作。
异步,即为不等待结果的操作。在js中的异步有:onXXX()事件,setInterval()(定时器、延时器)、ajax发送的请求、本章学习的Promise.then()方法还有一些回调函数都是异步的操作!
异步操作示例
<script>
function fun() {
//函数中设置一个延时器,需要传入一个回调函数,该函数就是一个异步方法
setTimeout(() => {
console.log("执行的异步方法!");
}, 2000);
}
//测试异步回调方法与同步方法的区别!
fun();
console.log("执行的同步方法!");
</script>
说明:从程序过程来看明明是先执行的fun()
函数,结果却是先打印了11行的输出语句,而后延时器结束再进行输出的结果!
1.2、Promise的基本使用
1.2.1、认识Promise的三个状态
三个状态包含:pending
(未完成)、fulfilled
(已成功)、rejected
(已失败)。
三个状态的意义何在?
- 对于已成功或已失败状态肯定后面会跟着对应的执行方法来进行对应状态处理,这就与之后的
then()
方法有关系了!
示例:一切从Promise
构造函数开始,默认状态就是pending
(未完成),之后通过调用执行回调函数参数方法来进行改变状态。
<script>
//使用构造函数,需要写一个回调函数,可获取到两个参数,其默认就是js传给你的两个方法你可以进行调用并执行
//情况1:在回调函数中什么也不执行,默认就是未执行状态:pending
let p = new Promise((resolve, reject) => {
});
console.log(p);//Promise { <state>: "pending" }
//情况2:执行resolve(),改变状态为已完成
p = new Promise((resolve, reject) => {
resolve();//执行该方法改变状态为已完成
});
console.log(p);//Promise { <state>: "fulfilled", <value>: undefined }
//情况3:执行reject(),改变状态为已失败
//注意:此时若是执行已失败方法时,需要配合then()方法中的第二个回调函数或者catch()方法进行捕获操作,否则会报出异常,这里我们暂且忽略
p = new Promise((resolve, reject) => {
reject();//执行该方法改变状态为已失败
});
console.log(p);//Promise { <state>: "rejected", <reason>: undefined }
</script>
- 在
fulfilled
(已成功)、rejected
(已失败)状态的Promise
对象输出中,你可以很明显的看到有第二个参数值对于成功的是value
(需要传递的值),对于失败的是reason
(失败原因),这同样也与之后对应的回调函数相关。
注意点:对于resolve()
、reject()
方法,一旦执行了其中的一个方法改变状态了,该回调函数中后面再调用改变状态的方法就无效了!
1.2.2、Promise中回调函数原理(参数为函数缘由)
在1.2.1中,我们在Promise
的构造函数中添加一个回调函数并可以添加两个参数如下:
<script>
new Promise((resolve, reject) => {
//默认是两个函数
console.log(resolve, reject);
});
</script>
重点是这个回调函数中的两个参数都是函数实例,并且都是可以进行调用的!那么它究竟是怎么样定义,才能够让我们来写这个回调函数时能够接收参数并进行调用呢?
自定义一个普通的回调函数
<script>
//自定义回调函数实现
function myFun(fn) {
//模拟回调函数前的操作
console.log("回调函数前执行的代码.......");
fn = fn || function () { };
fn();//执行回调函数
}
myFun(() => {
console.log("我是回调函数!")
});
</script>
模拟Promise构造函数
对上面的普通回调函数稍作修改即可!如下:
<script>
//模仿测试代码缩写
// new Promise((resolve, reject) => {
// //默认是两个函数
// console.log(resolve, reject);
// });
//模拟Promise对象的两个改变状态方法
function resolve() { };
function reject() { };
//自定义回调函数实现
function myPromise(fn) {
//执行回调函数,并传入指定的两个函数
fn(resolve, reject);
}
//模拟new Promise构造器
new myPromise((resolve, reject) => {
console.log(resolve, reject);
});
</script>
1.2.3、Promise的then()方法
then()方法初次测试
通过构造函数获得的对象调用then()
方法,需要你编写两个回调函数(回调函数根据你前面的状态来进行调用),每个回调函数你可以添加参数,对应的参数实际上就是前面我提到的value
与reason
两个值:
<script>
let promise = new Promise((resolve, reject) => {
// reject("失败原因:出现异常!");//状态为rejected,表示已失败
resolve("成功的data(数据)");//状态为fulfilled,表示已成功
});
//根据promise的状态来执行指定函数
//状态为pending(未完成):then后一个也不执行
//状态为fulfilled(已成功):执行第一个回调函数,可接收执resolve()方法传入的参数
//状态为rejected(已失败):执行第二个回调函数,可接收执reject()方法传入的参数
promise.then((data) => {
console.log("success", data);
}, (data) => {
console.log("error", data);
});
console.log(promise);
</script>
我们来测试一下上面的程序(测试两次,两种情况执行):
then()知识点说明
1、何时执行?只有当Promise
对象的状态从未执行状态改变才会执行!
2、then()执行后的返回值:执行then()
后的返回值是一个新的Promise
对象。
<script>
//第一个Promise对象:手动new实例
let p1 = new Promise((resolve, reject) => {
resolve("成功的data(数据)");//状态为fulfilled,表示已成功
});
//第二个Promise对象,执行了then()方法得到的实例
let p2 = p1.then(() => { }, () => { });
console.log(p1, p2, p1 == p2)
</script>
3、then()
执行后得到一个新的Promise
对象p,此时如何决定之后使用p调用then()
方法执行的对应回调函数呢?
答案:根据return "xxx"
或return new Promise((xxx,xxx)=>{xxx})
;来进行指定状态。
情况1:无任何返回值时
该对象没有调用then()时,状态为pending。
<script>
let p1 = new Promise((resolve, reject) => {
resolve("成功的data(数据)");//状态为fulfilled,表示已成功
});
//本部分来探讨此时通过then()获取到的promise实例如何决定之后再次调用then()中的执行回调函数
//答案:通过return来确定,若是没有return操作默认就是pending(未执行)状态,但若p2调用了then()就会默认执行成功的回调函数!
let p2 = p1.then(() => { }, () => { });
console.log(p2);
</script>
若是该对象调用了then(),状态也会自动改变为pending(已完成),执行第一个回调函数
<script>
let p1 = new Promise((resolve, reject) => {
resolve("成功的data(数据)");//状态为fulfilled,表示已成功
});
//本部分来探讨此时通过then()获取到的promise实例如何决定之后再次调用then()中的执行回调函数
//答案:通过return来确定,若是没有return操作默认就是pending(未执行)状态,但若p2调用了then()就会默认执行成功的回调函数!
let p2 = p1.then(() => { }, () => { });
console.log(p2);
p2.then(() => console.log("p2-success"), () => console.log("p2-reason"));
</script>
情况2:若是有返回值,直接return ""
字符串形式就是表示改变状态为成功;若想指定为已失败,需要手动返回一个新的Promise
对象如new Promise((resolve,reject)=>{reject(xxx);})
本质:return ""实际上就本质就是return new Promise((resolve)=>{resolve();})
说明:若是我们想直接执行then()后的已完成的回调函数,直接返回字符串就好(比较方便),对了这个字符串就是我们想要传的值。
测试then()
后执行的是已完成的函数:使用的是简写形式
<script>
let p1 = new Promise((resolve, reject) => {
resolve("成功的data(数据)");//状态为fulfilled,表示已成功
});
//本部分来探讨此时通过then()获取到的promise实例如何决定之后再次调用then()中的执行回调函数
let p2 = p1.then(() => {
return "成功啦!";//实际上就等同于return new Promise((resolve)=>{resolve("成功啦!");})
}, () => { });
console.log(p2);
p2.then((data) => console.log(data), (reason) => console.log(reason));
</script>
测试then()
后执行的是未完成的函数:使用的是完整形式
简而言之:若是你在then()方法中返回了一个值,可以再次编写调用then()方法来接收该返回值。使用该种方法可以进行解构,模块化处理指定的事情。
const p = new Promise(resolve=>{
resolve("hello");
});
p.then(res=>{
return res;
}).then(res=>{
console.log(res);
});
解决实际问题:回调地狱(解决过多函数回调相互嵌套问题)
需求:让一个盒子进行正方形移动,需要我们编写一个函数来进行移动。
解决方案1:通过多次回调执行,构成回调地狱(不利于代码删减)
<style>
* {
margin: 0;
padding: 0;
}
div {
height: 100px;
width: 100px;
border: 1px solid #000;
background-color: red;
/* 设置过渡 */
transition: all linear .5s;
}
</style>
<body>
<!-- promise实际应用 -->
<div id="box"></div>
<script>
let num = 1;
//创建一个运动函数.参数1:指定的dom元素;参数2:x,y坐标存储在对象中;参数3:下一步要做的事情
const move = (el, { x = 0, y = 0 } = {}, next = () => { }) => {
el.style.transform = `translate3d(${x}px,${y}px,0)`;
el.remove
//添加一个监听器,监听过渡结束之后事件
el.addEventListener('transitionend', () => {
// console.log(num++);
next();//执行下一个操作
}, false);
}
//方式一:层层嵌套,形成回调地狱(不推荐)
movBox.addEventListener('click', () => {
move(movBox, { x: 150 }, () => {
move(movBox, { x: 150, y: 150 }, () => {
move(movBox, { y: 150 }, () => {
move(movBox, {}, () => {
})
})
})
});
}, false);
</script>
</body>
问题描述:若是有大量的移动过程呢,就会产生大量的嵌套,若是有一些需求修改情况就会很麻烦,一旦出现下面层层嵌套情况:
我们应当使用promise来进行解决!!!
方案2:通过使用promise对象来解决回调地狱
下面的代码替换掉之前的的方式一即可:此时我们看到不再是嵌套的情况,而是多个方法纵向执行
//方式二:借助promise来进行解决过多函数回调
//第一步:封装promise,需要传入一个el(dom对象)以及对象包含x,y的数字
const movePromise = (el, point) => {
//返回一个封装好的promise对象
return new Promise(reslove => {
//其中move()方法的下一步操作(这里设置reslove())就是promise对象的then的第一个回调函数
move(el, point, () => {
reslove();
})
});
};
//第二步:开始写执行操作(通过then形成一个执行链条进行纵向执行,而不是进行嵌套回调)
movBox.addEventListener('click', () => {
movePromise(movBox, { x: 150 })
.then(() => {
return movePromise(movBox, { x: 150, y: 150 });
})
.then(() => {
return movePromise(movBox, { y: 150 });
})
.then(() => {
return movePromise(movBox, {});
});
}, false);
使用之后的效果与之前的一致,并且若是想要修改位置或者说是添加业务代码等都更加的方便了!
1.3、Promise对象的catch()方法
1.3.1、替换then()中的第二个回调函数
catch()
:是专门用来处理rejected
(未完成)状态的!用来捕获失败的事件。
我们原本对于rejected
状态通过是通过设置then()
方法中的第二个回调函数来进行对应的,实际开发中对于未完成的我们都是通过使用catch()
方法来进行处理的,而不是写在第二个参数的!
原始写法
说明:为了更方便分清成功与失败的回调函数,我们通常不写在then()的第二个参数上!下面是原始的写法:
<script>
let p1 = new Promise((resolve, reject) => {
reject("未加载到图片!");//表示状态为已失败!
});
//原始写法:对应已失败的情况,我们设置一个回调函数到then()的第二个参数中来进行处理!
p1.then(() => { }, (reason) => {
console.log(`失败的原因:${reason}`);
});
</script>
推荐写法:后面跟上catch(),添加一个回调函数
<script>
let p1 = new Promise((resolve, reject) => {
reject("未加载到图片!");//表示状态为已失败!
});
//已失败状态传来到then()中首先会看其中有没有对应的处理失败回调函数,没有的话继续向下抛出
//合理写法:使用catch()来进行捕获失败状态,并且执行一些对应的操作
p1.then().catch((reason) => { //可设置参数来获取到传来的原因
console.log(`失败的原因:${reason}`);
});
</script>
1.3.2、catch()捕捉到之后的then()状态
catch()
捕捉好之后,若是下面依旧有then()
会若无return或**直接return ‘字符串’**继续向下传递到success
的回调函数中。若是想要从catch()
那传到下面的错误回调函数中,就需要手动进行返回Promise()
对象:new Promise((resolve,reject)=>{ reject();})
:
无返回值或返回值为字符串情况:传递到success回调函数
<script>
let p1 = new Promise((resolve, reject) => {
reject("未加载到图片!");//表示状态为已失败!
});
p1.catch((reason) => {
console.log(`失败的原因:${reason}`);
//若是无返回值的话,默认将undefined作为参数传递,相当于执行了return undefined;
//若是有返回字符串的,则将该字符串作为参数传递到下面then()的success回调函数中
}).then((data) => {
console.log(`catch之后的success函数,传递参数为:${data}`);
});
</script>
catch()想要传递到之后then()的对应错误回调函数(相当于出错情况)
<script>
let p1 = new Promise((resolve, reject) => {
reject("未加载到图片!");//表示状态为已失败!
});
p1.catch((reason) => {
console.log(`失败的原因:${reason}`);
//传递已失败状态对应的函数
return new Promise((resolve, reject) => { reject(); })
}).then(() => { }, (reason) => {
console.log(`catch之后的错误函数,传递参数为:${reason}`);
});
</script>
1.3.3、捕捉异常另一种方式(推荐):throw+catch()
建议:catch()
可以捕获它前面的错误,一般总是建议在Promise
对象后跟着catch()
方法,这样可以处理Promise
内部发生的错误!
- catch()不仅仅能够捕捉自己抛出的异常,还能够捕捉到其他异常情况!
其实对于捕捉异常,最好的方式就是使用throw
来抛出异常,接着使用catch()
来进行捕捉异常最合适!
<script>
let p1 = new Promise((resolve, reject) => {
resolve("数据");//表示状态为已失败!
});
p1.then((data) => {
console.log(data);
//抛出异常,让下面的catch()进行处理
throw new Error('失败原因xxxxx');
}).catch((error) => {
console.log(error);
});
</script>
1.4、Promise对象的finally()使用
执行时间:当Promise
状态发生改变时,不论如何变化都会执行,不变化不执行!
应用场景:一般放置在最后,前端很少使用,在后端中经常用来处理一些资源流的关闭!
<script>
let p1 = new Promise((resolve, reject) => {
//状态发生改变才会执行finally(),否则不执行
resolve(12);
});
//参数为undefined,几乎不怎么使用
p1.finally((data) => {
console.log(data);
});
</script>
本质
finally()的本质实际上就是由then()方法构成,如下:无论是成功还是失败都会执行
<script>
let p1 = new Promise((resolve, reject) => {
//状态发生改变才会执行finally(),否则不执行
resolve(12);
});
//参数为undefined,几乎不怎么使用
// p1.finally((data) => {
// console.log(data);
// });
//模拟finally()操作
p1.then(() => {
console.log("模拟finally()操作....");
}, () => {
console.log("模拟finally()操作....");
});
</script>
1.5、Promise.resolve()与Promise.reject()
1.5.1、介绍两种方法以及Promise实例作为参数的情况(两种)
介绍两种方法
Promise.resolve()
与Promise.reject()
:是成功与失败状态的获取Promise()
对象的一种简写方式。
示例:
<script>
//两个构造函数方法的本质都是返回一个Promise对象,简化了原本使用new Promise((res,rej)=>{res();})的方式
//Promise.resolve():改变为成功状态
console.log(Promise.resolve("成功传输数据"));;
//Promise.reject():改变为失败状态
console.log(Promise.reject("失败原因xxx").catch((data) => console.log(data)));;
</script>
注意点说明:Promise对象作为参数时不同情况
当Promise
对象作为resolve()
以及reject()
参数时是否会将Promise
对象作为参数传递呢?
结论:
- 作为
resolve()
的参数:间接直接执行该promise对象后传递指定的参数,不是传递该实例! - 作为
reject()
的参数:并不会执行该promise对象,而是直接将该实例进行传递!
结论一测试:
let p = Promise.resolve("成功传输数据");
new Promise((resolve, reject) => {
resolve(p);//作为resolve()方法的参数,会直接间接执行该promise对象之后
}).then((data) => {
console.log(data);
})
结论二测试:
let p = Promise.resolve("成功传输数据");
new Promise((resolve, reject) => {
reject(p);//作为reject()方法的参数,会直接间接执行该promise对象之后
}).then((data) => {
console.log(data);
}, (reason) => {
console.log(reason);
})
1.5.2、在两个方法中传入具有then()方法的对象
说明:定义一个对象并添加一个then()
函数,接着将该对象传入到resolve()
方法中,即可与new Promise()
构造函数中写回调函数方式一致,如下示例:
<script>
var hasThenObj = {
//添加一个then方法,其中执行指定改变状态方法(与new Promise()中的回调函数使用方法一致)
then(resolve, reject) {
// resolve("成功执行的传递数据!");
reject("失败的原因xxx");
}
};
//将具有then()方法的对象传入依旧可进行使用!
Promise.resolve(hasThenObj).then((data) => {
console.log(data);
}, (data) => {
console.log(data);
})
</script>
1.6、Promise.all():与传入的对象状态相关
Promise.all()
的状态变化与所有传入的Promise
实例对象状态有关!
- 都不改变状态情况(无任何返回值),默认将undefined作为传递值改变状态为成功。
- 所有的状态都变成
resolved
,最终的状态才会变成resolved
,才会去调用生成的Promise实例的成功回调函数! - 只要有一个
Promise
实例变成rejected
,最终的状态就会变成rejected
!(另一个Promise实例会继续执行,而并不是说最终状态得到了就不执行了)
实际用途:应用与前端向后端发送ajax,如同时获取多个数据,只有同时都获取到了才将数据显示出来,如果没有获取到呢就再进行处理。
示例:
<script>
//自定义封装Promise
const delay = ms => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, ms);
});
};
//设置两个p2
const p1 = delay(1000).then(() => {
console.log("p1-success!");
// return "p11";
// return Promise.reject("p11");
});
const p2 = delay(2000).then(() => {
console.log("p2-success!");
// return "p22";
});
//执行Promise.all()
//情况1:两个Promise实例都已成功后,将两个传递参数封装到数组中作为p实例调用then()第一个回调函数的参数
//情况2:只要一个Promise实例为rejected,就会立刻执行p中的then()方法,即第二个回调函数
//情况3:若是两个实例都没有返回值或改变状态方法,默认会调用p实例中的第一个回调函数,传值为两个undefined
const p = Promise.all([p1, p2]);
p.then((data) => {
console.log(data);
}, (error) => {
console.log(error);
})
</script>
1.7、Promise.race()和Promise.allSettled()
Promise.race()
Promise.race()
:其状态取决于第一个完成的Promise实例对象,如果第一个完成的成功了,那最终就是成功(得到第一个成功返回的参数);如果第一个完成的失败了,那么最终就是失败!(得到的参数也是取决于第一个完成的)
示例:
<script>
//自定义封装Promise
const delay = ms => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, ms);
});
};
//设置两个p2
const p1 = delay(1000).then(() => {
// return "p11-success";
return Promise.reject("p11-error");
});
const p2 = delay(2000).then(() => {
console.log("p2-success!");
// return "p22";
});
//执行Promise.race():
const p = Promise.race([p1, p2]);
p.then((data) => {
console.log(data);
}, (error) => {
console.log(error);
})
</script>
Promise.allSettled()
Promise.allSetted()
:该方法的状态与传入的Promise
状态无关,它们永远都是成功的,并且会记录下各个Promise
的表现,以对象键值对形式存放在一个数组中。也就是说它不可能会有失败情况!
示例:
<script>
//自定义封装Promise
const delay = ms => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, ms);
});
};
//设置两个p2
const p1 = delay(1000).then(() => {
return Promise.reject("p11-error");
});
const p2 = delay(2000).then(() => {
return "p22-succes";
});
//执行Promise.allSettled():获取所有传入的promise实例的最终状态(存储的是Promise实例到数组中)
const p = Promise.allSettled([p1, p2]);
p.then((data) => {
console.log(data);
}, (error) => {
console.log(error);
})
</script>
1.8、Promise的注意事项
1、resolve()与reject()后的代码也会执行。
<script>
new Promise((resolve, reject) => {
resolve("传递数据xxx");
//改变状态的方法下面代码依旧会执行
console.log(111111);
}).then((data) => console.log(data));
</script>
2、Promise.all()
、race()
、allSettled()
的参数如果不是Promise
数组,会将不是Promise
的数组转为成Promise
对象!
- 不只是数组,任何可遍历的都可以作为参数如数组、字符串、Set、Map、NodeList、arguments…
下面第2行就是传入非Promise
数组,实质你可以看下下面就是自动帮你封装为Promise.resolve()
:
<script>
Promise.all([1, 2, 3]).then((data) => console.log(data));
//上面传入的是非Promise实例,实质可以转为如下:内部自动会帮你进行封装执行改变为已成功!
Promise.all([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
]).then((data) => console.log(data))
</script>
3、Promise.all()
、race()
、allSettled()
的错误处理:既可以单独处理也可以统一处理!
针对于all如何进行批量处理错误?
- 直接在对应的某个promise上添加catch处理。自己将错误进行处理。
- 直接在all返回的promise上进行处理,达到统一处理的效果。
单独处理形式:直接让对应的Promise实例自己处理,处理好之后不会再向下传
<script>
//自定义封装Promise
const delay = ms => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, ms);
});
};
//情况1:两个Promise实例自定义异常处理!!!
const p1 = delay(1000).then(() => {
// return "p11";
return Promise.reject("p11的错误原因!");
}).catch((error) => {
console.log(`p1自己处理:${error}`);
});
const p2 = delay(2000).then(() => {
return Promise.reject("p22的错误原因!");
}).catch((error) => {
console.log(`p2自己处理:${error}`);
});;
const p = Promise.all([p1, p2]);
p.then((data) => {
console.log(data);
}, (error) => {
console.log(error);
}).catch(error => {
console.log("统一处理错误....")
});
</script>
统一处理:统一交由all()或其他返回的Promise对象来处理
<script>
//自定义封装Promise
const delay = ms => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, ms);
});
};
//情况1:两个Promise实例自定义异常处理!!!
const p1 = delay(1000).then(() => {
// return "p11";
return Promise.reject("p11的错误原因!");
});
const p2 = delay(2000).then(() => {
return Promise.reject("p22的错误原因!");
});
const p = Promise.all([p1, p2]);
p.then((data) => {
console.log(data);
}).catch(error => {
console.log("统一处理错误....")
});
</script>
实际应用:异步加载图片
Promise应用:进行异步加载,当停留在当前页面时,利用这个空隙即浏览的时间,将其他的资源提前加载!之后访问指定的资源就减少时间,体验更好!
知识说明
当我们new Imgae
时就相当于创建了一个图片Dom
元素,在构造函数中你可以设置width
,height
,重要的来的就是当你对该实例的src
添加请求路径时,浏览器就会发送一个请求得到资源!
<script>
const img = new Image(300, 100);
img.src = 'https://img0.baidu.com/it/u=2151136234,3513236673&fm=26&fmt=auto&gp=0.jpg';
console.log(img);
</script>
实现异步加载图片
想要实现异步加载图片,那么我们就需要使用到Promise
了,其就是异步方法,我们将Image
实例化以及设置src
属性的操作放置在构造函数的回调函数里进行!
在加载页面的过程中,实际上就已经将对应的图片资源请求到了,当我们点击页面中的图片时,就能够实现快速响应的效果!!!
<style>
* {
margin: 0;
padding: 0;
}
img {
width: 300px;
height: auto;
margin: 10px;
}
</style>
<body>
<img id="myimg" src="./css/images/01_1.jpg" alt="">
<script>
//异步加载图片
const loadImgAsync = url => {
return new Promise((resolve, reject) => {
//实例化一个图片
const img = new Image();
//绑定加载完毕事件(在设置src路径得到响应码之后来决定是否加载成功或失败)
img.onload = () => {
resolve(img);
};
//出现错误
img.onerror = () => {
reject(new Error(`Could not load image at ${url}`));
}
//设置url,资源路径(设置的同时发出请求进行响应)
img.src = url;
})
};
//修改页面中图片的标签
const modifyPic = (img_dom, targetUrl) => {
img_dom.src = targetUrl;
}
//获取到原本图片的Dom元素
var m_img = document.getElementById("myimg");
//加载好图片之后(自动做的异步操作),点击原本图片进行切换
loadImgAsync('https://img0.baidu.com/it/u=2151136234,3513236673&fm=26&fmt=auto&gp=0.jpg')
.then((img) => {
console.log(img);
//绑定点击事件,点击进行修改操作
m_img.onclick = () => modifyPic(m_img, img.src);
}).catch((error) => {
console.log("处理失败原因!", error);
});
</script>
</body>
对于若是加载失败情况:若是没有加载到的可能会再加载一遍或者说是从别的地址重新进行加载,根据项目组规定来进行实施!
二、Class类
2.1、Class的基本使用
2.1.1、构造函数
在ES6
之前,我们是通过new
构造函数来创建的指定对象,为了更好的区分类与函数,ES6
推出了类相关的关键字如class
。
重要几点内容:
- class表示声明一个类,其底层实际上就是一个函数。
- 定义了一个class后,默认会自带一个无参构造器,若是自定义构造函数(使用
constructor
),就会覆盖掉默认的无参构造函数,之后使用new都会走该构造函数。 - 在构造函数中使用
this.属性
是为对应生成的对象添加的实例属性,而不是该类自带的属性。
构造函数示例:
<script>
class Person {
//默认自带了一个无参的构造函数
//下面是自定义的构造函数
constructor(name, sex) {
this.name = name;
this.sex = sex;
}
}
//查看一下Person类状态
console.log(Person);
//Person的本质:就是一个函数
console.log(typeof Person);;
//构造函数
console.log(new Person());;
console.log(new Person('changlu', '男'));;
</script>
2.1.2、函数定义(两种情况)
构造函数中使用this.函数形式(不推荐,每次创建实例时对应的speak函数都会指向不同的内存地址,造成内存浪费)
<script>
class Person {
constructor() {
this.speak = () => { console.log("hello"); }
}
}
//创建的多个实例都是引用的一个函数
console.log(new Person().speak === new Person().speak);
console.log(Person);
</script>
类中义定义函数(推荐,本质在对象原型链中添加函数)
<script>
class Person {
//类中创建函数
speak() {
console.log("Person类的speak()方法.....");
}
}
//创建的多个实例都是引用的一个函数
console.log(new Person().speak === new Person().speak);
console.log(Person);
</script>
-
speak()
函数方法被添加到了Person
的原型对象中去了。这实际上与之前执行Person.prototype.speak = ()=>{};
效果一致。
2.2.2、Class的两种定义形式(声明与表达形式)
声明形式
<script>
//声明形式
class Person {
constructor() { }
}
console.log(new Person());
</script>
表达式形式
实际与快速执行函数基本类似,如下:能够快速定义类并且创建类实例
<script>
//表达式形式
console.log(
//new (class xxx{})()
new (class Person {
constructor() { }
})()
);
</script>
2.2、class中的实例属性
认识class类中的实例属性
什么是实例属性呢?
- 之前我们在构造器中使用
this.属性
进行赋值实际上就是给生成的对象创建并赋值属性,这里的实例属性也是指的这个效果。
<script>
class Person {
//实例属性:不能使用任何关键字来进行定义如let、var、const等等
//本质就是会执行this.name='xxx';this.age=0;
name = 'xxx';
age = 0;
}
//查看下这个Person类:在该类中并没有实例属性!
console.log(Person);
//创建好实例后就会得到对应的实例属性
console.log(new Person().name)
console.log(new Person().age)
</script>
结论:直接在类中创建的属性就是实例属性!
构造器中如何赋值实例属性
<script>
class Person {
//实例属性 本质就是会执行this.name='xxx';this.age=0;
name = 'xxx';
age = 0;
constructor(name, age, sex) {
//根据参数传递情况来是否对实例属性进行赋值
if (sex !== undefined) {
this.name = name;
this.age = age;
this.sex = sex;
} else if (age !== undefined) {
this.name = name;
this.age = age;
}
}
}
console.log(new Person());
console.log(new Person('changlu', 18, '男'));
</script>
2.3、静态方法与静态属性
2.3.1、静态方法与静态属性的基本定义
静态方法
定义位置与方式:直接在类中定义static
开头的函数名即可。
本质:就是在下面Perosn
构造函数上添加了一个方法,不会出现在原型链中!
- 相当于之前执行
Person.xxx = ()=>{}
,直接添加在构造函数上!
示例:
<script>
class Person {
//静态方法:添加到Person构造函数上去的
static speak() {
console.log("i am Person!")
}
}
console.log(Person);
//实例化对象的原型链中没有对应静态方法
console.log(new Person());
new Person().speak();//报异常,没有对应定义
</script>
静态属性
定义的方式很简单:直接在类中对属性添加static
即可。
说明:静态属性只有在类中有,实例中没有!
class Person {
static num = 1;
static str = "hello";
}
console.log(Person);
console.log(Person.num);
console.log(Person.str);
2.3.2、this对象介绍(静态方法、构造函数与普通函数)
静态方法中的this
结论:静态方法中的this指向的是类。
<script>
class Person {
static num = 1;
//静态方法:添加到Person构造函数上去的
static speak() {
console.log("i am Person!")
console.log('静态方法中的this对象为:', this);
//能够访问到Person类中的值
console.log(this.num);
}
}
//调用静态方法
Person.speak();
</script>
普通方法与构造函数中的this
结论:普通方法与构造函数中的this都指向了对应创建与调用的对象实例!
<script>
class Person {
constructor() {
console.log(`constructor中的this为:${this}`)
}
speak() {
console.log(`普通函数中的this为:${this}`)
}
}
//结论:原型对象链中的函数以及构造函数中的this指向了对应创建与调用的实例
new Person().speak();//执行了构造函数以及调用了函数
</script>
2.4、私有属性与方法
问题说明:对于类中你定义静态方法或者实例方法、属性,在外部浏览器都很容易受到篡改等危险操作,那么我们就需要来定义私有属性与方法来进行防范!
有两种方式来进行定义私有属性:
- 模拟私有属性与方法:通过在变量名开头添加_来俗成约定表示私有属性或方法。(此方法仅仅只是约定,并没有进行了约束!)
- 将私有属性与方法移除类:需要搭配立即调用函数表达式
()()
来进行移出私有属性或方法!
方式一:模拟私有属性与方法,该方式还是不太可靠的
<script>
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}
//通过普通函数来获得
getName() {
return this._name;
}
getAge() {
return this._age;
}
}
//创建一个实例
const p = new Person('changlu', 18)
console.log(p);
console.log(p.getName());
console.log(p.getAge());
</script>
方式二:将属性移出类,配合立即调用函数表达式
<script>
(function () {
//将指定的属性移到外面去
let name = '';
let pw = '';
class Person {
constructor(username, password) {
//定义的私有属性必须与传入的形参名称不同,否则无法识别
name = username;
pw = password;
}
//获取属性并不是从自己本身对象中获取的,而是通过该作用域来得到!
getUsername() {
return name;
}
getPassword() {
return pw;
}
}
//绑定到window上
window.Person = Person;
})();
//通过()()形式,创建多个实例也是可以的,会形成多个局部作用域
//创建第一个实例,只能够通过调用方法才能够获取到对应的值
console.log("第一个实例:");
const p = new Person("changlu", '123456')
console.log(p);
console.log(p.getUsername());
console.log(p.getPassword());
//想要尝试直接获取是不允许的!!!
console.log(p.name);
console.log(p.pw);
//创建第二个实例,只能够通过调用方法才能够获取到对应的值
console.log("第二个实例:");
const p1 = new Person("liner", '987654')
console.log(p1);
console.log(p1.getUsername());
console.log(p1.getPassword());
//想要尝试直接获取是不允许的!!!
console.log(p1.name);
console.log(p1.pw);
</script>
2.5、extends继承
2.5.1、初识extends继承(三个结论)
结论:
- 子类的类原型对象指向着父类的类原型对象,意味通过子类的类就能够调用执行父类的静态方法!
- 子类的实例原型对象指向着父类的实例原型对象,意味着子类的实例能够调用执行父类实例的普通方法!
- 子类能够具有父类的实例属性。
示例:
<script>
class Person {
//实例变量
num = 1;
//静态变量
st_num = 2;
//静态方法
static st_speak() {
console.log("st_speak()");
}
//实例方法
speak() {
console.log("speak()");
}
}
//自定义类继承Person类
class Student extends Person {
}
//查看Studnent类的结构
console.log(Student);
const p = new Student();
//查看Student进行new的实例
console.log(p);
//调用Student实例的speak()方法
p.speak();
</script>
2.5.2、继承后子类调用父类普通方法或构造函数中的的this
结论:
-
new 子类
时,会首先去调用父类的构造函数,紧接着调用子类的构造函数。其中的this
在父类构造器中指向的就是父类实例对象,在子类构造中就是指向子类实例对象。 - 子类调用父类中的普通方法时,该方法其中的
this
指向子类对象实例。
结论一证明
<script>
class Person {
name = 'Person';
constructor() {
console.log(this, this.name);
}
}
//自定义类继承Person类
class Student extends Person {
name = 'Student';
constructor() {
super();//若是不写会报错(原因是父类的无参构造是自定义的),此时会多执行一次父类的默认的构造函数
console.log(this, this.name);
}
}
const p = new Student();
//查看Student进行new的实例
console.log(p);
</script>
结论二证明
<script>
class Person {
name = "Person";
info() {
//普通方法函数中,子类执行调用的函数this就是指向子类实例
console.log(this, this.name);
}
}
//自定义类继承Person类
class Student extends Person {
name = 'Student';
}
new Student().info();
</script>
2.5.3、重写(覆盖)父类的属性与方法
仅仅只需要在子类中声明定义父类的属性与方法即可,这里就不进行演示了!
2.6、super用途(对象、函数)
2.6.1、作为函数调用
super()
:作为函数时,只能够使用于子类的构造函数中,用在其他地方就会报错!
重点:super虽然代表了父类的构造方法,但是内部的this
指向子类的实例(暂时还是持怀疑的!!!)。
示例:
<script>
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
console.log(this);
}
}
//自定义类继承Person类
class Student extends Person {
constructor(name, age, sex) {
super(name, age);
this.sex = sex;
}
}
console.log(new Student('changlu', 18, '男'));
</script>
2.6.2、作为对象使用
super.属性
:在构造方法中或一般方法中使用,super
代表父类的原型对象,通过super
调用父类的方法时,方法内部的this
依旧指向当前的子类实例。
注意:定义在父类实例上的方法与属性是无法通过super
调用得到的,但是方法是可以执行的!
<script>
class Person {
name = "Person";
getName() {
console.log(this);//子类调用时指向子类实例
return this.name;
}
}
//自定义类继承Person类
class Student extends Person {
name = "Student";
//证明父类中的方法里使用的this是子类的对象
getName() {
return super.getName();
}
}
console.log(new Student().getName());
</script>
注意事项
1、在子类构造器中使用super
前需要先执行super()
。
2、在使用super
的时候,必须显示是作为函数super()
,还是作为对象使用,否则会报错(如仅仅是是输出super
)!
整理者:长路 时间:2021.6.23
Student’;
}
new Student().info();
```
[外链图片转存中…(img-mFVe8OFz-1651988840423)]
2.5.3、重写(覆盖)父类的属性与方法
仅仅只需要在子类中声明定义父类的属性与方法即可,这里就不进行演示了!
2.6、super用途(对象、函数)
2.6.1、作为函数调用
super()
:作为函数时,只能够使用于子类的构造函数中,用在其他地方就会报错!
重点:super虽然代表了父类的构造方法,但是内部的this
指向子类的实例(暂时还是持怀疑的!!!)。
示例:
<script>
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
console.log(this);
}
}
//自定义类继承Person类
class Student extends Person {
constructor(name, age, sex) {
super(name, age);
this.sex = sex;
}
}
console.log(new Student('changlu', 18, '男'));
</script>
[外链图片转存中…(img-X9zPtbiw-1651988840423)]
2.6.2、作为对象使用
super.属性
:在构造方法中或一般方法中使用,super
代表父类的原型对象,通过super
调用父类的方法时,方法内部的this
依旧指向当前的子类实例。
注意:定义在父类实例上的方法与属性是无法通过super
调用得到的,但是方法是可以执行的!
<script>
class Person {
name = "Person";
getName() {
console.log(this);//子类调用时指向子类实例
return this.name;
}
}
//自定义类继承Person类
class Student extends Person {
name = "Student";
//证明父类中的方法里使用的this是子类的对象
getName() {
return super.getName();
}
}
console.log(new Student().getName());
</script>
[外链图片转存中…(img-evCEq4md-1651988840423)]
注意事项
1、在子类构造器中使用super
前需要先执行super()
。
2、在使用super
的时候,必须显示是作为函数super()
,还是作为对象使用,否则会报错(如仅仅是是输出super
)!
我是长路,感谢你的耐心阅读。如有问题请指出,我会积极采纳!
欢迎关注我的公众号【长路Java】,分享Java学习文章及相关资料
Q群:851968786 我们可以一起探讨学习
注明:转载可,需要附带上文章链接