【前端学习笔记】Javascript学习二(运算符、数组、函数)

一、运算符

运算符(operator)也被称为操作符,是用于实现赋值、比较和执行算数运算等功能的符号。
JavaScript中常用的运算符有:
算数运算符、递增和递减运算符、比较运算符、逻辑运算符、赋值运算符

算数运算符
+-*/% :加、减、乘、除、取余;
递增(++)和递减(--

比较运算符
(关系运算符)是两个数据进行比较时所使用的运算符,比较运算后,会返回一个布尔值(true / false)作为比较运算的结果。不要直接判断两个浮点数是否相等。
> < >= <= == != !== ===
其中==:判断两边的值是否相等(有隐藏数据类型转换)
其中===:判断两边的值和类型是否全相等
逻辑运算符
概念:逻辑运算符是用来进行布尔值运算的运算符,其返回值也是布尔值。
&& || ! :逻辑与 and;逻辑或 or;逻辑非 not。

短路运算的原理:当有多个表达式(值)时,左边的表达式值可以确定结果时,就不再继续运算右边的表达式的值。

赋值运算符
概念:用来把数据赋值给变量的运算符。
= += -= *= /= %= :直接赋值和 操作后再赋值。

优先级:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、流程控制

在一个程序执行的过程中,各条代码的执行顺序对程序的结果是有直接影响的。很多时候我们要通过控制代码的执行顺序来实现我们要完成的功能。
流程控制主要有三种结构,分别是顺序结构、分支结构和循环结构,这三种结构代表三种代码执行的顺序。

1 顺序流程控制
顺序结构是程序中最简单、最基本的流程控制,它没有特定的语法结构,程序会按照代码的先后顺序,依次执行,程序中大多数的代码都是这样执行的。
2 分支流程控制
由上到下执行代码的过程中,根据不同的条件,执行不同的路径代码(执行代码多选一的过程),从而得到不同的结果

  • if 语句
  • 三元表达式
  • switch 语句

switch 语句和 if else if 语句的区别

  • 一般情况下,它们两个语句可以相互替换
  • switch…case语句通常处理case为比较确定值的情况, 而if…else…语句更加灵活,常用于范围判断(大于、等于某个范围)
  • switch 语句进行条件判断后直接执行到程序的条件语句,效率更高。而if…else 语句有几种条件,就得判断多少次。
  • 当分支比较少时,if… else语句的执行效率比 switch语句高。
  • 当分支比较多时,switch语句的执行效率比较高,而且结构更清晰。

3 循环
for 循环
初始化变量:通常被用于初始化一个计数器,该表达式可以使用 var 关键字声明新的变量,这个变量帮我们来记录次数。
条件表达式:用于确定每一次循环是否能被执行。如果结果是 true 就继续循环,否则退出循环。
操作表达式:每次循环的最后都要执行的表达式。通常被用于更新或者递增计数器变量。当然,递减变量也是可以的。
双重 for 循环
while 循环
do while 循环

  • JS 中循环有 for 、while 、 do while
  • 三个循环很多情况下都可以相互替代使用
  • 如果是用来计次数,跟数字相关的,三者使用基本相同,但是我们更喜欢用 for
  • while 和 do…while 可以做更复杂的判断条件,比 for 循环灵活一些
  • while 和 do…while 执行顺序不一样,while先判断后执行,do…while 先执再判断执行
  • do…while 至少会执行一次循环体, 而 while 可能一次也不执行
  • 实际工作中,我们更常用for循环语句,它写法更简洁直观

continue 关键字
continue 关键字用于立即跳出本次循环,继续下一次循环(本次循环体中 continue之后的代码就会少执行一次)
break 关键字
break 关键字用于立即跳出整个循环(循环结束)

三、数组

JavaScript 数组是可调整大小的,并且可以包含不同的数据类型。(当不需要这些特征时,可以使用类型化数组。)
JavaScript 数组不是关联数组,必须使用非负整数(或它们各自的字符串形式)作为索引访问,index从0开始。JavaScript 语法要求使用方括号表示法而不是点号表示法来访问以数字开头的属性。
JavaScript 数组复制操作创建浅拷贝。(所有 JavaScript 对象的标准内置复制操作都会创建浅拷贝,而不是深拷贝)。

浅拷贝
对象的浅拷贝是属性与拷贝的源对象属性共享相同的引用(指向相同的底层值)的副本。因此,当你更改源对象或副本时,也可能导致另一个对象发生更改。与之相比,在深拷贝中,源对象和副本是完全独立的。

在 JavaScript 中,所有标准内置对象复制操作(扩展语法、Array.prototype.concat()Array.prototype.slice()Array.from()Object.assign())都创建浅拷贝,而不是深拷贝

如果两个对象 o1 和 o2 是浅拷贝,那么:

  • 它们不是同一个对象(o1 !== o2)。
  • o1 和 o2 的属性具有相同的名称且顺序相同。
  • 它们的属性值相等。
  • 它们的原型链相等。

深拷贝
对象的深拷贝是指其属性与其拷贝的源对象的属性不共享相同的引用(指向相同的底层值)的副本。因此,当你更改源或副本时,可以确保不会导致其他对象也发生更改。

如果两个对象 o1 和 o2 是结构等价的,那么它们的观察行为是相同的。这些行为包括:

  • o1 和 o2 的属性具有相同的名称且顺序相同。
  • 它们的属性的值是结构等价的。
  • 它们的原型链是结构等价的(尽管在处理结构等价时,这些对象通常是普通对象,意味着它们都继承自 Object.prototype)。

所以有个面试题就是:如何创建深拷贝。
1.使用 JSON.stringify() 将该对象转换为 JSON 字符串,然后使用 JSON.parse() 将该字符串转换回(全新的)JavaScript 对象
2.structuredClone()

1.创建数组

JavaScript 中有几种方法来创建数组:
使用数组字面量(推荐)
最常见的方式是使用数组字面量:

let fruits = ["apple", "banana", "cherry"];

使用 Array 构造函数
也可以使用 Array 构造函数来创建数组:

let numbers = new Array(1, 2, 3, 4);

如果只传入一个数字参数,表示数组的长度:

let arr = new Array(5); // 创建一个长度为5的空数组
console.log(arr); // [ <5 empty items> ]

使用 Array.of() 和 Array.from()
Array.of():用于创建一个新的数组实例,传入的参数会作为数组元素。

let arr = Array.of(1, 2, 3);
console.log(arr); // [1, 2, 3]

Array.from():用于将类数组对象或可迭代对象转换为数组。

let str = "hello";
let arr = Array.from(str); // 将字符串转换为字符数组
console.log(arr); // ['h', 'e', 'l', 'l', 'o']

2. 数组常用的方法

添加元素
push():在数组的末尾添加一个或多个元素。

let arr = [1, 2, 3];
arr.push(4, 5);
console.log(arr); // [1, 2, 3, 4, 5]

unshift():在数组的开头添加一个或多个元素。

let arr = [1, 2, 3];
arr.unshift(0);
console.log(arr); // [0, 1, 2, 3]

删除元素
pop():删除数组的最后一个元素。

let arr = [1, 2, 3];
arr.pop();
console.log(arr); // [1, 2]

shift():删除数组的第一个元素。

let arr = [1, 2, 3];
arr.shift();
console.log(arr); // [2, 3]

查找元素
indexOf():返回数组中首次出现的元素索引,如果没有找到返回 -1。

let arr = [10, 20, 30];
console.log(arr.indexOf(20)); // 1
console.log(arr.indexOf(40)); // -1

lastIndexOf(): 查询某个元素在数组中最后一次出现的位置 (或者理解为反向查询第一次出现的位置) 存在该元素,返回下标,不存在 返回 -1。

includes():判断数组中是否包含某个元素,返回 true 或 false。

let arr = [10, 20, 30];
console.log(arr.includes(20)); // true
console.log(arr.includes(40)); // false

遍历数组
forEach():对数组中的每个元素执行一个提供的函数,不改变原数组。函数一般是function(item,index,array){}

let arr = [1, 2, 3];
arr.forEach(function(element) {console.log(element);
});
// 输出:
// 1
// 2
// 3

map():返回一个新数组,其中包含通过提供的函数处理过的每个元素。

let arr = [1, 2, 3];
let doubled = arr.map(function(element) {return element * 2;
});
console.log(doubled); // [2, 4, 6]

过滤数组
filter():返回一个数组,其中包含满足给定条件的所有元素。

let arr = [1, 2, 3, 4, 5];
let even = arr.filter(function(element) {return element % 2 === 0;
});
console.log(even); // [2, 4]

数组排序和反转
sort():按照字符串的Unicode顺序对数组进行排序,排序时可以传入自定义的比较函数。 function(a, b) {return: a - b;} ,=> a - b > 0 那么 a 会被排列到 b 之前; (从小到大排序)
function(a, b) {return: b - a;} ,=> b - a > 0 那么b会被排列到 a 之前; (从大到小排序)

let arr = [5, 2, 8, 1];
arr.sort(); // 默认按字母顺序排序
console.log(arr); // [1, 2, 5, 8]

reverse():反转数组中的元素顺序。

let arr = [1, 2, 3];
arr.reverse();
console.log(arr); // [3, 2, 1]

数组拼接与切割
concat():用于合并两个或多个数组。

let arr1 = [1, 2];
let arr2 = [3, 4];
let arr3 = arr1.concat(arr2);
console.log(arr3); // [1, 2, 3, 4]

slice():返回数组的一个浅拷贝,可以指定起始和结束索引。

let arr = [1, 2, 3, 4];
let sliced = arr.slice(1, 3);
console.log(sliced); // [2, 3]

splice():用来向数组中添加,或从数组删除,或替换数组中的元素,然后返回被删除/替换的元素所组成的数组元素,并且可以插入新的元素。 从索引的位置删除或替换
arrayObject.splice(index,howmany,item1,…,itemX)

let arr = [1, 2, 3, 4];
arr.splice(1, 2, "a", "b");
console.log(arr); // [1, "a", "b", 4]

join():用特定的字符,将数组拼接形成字符串 (默认",")
toString():直接将数组转换为字符串,并且返回转换后的新数组,不改变原数组,与join();方法不添加任何参数 相同。
valueOf():返回数组的原始值(一般情况下其实就是数组自身)

every():遍历数组, 每次循环时执行传入的回调函数,回调函数返回一个条件,全都满足返回true 只要有一个不满足 返回false => 判断数组中所有的元素是否满足某个条件。

some():遍历数组, 每次循环时执行传入的回调函数,回调函数返回一个条件,只要有一个元素满足条件就返回true,都不满足返回false => 判断数组中是否存在,满足某个条件的元素。

reduce():遍历数组, 每次循环时执行传入的回调函数,回调函数会返回一个值,将该值作为初始值prev,传入到下一次函数中, 返回最终操作的结果;
语法: arr.reduce( function(prev,item,index,array){} , initVal)
就是对数组中每一个都处理,每次的结果是prev,然后与下一个item进行操作。

find():遍历数组 每次循环 执行回调函数,回调函数接受一个条件 返回满足条件的第一个元素,不存在则返回undefined。

fill(): 用给定值填充一个数组。value 必需。填充的值。start 可选。开始填充位置。end 可选。停止填充位置 (默认为 array.length)

flat(): 用于将嵌套的数组"拉平",变成一维的数组。该方法返回一个新数组,对原数据没有影响。
注意 默认拉平一次 如果想自定义拉平此处 需要手动传参 ,如果想全都拉平 传 Infinity。

四、函数

函数:就是封装了一段可被重复调用执行的代码块。包含声明函数和调用函数。
function是声明函数的关键字,必须小写,声明函数有两种方式:

  1. 自定义函数方式function 函数名() {...}
    • 因为有名字,所以也被称为命名函数
    • 调用函数的代码既可以放到声明函数的前面,也可以放在声明函数的后面
  2. 函数表达式方式
    • 被称为匿名函数var 变量名 = function() {...}
    • 这个 变量 里面存储的是一个函数
    • 函数调用的代码必须写到函数体后面
  3. 箭头函数
    • 箭头函数是 ES6 引入的一种简洁的函数表达方式,使用箭头符号 => 来定义。箭头函数没有自己的 this,它会从外部作用域继承 this。
    • var 变量名 = (形参) =>{ return x}

函数调用结束后使用return返回值。在使用 return 语句时,函数会停止执行,return 语句之后的代码不被执行,并返回指定的值。return 只能返回一个值。如果用逗号隔开多个值,以最后一个为准。如果函数没有 return ,返回的值是 undefined。

1.参数

在声明函数时,可以在函数名称后面的小括号中添加一些参数,这些参数被称为形参
在调用该函数时,同样也需要传递相应的参数,这些参数被称为实参
ES6 引入了默认参数的概念,如果在调用函数时没有传递某个参数,则该参数会使用定义时提供的默认值。
剩余参数是一个表示不确定数量的参数的数组,它必须放在参数列表的最后。通过剩余参数,函数可以接收任意数量的参数。

arguments的使用
当我们不确定有多少个参数传递的时候,可以用arguments来获取。在 JavaScript中,arguments 实际上它是当前函数的一个内置对象。所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有实参。

arguments 对象是一个类数组对象,包含函数调用时传入的所有实参,适用于传统函数表达式或函数声明中。注意,箭头函数没有 arguments 对象
具有length属性;按索引方式储存数据;不具有数组的push、pop等方法

2.作用域与闭包

函数有自己的作用域,这意味着函数内部的变量在外部是不可见的。JavaScript 使用词法作用域,即变量的作用域是在编写代码时确定的,而不是在执行时确定的。

闭包(Closure):是指一个函数能够“记住”并访问它定义时的词法作用域,即使函数在外部被调用时,仍能访问到定义时的作用域。闭包是由函数以及创建该函数时的作用域(创建时作用域内的任何局部变量)组合而成的。

函数嵌套:闭包通常涉及函数内部的函数。
作用域链:内部函数可以访问其外部函数(以及更外层)的作用域链中的变量。
持久化状态:即使外部函数已经返回,内部函数仍然可以访问其变量,因为这些变量被保存在内存中的闭包结构中,简单来说就是内部函数调用了外部函数的变量,变量还被调仍未释放。

function outer() {let count = 0;  // 这是外部函数的局部变量return function inner() {  // 内部返回的匿名函数count++;  // 每次调用 inner 函数时,count 会递增console.log(count);  // 输出 count 的当前值};
}const counter = outer();  // 调用 outer 函数,返回 inner 函数并赋值给 counter
counter();  // 输出 1
counter();  // 输出 2
counter();  // 输出 3

闭包的优点

  • 封装:闭包允许将变量和方法封装在一起,形成一个私有作用域,从而避免全局命名冲突和数据污染。这是模块化编程的基础。
  • 保持状态:闭包可以保持其创建时的外部变量的状态,即使外部变量在闭包外部发生了变化,闭包内部仍然可以访问到原始的变量值。
  • 实现工厂函数:通过闭包,可以创建具有私有变量和方法的函数工厂,根据不同的参数生成不同的函数实例。
  • 记忆化:闭包可以用于记忆化函数,将函数的计算结果缓存起来,避免重复计算,从而提高性能。
    回调函数和异步操作:在JavaScript中,闭包常用于回调函数和异步操作中,以保持数据的状态和上下文。

闭包的缺点

  • 内存泄漏:如果闭包引用的外部变量不再需要,但由于闭包的存在而无法被垃圾回收机制回收,就会导致内存泄漏。因此,在使用闭包时,需要确保在不再需要闭包时将其引用置为null,以释放内存。
  • 性能影响:由于闭包涉及作用域链的查找,相比普通函数,闭包的执行速度可能较慢。

闭包的应用场景

  • 模块化编程:通过闭包可以创建模块,将相关的函数和数据封装在一起,避免全局命名冲突,实现模块化开发。
  • 事件处理程序:在DOM事件处理程序中,闭包常用于保持事件处理函数的上下文和状态。
    回调函数:在异步操作中,闭包常用于回调函数中,以保持异步操作完成后的结果和上下文。
  • 动态函数创建(柯里化):通过闭包可以动态生成函数,每个函数都有自己的独立作用域和状态。函数柯里化是将一个多参数函数转换为一系列接受单个参数的函数的技术。

3.回调函数

回调函数是指一个函数被作为参数传递给另一个函数,并在某些条件下执行。它是 JavaScript 中异步编程和事件处理的核心概念之一。回调函数允许你在某个操作完成后“回调”或执行额外的代码。

回调函数在 JavaScript 中非常常见,尤其是在处理异步操作时,如网络请求、事件监听、定时器等。

异步操作通常需要在某个操作完成后执行特定的任务。回调函数可以在异步操作完成时被调用,处理返回的数据或结果:

function sayHello() {console.log("Hello after 2 seconds!");
}// 使用回调函数
setTimeout(sayHello, 2000);  // 2秒后执行 sayHello

事件处理程序中,回调函数常常用于响应用户操作(如点击、键盘输入等):

function handleClick() {console.log("Button clicked!");
}// 为按钮元素绑定事件回调
document.getElementById("myButton").addEventListener("click", handleClick);

回调函数的异步与同步
回调函数既可以用于同步操作,也可以用于异步操作。

同步回调函数是指在调用回调函数时,它会阻塞代码的执行,直到回调函数执行完成。回调函数的执行是在主线程中按顺序进行的。

异步回调函数是在任务完成后才执行的回调。通常用于处理需要一段时间才能完成的任务,如读取文件、数据库查询或网络请求等。异步回调函数通常通过事件、定时器或回调队列来实现。

异步回调函数

1.异步操作setTimeout:

function sayHello() {console.log("Hello after 2 seconds!");
}// 使用回调函数
setTimeout(sayHello, 2000);  // 2秒后执行 sayHello

2.事件处理:

function handleClick() {console.log("Button clicked!");
}// 为按钮元素绑定事件回调
document.getElementById("myButton").addEventListener("click", handleClick);

回调地狱

当回调函数嵌套较深时,代码会变得难以阅读和维护,这种情况被称为 回调地狱(Callback Hell)。特别是在处理多个异步操作时,代码会逐渐变得复杂,像“金字塔”一样层层嵌套。

为了解决回调地狱,JavaScript 引入了 Promiseasync/await 语法,这使得异步操作的处理变得更加简洁和易于理解。

Promise

Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。本质上 Promise 是一个函数返回的对象,我们可以在它上面绑定回调函数,这样我们就不需要在一开始把回调函数作为参数传入这个函数了。

then() 函数会返回一个和原来不同的新的 Promise。.then() 方法最多接受两个参数;第一个参数是 Promise 兑现时的回调函数,第二个参数是 Promise 拒绝时的回调函数。每个 .then() 返回一个新生成的 Promise 对象,这个对象可被用于链式调用。

异步方法不会立即返回最终值,而是返回一个 promise,以便在将来的某个时间点提供该值。这个值可以是成功并返回值,也可以是失败的加上原因。

一个 Promise 必然处于以下几种状态之一:

待定(pending):初始状态,既没有被兑现,也没有被拒绝。
已兑现(fulfilled):意味着操作成功完成。
已拒绝(rejected):意味着操作失败。

一个待定的 Promise 最终状态可以是已兑现并返回一个值,或者是已拒绝并返回一个原因(错误)。当其中任意一种情况发生时,通过 Promise 的 then 方法串联的处理程序将被调用。如果绑定相应处理程序时 Promise 已经兑现或拒绝,这处理程序将被立即调用,因此在异步操作完成和绑定处理程序之间不存在竞态条件。

在这里插入图片描述
Promise.prototype.then()、Promise.prototype.catch() 和 Promise.prototype.finally() 方法用于将进一步的操作与已敲定的 Promise 相关联。

总体使用流程如下

首先需要定义一个Promise对象,这个对象要传入一个执行器函数(一般是异步的,也就是要执行的函数),执行器函数本身接收两个函数作为参数,通常被命名为 resolve 和 reject,同时函数体里会定义这两个函数的调用(也就是定义成功失败的时候的操作):

  • resolve:当异步操作成功时,我们调用 resolve 函数,这会将 Promise 的状态从 “pending” 变为 “fulfilled”。
  • reject:当异步操作失败时,我们调用 reject 函数,这会将 Promise 的状态从 “pending” 变为 “rejected”。

然后需要使用Promise对象,用.then().catch()方法来处理成功或失败的情况(用于注册处理这些结果的回调函数)。

  • .then():接收两个函数作为参数。第一个函数是当 Promise 成功(fulfilled)时调用,并接收 resolve 传递的值。第二个函数是当 Promise 失败(rejected)时调用,并接收 reject 传递的错误信息。
  • .catch():当 Promise 被拒绝(rejected),并接收 reject 传递的错误信息时被调用。

在执行器函数内部,根据异步操作的结果,你会调用 resolve(value) 来表示成功,或调用 reject(error) 来表示失败。这些调用将会改变 Promise 的状态,并且 resolve 和 reject 的参数会被传递给 .then() 或 .catch() 注册的回调函数。

为什么在then函数中可以定义成功失败的场景,还需要catch?
因为promise的链式调用(可以一直.then()),这时候如果出现错误或者失败,在then函数中定义的处理失败措施只能捕获到该 .then() 之前的 Promise 拒绝,而 .catch() 能捕获所有之前的异常,包括任何 .then() 链中的错误。.catch() 不仅捕获前面 Promise 中的错误,它还能捕获前面 .then() 中的错误(冒泡)。这样一来,你可以在链的末尾只使用一个 .catch() 来处理所有可能的错误。

当你在 JavaScript 的 Promise 链中使用 throw 抛出一个错误,并且这个错误被链中的 .catch() 捕获,这个错误就会被处理,并且不会在控制台显示为未捕获的错误(除非你在 .catch() 中再次抛出它或者将其打印出来)。

.finally():用来在所有都处理完了之后进行清理工作。

// 创建一个新的 Promise 对象
let promise = new Promise((resolve, reject) => {// 异步操作的示例:使用 setTimeout 模拟setTimeout(() => {const operationWasSuccessful = Math.random() > 0.5;if (operationWasSuccessful) {resolve("Operation successful");  // 异步成功} else {reject("Operation failed");  // 异步失败}}, 1000);
});// 处理 Promise 的结果
promise.then((result) => {console.log("Success:", result);}).catch((error) => {console.error("Error:", error);});

优势

1.支持链式调用,解决回调地狱问题。
2.指定回调函数可以在启动异步任务之后再。

Promise 还提供了几个有用的静态方法,用于处理多个 Promise 的组合。

通常,如果你不知道一个值是否是 Promise,那么最好使用 Promise.resolve(value) 将其转换成 Promise 对象,并将返回值作为 Promise 来处理。

forEach 是同步执行的,它不会等待其中的异步操作。forEach 和 for…of 在处理异步操作时的主要区别确实主要在于 await 的使用。

Promise.all() 方法接受一个 Promise 数组(或任何可迭代对象)作为输入,并返回一个新的 Promise。这个返回的 Promise 会等待所有传入的 Promise 都完成(即变为 “fulfilled” 状态):

成功情况:当所有的 Promise 都成功解决时,Promise.all() 返回的 Promise 也会成功解决,并且它的解决值是一个数组,包含所有传入 Promise 的解决值,按照它们在输入数组中的顺序排列。
失败情况:如果任何一个传入的 Promise 失败(即变为 “rejected” 状态),Promise.all() 返回的 Promise 也会立即失败,并且拒绝的原因是第一个失败的 Promise 的原因。

function promiseAll(promises) {return new Promise((resolve, reject) => {// 确保输入是可迭代的if (!Array.isArray(promises)) {return reject(new TypeError('The input must be an array'));}let results = [];  // 用于存储每个 promise 的结果let completed = 0;  // 已完成的 promise 数量if (promises.length === 0) {resolve(results);  // 空数组的情况,直接解决}// 立刻让所有promise都执行,不等待promises.forEach((promise, index) => {// 使用 Promise.resolve 包装,以兼容非 Promise 输入Promise.resolve(promise).then(value => {results[index] = value;  // 存储每个 promise 的解决值completed += 1;  // 完成计数if (completed === promises.length) {resolve(results);  // 如果所有 promise 都已解决,则解决 promiseAll 返回的 promise}}).catch(reject);  // 任何一个 promise 被拒绝,则拒绝 promiseAll 返回的 promise});});
}

Promise.race() 方法也接受一个 Promise 数组(或任何可迭代对象)作为输入,并返回一个新的 Promise。这个返回的 Promise 将会 “赛跑”,即它将由第一个解决或拒绝的输入 Promise 来决定其结果:
成功或失败情况:Promise.race() 返回的 Promise 将采用第一个完成的 Promise 的状态(无论是解决还是拒绝)。其解决或拒绝的值也将是首个完成的 Promise 的相应值。
自己实现:

function promiseRace(promises) {return new Promise((resolve, reject) => {// 确保输入是可迭代的if (!promises || !Array.isArray(promises)) {return reject(new TypeError('The input should be an array of promises.'));}if (promises.length === 0) {// 如果传入的是空数组,那么永远挂起return;}promises.forEach((promise)=>{Promise.resolve(promise).then(value=>{resolve(value);}).catch(reject);})});
}

Promise.any() 是一个比较新的 Promise 方法,它接收一个 Promise 数组作为参数,返回一个新的 Promise。这个新 Promise 解决为数组中第一个成功解决的 Promise 的结果。如果所有的 Promise 都失败了,那么返回的 Promise 将会被拒绝,并且拒绝的原因是一个 AggregateError,其中包含所有失败 Promise 的原因。

function promiseAny(promises) {return new Promise((resolve, reject) => {// 检查输入是否为数组if (!Array.isArray(promises)) {return reject(new TypeError('The input must be an array'));}let errors = [];  // 用于存储每个 promise 的错误let rejectedCount = 0;  // 已拒绝的 promise 数量if (promises.length === 0) {return reject(new AggregateError([], 'All promises were rejected'));}promises.forEach((promise, index) => {Promise.resolve(promise).then(resolve)  // 第一个解决的 promise 将调用这个 resolve.catch(error => {errors[index] = error;  // 存储错误信息rejectedCount += 1;  // 错误计数if (rejectedCount === promises.length) {// 所有 promises 都拒绝了reject(new AggregateError(errors, 'All promises were rejected'));}});});});
}

Promise.allSettled() 是一个用于处理多个 Promise 对象的方法,它返回一个新的 Promise,这个 Promise 解决为一个数组,每个数组项都是一个对象,表示每个原始 Promise 的结果。不同于 Promise.all(),Promise.allSettled() 不会在某个 Promise 被拒绝时立即拒绝整个返回的 Promise。它会等待所有 Promise 都定案(无论是解决还是拒绝)。

function promiseAllSettled(promises) {return new Promise((resolve, reject) => {// 确保输入是可迭代的if (!Array.isArray(promises)) {return reject(new TypeError('The input must be an array'));}let results = new Array(promises.length);  // 存储所有 promise 的结果let settledCount = 0;  // 已定案的 promise 数量promises.forEach((promise, index) => {Promise.resolve(promise).then(value => {results[index] = { status: 'fulfilled', value: value };settledCount++;if (settledCount === promises.length) {resolve(results);}}).catch(reason => {results[index] = { status: 'rejected', reason: reason };settledCount++;if (settledCount === promises.length) {resolve(results);}});});});
}

async和await语法糖
async 和 await 是用于简化异步编程的关键字,基于 Promise 的代码。

async 用于声明函数为异步函数。一个 async 函数是一种返回 Promise 对象的函数。如果在函数中返回一个值(return),async 函数会自动将该值包装成一个解决的(resolved)Promise。如果抛出异常(throw error),则返回一个被拒绝的(rejected)Promise。

await 只能在 async 函数内部使用。它可以暂停 async 函数的执行,等待 Promise 的解决(resolve)或拒绝(reject),然后恢复 async 函数的执行并返回解决的值。当 await 后面的 Promise 被拒绝时,它会抛出一个异常。这个异常可以用传统的 try…catch 语句捕获。

async function fetchDataWithError() {try {let data = await new Promise((resolve, reject) => setTimeout(() => reject(new Error("Failed to fetch")), 1000));} catch (e) {console.log(e.message);  // 输出 "Failed to fetch",1秒后}
}fetchDataWithError();

await 虽然非常有用,但使用不当可能会导致不必要的序列化执行,特别是当你可以同时启动多个异步任务时。为了并行处理多个 Promise,可以使用 Promise.all 方法。

async function fetchMultipleData() {let [data1, data2] = await Promise.all([new Promise((resolve) => setTimeout(() => resolve("Data 1 fetched"), 1000)),new Promise((resolve) => setTimeout(() => resolve("Data 2 fetched"), 2000))]);console.log(data1);  // 输出 "Data 1 fetched"console.log(data2);  // 输出 "Data 2 fetched"
}fetchMultipleData();

4.箭头函数

箭头函数的基本语法使用一个箭头(=>)代替了传统函数的 function 关键字。这种形式不仅减少了代码的书写,也提供了一种更直接的方式来定义函数:

const sum = (a, b) => {return a + b;
};

如果函数体只有一行语句并且是一个返回值,你可以省略大括号和 return 关键字,使得函数定义更为简洁。

const sum = (a, b) => a + b;

如果没有参数,或者参数多于一个,需要使用圆括号,只有一个就可用可不用:

const sayHello = () => console.log("Hello!");
const multiply = (x, y) => x * y;

如果箭头函数需要返回一个对象字面量,语法需要稍作修改,因为花括号被解析为语句块的开始。为了返回对象,对象字面量需要被圆括号包围:

const getObject = () => ({name: "John", age: 30});

箭头函数不绑定自己的 this,它们在定义时捕获包含它们的上下文中的 this 值

  • 没有自己的 this、arguments、super 或 new.target:这些都是从外围作用域继承的。
  • 不能使用 new 调用:箭头函数不能作为构造函数使用,尝试这样做会抛出错误。
  • 不适合用作方法:在对象的方法中使用箭头函数时,this 关键字不会绑定到预期的对象实例上,而是继承自外围作用域。

传统函数的 this
在 JavaScript 中,传统函数(使用 function 关键字定义的函数)的 this 值是在函数被调用时确定的。它的值依赖于函数的调用方式:

  • 全局上下文:在非严格模式下,如果函数不是作为对象的方法被调用,this 默认指向全局对象(浏览器中的 window,Node.js 中的 global)。在严格模式(‘use strict’)下,this 会是 undefined。
  • 作为对象方法:如果函数作为对象的一个方法被调用,this 指向这个对象。
  • 构造函数:如果函数通过 new 关键字被调用,this 被绑定到新创建的对象上。
  • 通过 call() 或 apply():this 可以被显式设置为第一个参数传递的任何值。

箭头函数的 this
箭头函数则表现得不同。它们不自己绑定 this,而是捕获其定义时所在的上下文的 this 值。这个行为被称为“词法作用域”或“静态作用域”。这意味着:
箭头函数中的 this 的值是在定义箭头函数时继承自外围最近一层非箭头函数的 this 值。
即使是运行时调用箭头函数,它的 this 值也不会改变。
你不能通过 call()、apply() 或 bind() 方法来改变箭头函数的 this。

参考

闭包:https://blog.csdn.net/m0_55049655/article/details/143593869

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/475728.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python实战案例----使用 PyQt5 构建简单的 HTTP 接口测试工具

python实战案例----使用 PyQt5 构建简单的 HTTP 接口测试工具 文章目录 python实战案例----使用 PyQt5 构建简单的 HTTP 接口测试工具项目背景技术栈用户界面核心功能实现结果展示完整代码总结 在现代软件开发中&#xff0c;测试接口的有效性与响应情况变得尤为重要。本文将指导…

网络安全之信息收集-实战-1

请注意&#xff0c;本文仅供合法和授权的渗透测试使用&#xff0c;任何未经授权的活动都是违法的。 实战&#xff1a;补天公益src“吉林通用航空职业技术学院” 奇安信&#xff5c;用户登录https://www.butian.net/Loo/submit?cid64918 域名或ip&#xff1a;https://www.jlth…

鸿蒙实战:使用隐式Want启动Ability

文章目录 1. 实战概述2. 实现步骤2.1 创建鸿蒙应用项目2.2 修改Index.ets代码2.3 创建LuzhouAbility2.4 创建Luzhou页面2.5 设置模块配置文件 3. 测试效果4. 实战总结 1. 实战概述 本次鸿蒙应用实战&#xff0c;先创建项目“ImplicitWantStartAbility”&#xff0c;接着修改In…

STM32低功耗设计NFC与无线距离感应智能钥匙扣-分享

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 智能钥匙扣作为一种小巧而实用的智能设备&#xff0c;凭借其便携性…

【Node.js】Node.js 和浏览器之间的差异

Node.js 是一个强大的运行时环境&#xff0c;它在现代 JavaScript 开发中扮演着重要角色。然而&#xff0c;许多开发者在使用 Node.js 时常常会感到困惑&#xff0c;尤其是与浏览器环境的对比。本文将深入探讨 Node.js 和浏览器之间的差异&#xff0c;帮助你全面理解两者的设计…

qt之telnet连接目标设备在线调试功能

一、前言 在QT下使用telnet连接目标设备&#xff0c;进行在线命令调试&#xff0c;也可配合ftp或ssh使用。 telnet某些库在qt5下不可用&#xff0c;无法获取登录信息&#xff0c;只能获取到连接信息&#xff0c;这里我用自己的方式判断是否成功登录 二、环境 window qt5.7…

小熊派Nano接入华为云

一、华为云IoTDA创建产品 创建如下服务&#xff0c;并添加对应的属性和命令。 二、小熊派接入 根据小熊派官方示例代码D6完成了小熊派接入华为云并实现属性上传命令下发。源码&#xff1a;小熊派开源社区/BearPi-HM_Nano 1. MQTT连接代码分析 这部分代码在oc_mqtt.c和oc_mq…

Hbuilder X/Uniapp 关于app运行调试及mumu模拟器运行问题

Hbuilder X 关于app调试问题及mumu模拟器链接问题 Hbuilder 关于app调试问题1. app运行配置2. adb路径配置3. 模拟器端口查询4. 运行 Hbuilder 关于app调试问题 1. app运行配置 Hbuilder > 工具 > 设置 > 运行配置 adb路径配置&#xff08;见2&#xff09; Android模…

MySQL-关键字执行顺序

&#x1f496;简介 在MySQL中&#xff0c;SQL查询语句的执行遵循一定的逻辑顺序&#xff0c;即使这些关键字在SQL语句中的物理排列可能有所不同。 &#x1f31f;语句顺序 (8) SELECT (9) DISTINCT<select_list> (1) FROM <left_table> (3) <join_type> JO…

【SpringBoot】26 实体映射工具(MapStruct)

Gitee 仓库 https://gitee.com/Lin_DH/system 介绍 现状 为了让应用程序的代码更易于维护&#xff0c;通常会将项目进行分层。在《阿里巴巴 Java 开发手册》中&#xff0c;推荐分层如下图所示&#xff1a; 每层都有对应的领域模型&#xff0c;即不同类型的 Bean。 DO&…

RPC-健康检测机制

什么是健康检测&#xff1f; 在真实环境中服务提供方是以一个集群的方式提供服务&#xff0c;这对于服务调用方来说&#xff0c;就是一个接口会有多个服务提供方同时提供服务&#xff0c;调用方在每次发起请求的时候都可以拿到一个可用的连接。 健康检测&#xff0c;能帮助从连…

Enterprise Architect 16 下载、安装与无限30天操作

文章目录 Enterprise Architect 16 简介&#xff08;一&#xff09;支持多种建模语言和标准&#xff08;二&#xff09;强大的版本控制、协作和文档管理功能&#xff08;三&#xff09;增强的技术和用户体验&#xff08;四&#xff09;高级功能和扩展性 一&#xff0c;下载软件…

小程序租赁系统开发为企业提供高效便捷的租赁服务解决方案

内容概要 在这个数字化飞速发展的时代&#xff0c;小程序租赁系统应运而生&#xff0c;成为企业管理租赁业务的一种新选择。随着移动互联网的普及&#xff0c;越来越多的企业开始关注如何利用小程序来提高租赁服务的效率和便捷性。小程序不仅可以为用户提供一个快速、易用的平…

定时器的小应用

第一个项目 第一步&#xff0c;RCC开启时钟&#xff0c;这个基本上每个代码都是第一步&#xff0c;不用多想&#xff0c;在这里打开时钟后&#xff0c;定时器的基准时钟和整个外设的工作时钟就都会同时打开了 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);第二步&…

JVM--内存结构

目录 1. PC Register&#xff08;程序计数器&#xff09; 1.1 定义 1.2 工作原理 1.3 特点 1.4 应用 2.虚拟机栈 2.1定义与特性 2.2内存模型 2.3工作原理 2.4异常处理 2.5应用场景 2.6 Slot 复用 2.7 动态链接详解 1. 栈帧与动态链接 动态链接的作用&#xff1a…

一文读懂Redis6的--bigkeys选项源码以及redis-bigkey-online项目介绍

一文读懂Redis6的--bigkeys选项源码以及redis-bigkey-online项目介绍 本文分为两个部分&#xff0c;第一是详细讲解Redis6的--bigkeys选项相关源码是怎样实现的&#xff0c;第二部分为自己对--bigkeys源码的优化项目redis-bigkey-online的介绍。redis-bigkey-online是自己开发的…

Go语言跨平台桌面应用开发新纪元:LCL、CEF与Webview全解析

开篇寄语 在Go语言的广阔生态中&#xff0c;桌面应用开发一直是一个备受关注的领域。今天&#xff0c;我将为大家介绍三款基于Go语言的跨平台桌面应用开发框架——LCL、CEF与Webview&#xff0c;它们分别拥有独特的魅力和广泛的应用场景。通过这三款框架&#xff0c;你将能够轻…

音视频入门基础:MPEG2-TS专题(5)——FFmpeg源码中,判断某文件是否为TS文件的实现

一、引言 通过FFmpeg命令&#xff1a; ./ffmpeg -i XXX.ts 可以判断出某个文件是否为TS文件&#xff1a; 所以FFmpeg是怎样判断出某个文件是否为TS文件呢&#xff1f;它内部其实是通过mpegts_probe函数来判断的。从《FFmpeg源码&#xff1a;av_probe_input_format3函数和AVI…

C++初阶学习第十一弹——list的用法和模拟实现

目录 一、list的使用 二.list的模拟实现 三.总结 一、list的使用 list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指向 其前一个元素和后一个元素。 常见的list的函数的使用 std::list<int> It {1,…

Qlik Sense QVD 文件

QVD 文件 QVD (QlikView Data) 文件是包含从 Qlik Sense 或 QlikView 中所导出数据的表格的文件。QVD 是本地 Qlik 格式&#xff0c;只能由 Qlik Sense 或 QlikView 写入和读取。当从 Qlik Sense 脚本中读取数据时&#xff0c;该文件格式可提升速度&#xff0c;同时又非常紧凑…