【前端学习笔记】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.xdnf.cn/news/20023.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【开源免费】基于Vue和SpringBoot的智慧食堂系统(附论文)

本文项目编号 T 629 &#xff0c;文末自助获取源码 \color{red}{T629&#xff0c;文末自助获取源码} T629&#xff0c;文末自助获取源码 随着Internet的发展&#xff0c;人们的日常生活已经离不开网络。未来人们的生活与工作将变得越来越数字化&#xff0c;网络化和电子化。网…

基因组之全局互作热图可视化

引言 PlotHiC 是一个专为 Hi-C 数据可视化分析而设计的 Python 包。Hi-C 技术是一种能够检测染色体三维结构的实验方法&#xff0c;它能揭示 DNA 在细胞核内的三维组织结构。为了更好地展示和解释这些复杂的数据&#xff0c;PlotHiC[1] 可以帮助用户方便地绘制Hi-C 数据的热图。…

道本科技智慧合同管理平台,采用数字化技术帮助企业建立全生命周期的合同管理模式。

作为专业的企业合同管理平台建设专家&#xff0c;我们拥有丰富的实施经验和专业技术团队&#xff0c;致力于帮助企业搭建高效、安全的合同管理系统。我们的解决方案涵盖合同起草、审批、存储、分析和报告等多个环节&#xff0c;能够满足不同企业的多样化需求。 选择我们&#…

AmazonS3集成minio实现https访问

最近系统全面升级到https&#xff0c;之前AmazonS3大文件分片上传直接使用http://ip:9000访问minio的方式已然行不通&#xff0c;https服务器访问http资源会报Mixed Content混合内容错误。 一般有两种解决方案&#xff0c;一是升级minio服务&#xff0c;配置ssl证书&#xff0c…

QGIS使用WMS图层

目录 参考链接 参考链接 [1] 使用 WMS 数据 &#xff08;QGIS3&#xff09; 2023.8&#xff1b;

华为防火墙技术基本概念学习笔记

1.防火墙概述 1.1防火墙与交换机、路由器对比 路由器与交换机的本质是转发&#xff0c;防火墙的本质是控制。 防火墙与路由器、交换机是有区别的。路由器用来连接不同的网络&#xff0c;通过路由协议保证互联互通&#xff0c;确保将报文转发到目的地;交换机则通常用来组建局域…

面向FWA市场!移远通信高性能5G-A模组RG650V-NA通过北美两大重要运营商认证

近日&#xff0c;全球领先的物联网整体解决方案供应商移远通信宣布&#xff0c;其旗下符合3GPP R17标准的新一代5G-A模组RG650V-NA成功通过了北美两家重要运营商认证。凭借高速度、大容量、低延迟、高可靠等优势&#xff0c;该模组可满足CPE、家庭/企业网关、移动热点、高清视频…

idea maven 重新构建索引

当设置maven仓库为离线模式的时候&#xff0c;会出现一些问题。 比如本地的仓库被各种方式手动更新之后&#xff0c; 举例&#xff1a;我需要一个spring的包&#xff0c;在pmo文件中写好了引入包的代码 但是由于是离线模式没有办法触发自动下载&#xff0c;那么这个时候我可以…

React(二)

文章目录 项目地址七、数据流7.1 子组件传递数据给父组件7.1.1 方式一:給父设置回调函数,传递给子7.1.2 方式二:直接将父的setState传递给子7.2 给props传递jsx7.2.1 方式一:直接传递组件给子类7.2.2 方式二:传递函数给子组件7.3 props类型验证7.4 props的多层传递7.5 cla…

项目管理的核心指南:四管八理

01项目管理核心&#xff1a;四管八理 项目管理的复杂性在于其多变的细节&#xff0c;但一旦掌握了核心框架和方法论&#xff0c;便能轻松应对。以下是项目管理的“四管八理”框架&#xff0c;旨在帮助项目经理构建自己的管理方法论。 02项目管理“四管” 1.团队协调 项目成功…

消防设施操作员高频考点

