发布时间:2024-02-11 12:30
1、主要用于异步计算
2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
3、可以在对象之间传递和操作promise,帮助我们处理队列
如果有大量异步操作,而异步本身是没执行顺序的,这样就会出现问题,为了满足自身业务需求,将异步嵌套达到执行时排序,但是如果大量异步需要嵌套,则会出现 “回调地狱” 的问题,头皮发麻的那种!!!
<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js\" type=\"text/javascript\"></script>
<script src=\"https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js\" type=\"text/javascript\"></script>
<script type=\"text/javascript\">
let user;
// jquery 的异步获取 github 上所有用户
$.get(\'https://api.github.com/users\', data => {
console.log(\'fetched all users\');
user = data[0].login;
// 异步取出返回的第一个用户的 repos , 此异步的执行是必须在前一个异步之后,故嵌套在其里面
$.get(`https://api.github.com/users/${user }/repos`, data => {
console.log(\'fetched all repos\');
console.log(data);
});
});
// 上面的嵌套,如果大量出现,一层层的,就会出现“回调地狱”的现象
// promise 理解就像一种承诺,无论请求是否成功,都会给你一个返回信息,基于axios解决“回调地狱”
const userPromise = axios.get(\'https://api.github.com/users\');
userPromise.then(reponse =>{ // then() 就是请求返回的监听, reponse参数返回的信息
console.log(reponse);
let username = reponse.data[0].login; // 取出用户名
return axios.get(`https://api.github.com/users/${username }/repos`); // 这里返回的同样还是一个 promise
}).then(reponse => { // 可接着对上面 return 的 peomise 接着监听
console.log(reponse.data);
}).catch(err =>{ // 异常捕获
console.error(err); // 智能打印对应的 异步请求 的错误
});
</script>
如果嫌弃引用别的框架太大,而自己只是想用一下异步的处理 promise,那么可以自定义实现,展示如下:
<script type=\"text/javascript\">
// 自定义 promise
const p = new Promise((resolve, reject) => {
// 模拟异步请求
setTimeout(() => {
/*
1、参数介绍
resolve:用来返回成功的操作数据
reject : 如果失败用来返回指定信息,Error(),具体提示报错是哪一行!
*/
//resolve(\"异步成功数据!\");
reject(Error(\"异步失败哦!\"));
}, 2000);
})
p.then(data => {
console.log(data); // 异步成功数据!
}).catch(err => {
console.log(err); //Error: 异步失败哦! at index.html:63
});
</script>
自定义使用 Promose 时,如果需要多个异步,使用函数拆解,监听其返回即可!!!
<script type=\"text/javascript\">
// 模拟后台数据中的表
const repos = [
{name: \'grit\', owner: \'mojombo\', description: \'Grit is no longer maintained\', id: 1},
{name: \'jsawesome\', owner: \'vanpelt\', description: \'Awesome JSON\' , id: 2},
{name: \' merb-core\', owner: \'wycats\', description: \'Merb Core: All you need. None you don\\\'t.\', id: 3},
];
const owners =[
{name: \'mojombo\', location: \'San Francisco\', followers: 123},
{name: \'vanpelt\', location: \'Bay Minette\' , followers: 1034},
{name: \'wycats\', location: \'Los Angeles, CA\', followers: 388}
];
// 定义函数获取
function getRepoById(id){
// 使用 promise
return new Promise((resolve, reject) =>{
// 模拟异步请求
setTimeout(() => {
const repo = repos.find(repo => repo.id === id);
// 查找到有数据就是 true
if(repo){
resolve(repo);
}else{
reject(Error(`No repo found.`));
}
}, 1000); //1秒后
});
}
// 定义函数获取
function comboundOwner(repo){
// 使用 promise
return new Promise((resolve, reject) =>{
const owner = owners.find(owner => owner.name === repo.owner);
// 查找到有数据就是 true
if(owner){
repo.owner = owner; // 将repo 中的 ower 信息补全
resolve(repo);
}else{
reject(Error(`No owner found.`));
}
});
}
// 调用函数后直接使用 then() 监听
getRepoById(1).then(repo => {
console.log(repo);
return comboundOwner(repo); // 调用函数,并返回,同 axios
}).then(repo => { // 再次监听
console.log(repo);
}).catch(err =>{
console.log(err);
});
</script>
Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有 的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);
如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝(第一个处理完的 promise)
<script type=\"text/javascript\">
const usersPromise = new Promise((resolve, reject) =>{
setTimeout(() =>{
resolve([\'mojombo\', \'vanpelt\', \'wycats\']);
}, 2000);
});
const moviePromise = new Promise((resolve, reject) =>{
setTimeout(() =>{
// 使用了一个随机函数,概率返回
if(Math.round(Math.random()*10) > 5 ){
resolve({name: \'摔跤吧,爸爸!\', rating: 9.2, year: 2016});
}else{
reject(Error(\'No movie\'));
}
}, 500);
});
Promise
.all([usersPromise, moviePromise]) // 添加两个 promise
.then(responses => { // 所有正确执行完,才会执行监听里面的
//(2) [Array(3), {…}] ,打印的是一个长度为2的数组,第一个元素是长度为3的数组,第二个元素是个对象
//console.log(responses);
// 数组解构打印
const [users, movie] = responses;
console.log(users); // [\"mojombo\", \"vanpelt\", \"wycats\"]
console.log(movie); // {name: \"摔跤吧,爸爸!\", rating: 9.2, year: 2016}
}).catch(err => { // 只要有一个 promise 失败,则立马执行 catch 块
console.log(err);
});
Promise
.race([usersPromise, moviePromise]) // 添加两个 promise
.then(responses => { // 添加的两个promise,由于其定时时间是不一样的,则第一个 Promise 执行正确返回才会执行监听里面的
console.log(`responses:${responses }`); // responses:[object Object]
}).catch(err => { // 第一个 Promise 执行完的返回失败,则立马执行 catch 块
console.log(`err:${err }`);
});
</script>
<script type=\"text/javascript\">
// 定义一个数组,循环遍历
const fruits = [\'Apple\', \'Banana\', \'Orange\', \'Mango\'];
// 为其原型对象加个值
fruits.describe = \"My favorite fruits\";
// 1、此循环比较繁琐,可读性也不是很高
for(let i=0; i<fruits.length; i++){
console.log(fruits[i]); //逐个打印
}
// 2、js 提供的数组遍历函数,简化了语法,使用 fruit 变量参数遍历即可
fruits.forEach( fruit => {
// 缺点: 不能终止或跳过,下面的break,continue 会报错
if(fruit === \'Orange\'){
//break; //SyntaxError: Illegal break statement
//continue; //SyntaxError: Illegal continue statement: no surrounding iteration statement
}
console.log(fruit); //逐个打印
});
// 3、for in 循环遍历下标
for(let index in fruits){
console.log(fruits[index]); //缺点:逐个打印,还会打印其原型对象添加的值My favorite fruits
}
// 4、推荐使用 for of,取上之精华,去之其糟粕
for(let fruit of fruits){
// 支持终止与跳过
if(fruit === \'Orange\'){
break;
//continue;
}
console.log(fruit); //逐个打印
}
</script>
补充:有些场景可能需要使用到 index 下标
es6-for…of 循环一个数组返回索引的语法:
for(let [index,elem] of new Map( arr.map( ( item, i ) => [ i, item ] ) )){
console.log(index);
console.log(elem);
}
<script type=\"text/javascript\">
var arr = [ \'a\', \'b\', \'c\' ];
// new Map() 将其每次循环返回的 key=value,构建成新的 map
for( let [i, item] of new Map( arr.map((item, i) => [i, item]) ) ){
console.log( i, item ); //0 \"a\" 1 \"b\" 2 \"c\"
}
</script>
上一节讲到 for … of 可遍历可迭代对象,可迭代对象是部署了Iterator接口,或者定义了 [ Symbol.Iterator ] 方法的数据结构,遍历器便提供了这样的一种遍历方式。
<script type=\"text/javascript\">
// 1、定义一个数组,使用遍历器循环遍历
const fruits = [\'Apple\', \'Banana\', \'Orange\', \'Mango\'];
// 接受其遍历器对象
const iterator = fruits.entries();
for(let fruit of iterator){
// 其遍历器返回的是有下标的数组,这样既可以得到值,又能获得下标
console.log(fruit); //[0, \"Apple\"], [1, \"Banana\"], [2, \"Orange\"], [3, \"Mango\"]
console.log(fruit[1]); // 每次循环获得上面数组的第二的元素,Apple Banana Orange Mango
}
// 解构语法改写
for(let [index, fruit] of fruits.entries()){
// 理想状态的遍历结果如下
console.log(`${fruit} ranks ${index + 1} in my favorite fruits`);
}
// 2、 遍历参数 arguments 对象
function sum(){
let total = 0;
// 这里是一个参数对象arguments,虽然不是数组,但可以使用 for...of 遍历
for(let num of arguments){
total = num + total;
}
console.log(total); // 612
return total;
}
sum(10,24,58,69,451);
// 3、遍历字符串
let name = \'Tom\';
for(let char of name){
console.log(char); //逐个打印 T o m
}
// 4、遍历 nodlist 类型的标签集合
// 获取网页中所有 标签
const list = document.querySelectorAll(\'li\');
for(let li of list){
// meige li 都绑定一个点击事件
li.addEventListener(\'click\', function(){
// 被点击的对象添加高亮样式
this.classList.toggle(\'highlight\');
});
}
</script>
e6 为类数组提供的新方法 Array.from() ,Array.from() 方法从一个类似数组或可迭代对象中创建一个新的。类数组对象就是拥有 length 属性,可遍历的对象。
<ul>
<li>Go to storeli>
<li>Watch TVli>
<li>Go Shoppingli>
ul>
<script type=\"text/javascript\">
// 1、 将标签元素集合对象转换为数组
const todos = document.querySelectorAll(\'li\');
console.log(todos); // f12 看其打印类型,_proto__: NodeList
// 必须要将todos转换为 数组对象,才能调用数组原型的 map() 方法
const todosArr = Array.from(todos);
const names = todosArr.map(todo => todo.textContent);
console.log(names); // [\"Go to store\", \"Watch TV\", \"Go Shopping\"]
// Array.from()的第二个参数(可选),mapFn 最后生成的数组上再执行一次 map 方法后再返回
// 将上面内容可改写成一步
const strs = Array.from(todos, todo => todo.textContent);
console.log(strs); // [\"Go to store\", \"Watch TV\", \"Go Shopping\"]
// 2、将 arguments 参数转换为数组
function sum(){
console.log(arguments); // f12 查看其类型__proto__: Object
// reduce()调用之前,必须将 arguments 转为数组
return Array.from(arguments).reduce((prev, curr) => prev + curr, 0);
}
console.log(sum(78,85,63)); //226
// 3、字符串转换为数组
const website = \"Laravist\";
console.log(Array.from(website)); //[\"L\", \"a\", \"r\", \"a\", \"v\", \"i\", \"s\", \"t\"]
script>
Array数组创建时,由于传入参数个数不一样,导致其方法的创建行为是不一样的,Array.of()能解决这样的不一致性,Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
看下图:
<script type=\"text/javascript\">
let news = new Array(7);
console.log(news); //(7) [empty × 7],长度为7,各元素为空的数组
let arr = Array.of(7);
console.log(arr); //[7],长度为1,元素是7
arr = Array.of(7, 2, 3);
console.log(arr); //[7, 2, 3]
</script>
<script type=\"text/javascript\">
// 1、 声明了一个数组对象
const inventory = [
{name: \'apples\', quantity: 2},
{name: \'bananas\', quantity: 0},
{name: \'cherries\', quantity: 5}
];
/*
1、 查找数组中满足某个条件的元素,相对于for...of 循环,使用方法更简便
find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined,找到立马return
参考:https://www.runoob.com/jsref/jsref-find.html
*/
const bananas = inventory.find( (currentValue, index, arr) => {
if(currentValue.name === \'bananas\'){
return true; // 找到 return true, bananas变量接受到当前浏览的对象
}
return false;
});
//const bananas1 = inventory.find( fruit => fruit.name === \'bananas\'); //简写形式,推荐
console.log(bananas); //{name: \"bananas\", quantity: 0}
/*
2、 同理,查询数组中某个条件的元素的下标,找到立马return
findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。
*/
const cherries = inventory.findIndex(fruit => fruit.name === \'cherries\'); //简写
console.log(cherries); //2 ,这是返回的下标
/*
3、 检测数组中是否存在至少一个元素,满足自己传入的函数检验,存在-->true, 不存在 --> false (至少一个返回true)
some() 方法检测 TypedArray 的一些元素是否通过所提供函数的测试。
*/
const isEnough = inventory.some(fruit => fruit.quantity >0 );
console.log(isEnough); // true
/*
4、 同上,检测所有元素是否满足传入函数,一个不满足马上返回 false
every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。
*/
const isAllEnough = inventory.every(fruit => fruit.quantity >0 );
console.log(isAllEnough); // false
</script>
JS函数内部有个arguments对象,可以拿到全部实参。现在ES6给我们带来了一个新的对象,可以拿到除开始参数外的参数,即剩余参数。
理解:剩余参数语法允许我们将一个不定数量的参数表示为一个数组
<script type=\"text/javascript\">
/*
1、 剩余参数,用于对参数需要计算的场景
...numbers : ...后加上自定义的参数变量名 numbers 即可
*/
function sum(...numbers){
console.log(numbers); //(4) [1, 2, 5, 4], __proto__: Array(0),原型为数组,与 arguments 区别
return numbers.reduce((prev, curr) => prev + curr, 0);
}
console.log(sum(1, 2, 5, 4)); // 12
/*
2、 剩余参数只能是函数形参的最后一个的位置,且前面可以有多个其他形参
*/
function converCurrency(rate, ...amounts){
console.log(rate, amounts); // (3) [2, 5, 4], 只接受剩下没有映射的的参数,与 arguments 区别
return amounts.map(amount => amount * rate);
}
console.log(converCurrency(0.85, 2, 5, 4)); //(3) [1.7, 4.25, 3.4]
/*
3、 应用于数组的解构
*/
const player = [\'Jelly\', 12345, 6, 8, 5, 7];
const [name, id, scores] = player; // 复习,数组结构是下标对应映射
console.log(name, id, scores); //Jelly 12345 6 , 解构时 player 后面下标位没有则没有映射传入
// 如果 scores1 解构剩余的元素,使用剩余参数
const [name1, id1, ...scores1] = player; // 复习,数组结构是下标对应映射
console.log(name1, id1, scores1); //Jelly 12345 (4) [6, 8, 5, 7]
</script>
扩展运算符是把一个可遍历的对象的每个元素,扩展成一个新的参数序列
<script type=\"text/javascript\">
/*
数组之间的整合,由浅入深,讲解
*/
const youngers = [\'George\', \'John\', \'Tom\'];
const olders = [\'Tencent\', \'Baidu\', \'Ali\'];
// 1、利用数组的 concat() 方法,将数组整合为一个新的数组,原数组不变
const numbers = youngers.concat(olders);
console.log(youngers); // [\"George\", \"John\", \"Tom\"]
console.log(numbers); // [\"George\", \"John\", \"Tom\", \"Tencent\", \"Baidu\", \"Ali\"]
// 2、如果想在整合时在 youngers 和 olders 中新加一个 marry,老套用法
let nums = []; //先定义这是个数组
nums = nums.concat(youngers);
nums.push(\"Marry\"); // 从数组尾部追加一个元素
nums = nums.concat(olders);
console.log(nums); // [\"George\", \"John\", \"Tom\", \"Marry\", \"Tencent\", \"Baidu\", \"Ali\"]
// 3、扩展运算符
const str = \'Jerry\';
const sarr = [...str]; // 将每个字符扩展成了数组元素。此处等同 [...\'Jerry\']
console.log(sarr); // [\"J\", \"e\", \"r\", \"r\", \"y\"]
// 4、利用扩展运算符整合 youngers 和 olders
let news = [...youngers, ...olders];
console.log(news); // [\"George\", \"John\", \"Tom\", \"Tencent\", \"Baidu\", \"Ali\"]
// 5、当然,新增 marry 如下:
const newS = [...youngers, \'Marry\', ...olders];
console.log(newS); // [\"George\", \"John\", \"Tom\", \"Marry\", \"Tencent\", \"Baidu\", \"Ali\"]
// 6、浅析关于数组间赋值,是对象间的地址的引用
let s1 = youngers;
s1[0] = \'Str\'; // 改变了 s1 数组的0下标元素值,打印发现 youngers 数组也发生了变化
console.log(s1); // [\"Str\", \"John\", \"Tom\"]
console.log(youngers); // [\"Str\", \"John\", \"Tom\"]
// 6.1、杜绝上面的现象,利用 concat() 生成一个新的数组
let s2 = [].concat(olders);
s2[0] = \'Change\';
console.log(s2); // [\"Change\", \"Baidu\", \"Ali\"]
console.log(olders); // [\"Tencent\", \"Baidu\", \"Ali\"]
//6.2、使用扩展运算符改写
let s3 = [...olders];
s3[0] = \'Hello\';
console.log(s3); // [\"Hello\", \"Baidu\", \"Ali\"]
console.log(olders); // [\"Tencent\", \"Baidu\", \"Ali\"]
</script>
1、 下面这个是一个结合扩展运算符,写的一个漂亮的 css 动态展示效果,里面介绍了两个有意思的 css 属性:transform、transition。
<body>
<h2 class=\"heading\">LARAVIST!h2>
<script type=\"text/javascript\">
/*
通过 js 为上方h2标签元素的内容添加样式
*/
const heading = document.querySelector(\".heading\");
// 定义函数,将字符串内容扩张成字符数组,逐一处理
function wrapWithSpan(context){
//下面这一步简写,是不是很简单,此处所涉及到的都是之前举例过的知识
return [...context].map(text => `${text}`).join(\'\');
}
console.log(wrapWithSpan(heading.textContent)); // 查看返回的数据
// 将返回的数据重新写回 h2 标签元素
heading.innerHTML = wrapWithSpan(heading.textContent);
script>
body>
2、 扩展运算符应用于标签元素,对象属性,数组元素的示例。
<ul>
<li>Go to storeli>
<li>Watch TVli>
<li>Go Shoppingli>
ul>
<script type=\"text/javascript\">
/*
1、 应用于标签元素
*/
const todos1 = document.querySelectorAll(\"li\"); //__proto__: NodeList
console.log(Array.from(todos1)); // 使用之前讲解的方法将其转换为 数组类型
// 使用扩展运算符将todos转为数组再调用map()方法
console.log([...todos1].map(todo => todo.textContent)); // [\"Go to store\", \"Watch TV\", \"Go Shopping\"]
/*
2、 扩展对象的属性
*/
const favorites = {
color: [\'white\', \'black\'],
fruit: [\'banana\', \'mongo\']
}
// 在购买的列表中把喜欢的对象的水果属性扩展进来
const shoppingList = [\'milk\', \'sweets\', ...favorites.fruit];
console.log(shoppingList); // [\"milk\", \"sweets\", \"banana\", \"mongo\"]
/*
3、 扩展数组元素, 删除 todoss 数组中id为1(下标为1)的元素
*/
let todos = [
{id: 1, name: \'go to store\', complete: false},
{id: 2, name: \'watch TV\', complete: true},
{id: 3, name: \'go shopping\', complete: false}
]
const todoIndex = todos.findIndex(todo => todo.id === 2); // 获得下标 1
/*
arrayObject.slice(start,end):可从已有的数组中返回选定的元素。
--start 必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。
--end 可选。规定从何处结束选取。如果没有指定该参数,那么切分的数组结束的所有元素。
参考地址:https://www.w3school.com.cn/jsref/jsref_slice_array.asp
*/
// 下面注释内容得到的是一个二维数组,因为 slice() 切分得到是数组,而不是对象
// const newTodos = [todos.slice(0, todoIndex), todos.slice(todoIndex + 1)]; // [Array(1), Array(1)]
const newTodos = [...todos.slice(0, todoIndex), ...todos.slice(todoIndex + 1)]; //通过扩展运算符,迭代数组中的元素(对象),得到对象
console.log(newTodos); // [{…}, {…}]
todos = newTodos;
console.log(todos); // [{…}, {…}]
script>
3、 扩展运算符应用到函数参数的传入。
<script type=\"text/javascript\">
/*
1、 扩展运算符在函数中运用,数组追加元素
*/
const fruit = [\'apple\', \'banana\', \'pear\'];
const newFruit = [\'orange\', \'mongo\'];
// 将 newFruit 添加到 fruit 中
// fruit.push(newFruit); // [\"apple\", \"banana\", \"pear\", Array(2)] ,添加的元素为数组,不是我们想要的
/*
apply:方法能劫持另外一个对象的方法,继承另外一个对象的属性
Function.apply(obj,args)方法能接收两个参数
obj:这个对象将代替Function类里this对象
args:这个是数组,它将作为参数传给Function(args-->arguments)
*/
// fruit.push.apply(fruit, newFruit); //[\"apple\", \"banana\", \"pear\", \"orange\", \"mongo\"] ,可以利用 apply() 传值
fruit.push(...newFruit); // es6 写法,简单快捷,需注意这不同于 concat(),这里更改的就是原来的数组
console.log(fruit); // [\"apple\", \"banana\", \"pear\", \"orange\", \"mongo\"],
/*
2、 日期函数传参
*/
const dateFields = [2019, 9, 12];
// const date = new Date(dateFields[0], dateFields[1], dateFields[2]); //想利用这种数组来构建是不是很繁琐
const date = new Date(...dateFields); // es6改写,再多参数也灵活传入
console.log(date); // Sat Oct 12 2019 00:00:00 GMT+0800 (中国标准时间)
</script>