2025-03-19 21:19:20 +08:00

418 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: JavaScript
date: 2023-11-18 10:30:31
author: 文永达
---
# 异步编程
## await
在JavaScript中`await`关键字用于等待一个异步操作完成,并返回异步操作的结果。当遇到`await`关键字时JavaScript引擎会暂停当前函数的执行直到异步操作完成并返回结果。在等待异步操作的过程中JavaScript引擎会继续执行其他代码。
因此,`await`关键字的执行顺序是从上到下的,即在遇到`await`关键字之前的代码会先执行,然后等待异步操作完成后,再执行`await`关键字之后的代码。如果在`await`关键字之前的代码中抛出了异常,那么`await`关键字之后的代码将不会执行。
在你提供的代码片段中,当遇到`await`关键字时JavaScript引擎会等待`addCoCertification``updateCoCertification`函数完成并返回它们的结果。在等待这些函数的过程中JavaScript引擎会继续执行其他代码例如设置`isAddedFile.value``dialogVisible.value`的值。当异步操作完成后JavaScript引擎会继续执行`await`关键字之后的代码,例如显示成功消息。
总之,`await`关键字的执行顺序是从上到下的,即在遇到`await`关键字之前的代码会先执行,然后等待异步操作完成后,再执行`await`关键字之后的代码。
下面是一个简单的示例,演示了`await`关键字的执行顺序:
```javascript
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function example() {
console.log('Start')
await sleep(1000)
console.log('After 1 second')
await sleep(2000)
console.log('After 2 more seconds')
console.log('End')
}
example()
```
在这个示例中,我们定义了一个`sleep`函数它返回一个Promise对象并在指定的时间后将其状态设置为resolved。然后我们定义了一个名为`example`的异步函数,它使用`await`关键字等待`sleep`函数完成,并在控制台中打印一些消息。在`example`函数中,我们使用`await`关键字等待`sleep(1000)`函数完成并在1秒后打印"After 1 second"。然后,我们再次使用`await`关键字等待`sleep(2000)`函数完成并在2秒后打印"After 2 more seconds"。最后,我们在控制台中打印"End"。
当我们调用`example`函数时JavaScript引擎会执行`console.log('Start')`语句,然后等待`sleep(1000)`函数完成。在等待的过程中JavaScript引擎会继续执行其他代码但是当`sleep(1000)`函数完成后JavaScript引擎会继续执行`await`关键字之后的代码,即打印"After 1 second"。然后JavaScript引擎会等待`sleep(2000)`函数完成并在2秒后打印"After 2 more seconds"。最后JavaScript引擎会打印"End"。
---
在同步方法中使用`await`关键字是不合法的,因为`await`只能在异步函数中使用。如果你在同步函数中使用`await`关键字,它会被视为普通的表达式,不会暂停当前函数的执行。
如果你需要在同步函数中等待一个异步操作完成,你可以使用`Promise`对象和`then`方法来实现。例如,你可以将异步操作封装在一个`Promise`对象中,并在`then`方法中处理结果。下面是一个示例:
```javascript
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
function example() {
console.log('Start')
sleep(1000).then(() => {
console.log('After 1 second')
return sleep(2000)
}).then(() => {
console.log('After 2 more seconds')
console.log('End')
})
}
example()
```
在这个示例中,我们定义了一个`sleep`函数它返回一个Promise对象并在指定的时间后将其状态设置为resolved。然后我们定义了一个名为`example`的同步函数,它使用`Promise`对象和`then`方法等待`sleep`函数完成,并在控制台中打印一些消息。在`example`函数中,我们使用`sleep(1000).then()`方法等待`sleep(1000)`函数完成并在1秒后打印"After 1 second"。然后,我们再次使用`then`方法等待`sleep(2000)`函数完成并在2秒后打印"After 2 more seconds"和"End"。
当我们调用`example`函数时JavaScript引擎会执行`console.log('Start')`语句,然后等待`sleep(1000)`函数完成。在等待的过程中JavaScript引擎会继续执行其他代码但是当`sleep(1000)`函数完成后JavaScript引擎会执行`then`方法中的代码,即打印"After 1 second"。然后JavaScript引擎会等待`sleep(2000)`函数完成并在2秒后打印"After 2 more seconds"和"End"。
# 数组
## map
`map`方法会对数组中的每个元素都执行一个回调函数,并返回一个新的数组,新数组中的元素是回调函数的返回值。`map`方法不会改变原始数组,而是返回一个新的数组。下面是一个使用`map`方法的示例:
```javascript
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map((number) => number * 2);
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
```
在这个示例中,我们使用`map`方法将数组中的每个元素都乘以2并返回一个新的数组`doubledNumbers`
## filter
`filter`方法会对数组中的每个元素都执行一个回调函数,并返回一个新的数组,新数组中的元素是回调函数返回`true`的元素。`filter`方法不会改变原始数组,而是返回一个新的数组。下面是一个使用`filter`方法的示例:
```javascript
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter((number) => number % 2 === 0);
console.log(evenNumbers); // [2, 4]
```
在这个示例中,我们使用`filter`方法筛选出数组中的偶数,并返回一个新的数组`evenNumbers`
总之,`map``filter`都是JavaScript中常用的数组方法它们可以用于对数组进行转换和筛选。`map`方法会对数组中的每个元素都执行一个回调函数,并返回一个新的数组,新数组中的元素是回调函数的返回值。`filter`方法会对数组中的每个元素都执行一个回调函数,并返回一个新的数组,新数组中的元素是回调函数返回`true`的元素。
## reduce
`reduce()` 函数是 JavaScript 中的一个高阶函数,它可以将一个数组中的所有元素通过一个函数进行累加,最终返回一个累加结果。`reduce()` 函数接收两个参数:一个**回调函数**和一个**初始值**。回调函数接收四个参数:**累加器**、**当前元素**、**当前索引**和**原始数组**。在每次迭代中,回调函数都会将累加器和当前元素作为参数,并返回一个新的累加器值。最终,`reduce()` 函数返回最后一次调用回调函数时的累加器值。
```javascript
const numbers = [65, 44, 12, 4]
// total为 累加器 item为当前元素
// 0 为初始值,也就是 从什么值开始累加
const totalNumber = () => {
return numbers.reduce((total, item) => {
return total + item
}, 0)
}
```
# 循环
## for...in
`for...in`语句用于遍历对象的可枚举属性,包括对象自身的属性和继承的属性。在遍历时,`for...in`会将属性名作为变量传递给循环体,因此我们可以通过属性名来访问属性的值。下面是一个使用`for...in`语句遍历对象的示例:
```javascript
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
console.log(key, obj[key]);
}
// Output:
// a 1
// b 2
// c 3
```
在这个示例中,我们使用`for...in`语句遍历对象`obj`的属性,并将属性名作为变量`key`传递给循环体。在循环体中,我们可以通过`obj[key]`来访问属性的值。
## for...of
`for...of`语句用于遍历可迭代对象包括数组、字符串、Map、Set等。在遍历时`for...of`会将元素的值作为变量传递给循环体,因此我们可以直接访问元素的值。下面是一个使用`for...of`语句遍历数组的示例:
```javascript
const arr = [1, 2, 3];
for (const item of arr) {
console.log(item);
}
// Output:
// 1
// 2
// 3
```
在这个示例中,我们使用`for...of`语句遍历数组`arr`的元素,并将元素的值作为变量`item`传递给循环体。在循环体中,我们可以直接访问元素的值。
总之,`for...in``for...of`都是JavaScript中用于遍历数据结构的循环语句但它们的作用和用法有所不同。`for...in`语句用于遍历对象的可枚举属性,而`for...of`语句用于遍历可迭代对象的元素。
# 下载文件
## Url转Blob
```javascript
// xhr
function urlToBlob(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = function() {
if (xhr.status === 200) {
var blob = xhr.response;
callback(null, blob);
} else {
callback(new Error('Unable to fetch blob'));
}
};
xhr.onerror = function() {
callback(new Error('Network error'));
};
xhr.send();
}
// fetch
function urlToBlob(url) {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Unable to fetch blob');
}
return response.blob();
});
}
```
# 匿名函数和箭头函数
## 区别
匿名函数和箭头函数都是JavaScript中的函数形式它们之间有一些重要的区别。
1. 语法结构:匿名函数使用`function`关键字来定义,后面跟随函数名称(可选),参数列表和函数体。例如:
```javascript
var anonymousFunc = function(arg1, arg2) {
// 函数体
};
```
而箭头函数使用箭头操作符(`=>`)来定义,后面跟随参数列表和箭头函数体。没有显式的函数名称。例如:
```javascript
var arrowFunc = (arg1, arg2) => {
// 函数体
};
```
2. `this` 的指向:在匿名函数中,`this` 的指向是动态的,根据函数的调用方式而定。而在箭头函数中,`this` 的指向是词法作用域的,它继承自外围作用域。箭头函数没有自己的 `this` 值,它会使用外围作用域的 `this` 值。这使得箭头函数在处理函数上下文时更加方便。
3. `arguments` 对象:匿名函数中可以使用 `arguments` 对象访问函数的参数列表。而箭头函数没有自己的 `arguments` 对象,它会继承外围作用域中的 `arguments` 对象。
4. 构造函数:匿名函数可以用作构造函数来创建新对象。而箭头函数没有原型对象,因此不能用作构造函数,尝试使用 `new` 关键字调用箭头函数会抛出错误。
5. 箭头函数的简洁语法:箭头函数提供了更简洁的语法,尤其是在处理简单的函数体时。当函数体只有一条表达式时,可以省略花括号和 `return` 关键字,并将表达式的结果隐式返回。
这些是匿名函数和箭头函数之间的一些主要区别。选择使用哪种函数形式取决于具体的使用场景和需求。匿名函数适用于更传统的函数定义和需要动态 `this` 指向的情况,而箭头函数则提供了更简洁的语法和更方便的函数上下文处理。
# 运算符
## ~
在 JavaScript 中,`~` 符号是按位取反运算符。它将操作数的每个二进制位取反,即将 0 变为 1将 1 变为 0。然后返回这个值的负数加一。
例如,对于十进制数 10它的二进制表示为 1010按位取反后变为 0101即 5。因此`~10` 的结果为 -11。
# 性能优化
## 防抖debounce
> 多次触发 只执行最后一次
**作用:**高频率触发的事件,在指定的单位时间内,只相应最后一次,如果在指定时间内再次触发,则重新计算时间。
防抖类似于英雄联盟回城6秒如果回城中被打断再次回城需要再等6秒
![image-20230918145946833](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230918145946833.png)
**实现代码:**
```javascript
<body>
<input type="text" id="inp">
<script>
// 1.封装防抖函数
function debounce(fn, time) {
// 4.创建一个标记用来存放定时器的返回值
let timeout = null;
return function () {
// 5.每当用户触发input事件 把前一个 setTimeout 清楚掉
clearTimeout(timeout);
// 6.然后又创建一个新的 setTimeout, 这样就能保证输入字符后等待的间隔内 还有字符输入的话,就不会执行 setTimeout里面的内容
timeout = setTimeout(() => {
// 7.这里进行防抖的内容
fn();
}, time);
};
}
// 2.获取inpt元素
var inp = document.getElementById('inp');
// 8. 测试防抖临时使用的函数
function sayHi() {
console.log('防抖成功');
}
// 3.给inp绑定input事件 调用封装的防抖函数 传入要执行的内容与间隔事件
inp.addEventListener('input', debounce(sayHi, 5000));
</script>
</body>
```
**使用场景:**
search搜索时用户在不断输入值时用防抖来节约请求资源。
## 节流throttle
> 规定时间内 只触发一次
**作用:**高频率触发的事件,在指定的单位时间内,只响应第一次
节流类似于英雄联盟里的技能 触发一次必须等技能刷新后才能再次触发
![image-20230918151523728](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230918151523728.png)
**实现代码:**
```javascript
<script>
// 1.封装节流函数
function throttle(fn, time) {
//3. 通过闭包保存一个 "节流阀" 默认为false
let temp = false;
return function () {
//8.触发事件被调用 判断"节流阀" 是否为true 如果为true就直接trurn出去不做任何操作
if (temp) {
return;
} else {
//4. 如果节流阀为false 立即将节流阀设置为true
temp = true; //节流阀设置为true
//5. 开启定时器
setTimeout(() => {
//6. 将外部传入的函数的执行放在setTimeout中
fn.apply(this, arguments);
//7. 最后在setTimeout执行完毕后再把标记'节流阀'为false(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是true在开头被return掉
temp = false;
}, time);
}
};
}
function sayHi(e) {
// 打印当前 document 的宽高
console.log(e.target.innerWidth, e.target.innerHeight);
}
// 2.绑定事件,绑定时就调用节流函数
// 敲黑板!!! 这里是重点 绑定是就要调用一下封装的节流函数 触发事件是触发封装函数内部的函数
window.addEventListener('resize', throttle(sayHi, 2000));
</script>
```
**使用场景:**
鼠标不断点击触发mousedown单位时间内只触发一次
监听滚动事件比如是否滑到底部自动加载更多用throttle来判断
# Map 键值对
## 遍历MAP
### keys() 方法
该方法返回一个新的迭代器对象,它包括`Map`对象中每个元素的键。
```js
const map = new Map();
map.set('name', 'Alice');
map.set('age', 25);
for (const key of map.keys()) {
console.log(key);
}
// 输出:'name' 'age'
```
### values() 方法
该方法返回一个新的迭代器对象,它包括`Map`对象中每个元素的值。
```js
const map = new Map();
map.set('name', 'Alice');
map.set('age', 25);
for (const value of map.values()) {
console.log(value);
}
// 输出:'Alice' 25
```
### entries() 方法
该方法返回一个新的迭代器对象,它包括`Map`对象中每个元素的键值对。
```js
const map = new Map();
map.set('name', 'Alice');
map.set('age', 25);
for (const [key, value] of map.entries()) {
console.log(key + ': ' + value);
}
// 输出:'name: Alice' 'age: 25'
```
### forEach() 方法
此方法接受一个回调函数作为参数,`Map`对象中的每个元素都会调用一次这个回调函数。回调函数中的参数依次为:`value``key``mapObject`
```js
const map = new Map();
map.set('name', 'Alice');
map.set('age', 25);
map.forEach((value, key) => {
console.log(key, value);
});
// 输出:'name' 'Alice' 'age' 25
```
## 进阶用法
### Map 和 Array 的相互转化
有时我们需要在 `Map``Array` 之间相互转化,比如将数组转换为字典,或者从字典转换为数组时,我们就可以结合 `Array` 构造函数和扩展运算符来实现。
```js
let kvArray = [['key1', 'value1'], ['key2', 'value2']];
let myMap = new Map(kvArray);
console.log(myMap); // Map(2) {"key1" => "value1", "key2" => "value2"}
let arrayFromMap = Array.from(myMap);
console.log(arrayFromMap); // [["key1", "value1"], ["key2", "value2"]]
```
### Map 的合并和复制