1、职业是指从业人员为获取主要生活来源所从事的社会工作类别。&#xff08;正确&#xff09; 2、职业活动以获得现金或实物等报酬为目的&#xff0c;这属于职业特征的&#xff08;A&#xff09;。 A、目的性 B、社会性 C、稳定性 D、规范性 解析&#xff1a;…

传输层协议TCP

一.TCP协议格式 对于传输层协议我们之前是学过了UDP&#xff0c;对于传输层协议是存在了一定的了解的&#xff0c;所以现在我们再来看TCP协议格式&#xff1a; 我们之前学过UDP的报文格式&#xff0c;所以源端口和目的端口是不需要进行再次讲解的&#xff0c;对于32序号和确认序…

【面试题】接口怎么测试?如何定位前后端的Bug?

接口怎么测试&#xff1f; 接口测试用来验证不同软件组件之间的交互是否正常。包括验证数据传输&#xff0c;参数传递&#xff0c;我在多个项目中有过测试接口的经验。&#xff08;… 当进行接口测试时&#xff0c;会使用Postman和Python的Requests库。首先根据接口文档设计测…

【单点知识】基于PyTorch讲解自动编码器(Autoencoder)及其变种

文章目录 0. 前言1. 自动编码器的基本概念1.1 定义1.2 目标1.3 结构 2. PyTorch实现自动编码器2.1 导入必要的库2.2 定义自动编码器模型2.3 加载数据2.4 训练自动编码器 3. 自动编码器的意义4. 自动编码器的应用4.1 图像处理4.2自然语言处理&#xff1a;4.3推荐系统&#xff1a…

Redis篇

Redis使用场景 一、缓存穿透: 一个get请求: api/news/getById/1 1.缓存穿透:查询一个不存在的数据&#xff0c;mysql查询不到数据也不会直接写入缓存&#xff0c;导致每次请求都直接请求查询数据库&#xff0c;导致数据库的压力增大从而宕机 2.解决方案一:缓存空数据&#x…

每日一练:前缀和-矩阵区域和

1314. 矩阵区域和 - 力扣&#xff08;LeetCode&#xff09; 题目要求&#xff1a; 给你一个 m x n 的矩阵 mat 和一个整数 k &#xff0c;请你返回一个矩阵 answer &#xff0c;其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和&#xff1a; i - k < r &…

革新车间照明,分布式IO模块引领智能制造新纪元

在智能制造的浪潮中&#xff0c;每一个细节的优化都是推动生产效率与能耗管理迈向新高度的关键。车间照明系统&#xff0c;作为生产环境中不可或缺的一环&#xff0c;其智能化升级正成为众多企业转型升级的重要着力点。 一、从传统到智能&#xff1a;照明系统的变革之旅 传统…

Oracle19C AWR报告分析之Operating System Statistics

Oracle19C AWR报告分析之Operating System Statistics 一、分析数据二、详细分析三、总结建议 Oracle 19C的AWR&#xff08;Automatic Workload Repository&#xff09;报告中的Operating System Statistics部分提供了操作系统层面的性能统计数据。这些指标对于分析数据库性能的…

项目进度计划表:详细的甘特图的制作步骤

甘特图&#xff08;Gantt chart&#xff09;&#xff0c;又称为横道图、条状图&#xff08;Bar chart&#xff09;&#xff0c;是一种用于管理时间和任务活动的工具。 甘特图由亨利劳伦斯甘特&#xff08;Henry Laurence Gantt&#xff09;发明&#xff0c;是一种通过条状图来…

【PyTorch][chapter 28] 揭秘 Transformer:缩放定律指南

概括 我们介绍了 LLM 的各种缩放定律&#xff0c;研究了模型损失如何随着训练数据和参数数量的增加而变化。讨论包括对用于解释 LLM 缩放定律的 IsoLoss 轮廓和 IsoFLOPs 切片的解释&#xff0c;从而为优化计算资源提供了见解。 最后&#xff0c;我们讨论了 FLOP 和 FLOPS 的概…