SHEN YING

Syの小窝

来看看怎么使用TimeSelect组件。

先创建一个src/views/TimeSelect.vue组件,并写入代码:

@tab TimeSelct.vue

:collapsed-lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<div class="demo-time-range">
<h2>活动时间</h2>
<el-time-select
v-model="startTime"
placeholder="活动开始时间"
start="06:00"
step="00:10"
end="13:00"
>
</el-time-select>
<el-time-select
v-model="endTime"
:min-time="startTime"
placeholder="活动结束时间"
start="13:30"
step="00:10"
end="24:00"
editable
>
</el-time-select>
</div>
</template>

<script>
export default {
data() {
return {
startTime: "",
endTime: "",
};
},
};
</script>

<style>
.demo-time-range .el-select {
margin-right: 8px;
}
</style>

@tab App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div id="app">
<Timeselect />
</div>
</template>

<script>
import Timeselect from "./views/Timeselect.vue";

export default {
name: "App",
components: {
Timeselect,
},
};
</script>

页面效果: :::: demo-wrapper no-padding ::: center 20250226174528 ::: ::::

在 Element UI 中我们想给组件之间添加间隔,只能使用Divider 组件,它能在组件之间添加分割线。

:::: demo-wrapper no-padding ::: center hero ::: ::::

不过这样就需要添加一堆Divider组件,还是有点儿麻烦,一种更加简单的办法是使用Space组件。

我们可以在src/components文件夹下新建一个Space.vue组件,然后修改App.vue的代码。

@tab Space.vue

{4-10}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<el-slider v-model="size" style="width:50%" />
<el-space :size="size">
<el-link>链接一</el-link>
<el-link>链接二</el-link>
<el-link>链接三</el-link>
<el-link>链接四</el-link>
<el-link>链接五</el-link>
</el-space>
</div>
</template>
<script>
export default {
data() {
return {
size: 50,
};
},
};
</script>

@tab app.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div id="app">
<Space />
</div>
</template>

<script>
import Space from "./components/Space.vue";

export default {
name: "App",
components: {
Space,
},
};
</script>

运行效果如下:

::: demo-wrapper no-padding 20250226145633 :::

拖动Slidersize会跟着变化,间隔也会发生变化。

Element Plus提供了三种包管理器来安装:

1
npm install element-plus --save

安装成功后可以在package.json文件中看到相关依赖。当前,Element Plus还处在beta版本的开发当中。

除了包管理安装,还可以使用CDN直接引用来使用Element Plus

:collapsed-lines
1
2
3
4
5
6
7
8
<head>
<!-- 导入样式 -->
<link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css" />
<!-- 导入 Vue 3 -->
<script src="//unpkg.com/vue@next"></script>
<!-- 导入组件库 -->
<script src="//unpkg.com/element-plus"></script>
</head>

根据官方文档也可以使用手动引入。

这里就来讲一下使用插件Element Plus Vue Cli plugin来快速搭建Element Plus项目的方式。

  1. 步骤 1

    使用命令创建vuee-elementplus项目(基于Element Plus

    1
    2
    3
    vue create vue3-elementplus
    cd vue3-elementplus
    vue add element-plus

  2. 步骤 2

    根据安装过程进行选择: 20250226002623

  3. 步骤 3 在项目根目录创建一个vue.config.js文件

    :collapsed-lines
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const HOST = process.env.HOST;

    module.exports = {
    publicPath: "./",
    productionSourceMap: false,
    devServer: {
    host: HOST || "0.0.0.0",
    open: true,
    historyApiFallback: true,
    allowedHosts: 'all',
    client: {
    webSocketURL: 'ws://0.0.0.0:8080/ws',
    }
    },
    };

  4. 步骤 4

    运行项目

    1
    npm run serve

BOM 操作

BOM指的是浏览器对象模型:Browser Object Mode,通过操作 window 对象的属性和方法来实现与浏览器的交互。

BOM的构成如下图所示:

图片描述

其中,window对象是顶级对象,在 window 对象下面有一些重要的属性:

  • document:DOM 对象。

  • location:用于获取或设置文档当前 URL 的位置。

  • navigation:包含浏览器配置相关的信息。

  • history:用于操作浏览器的历史记录。

  • screen:用于获取屏幕设备信息。

    用户操作

    警告框:

    1
    alert(message)

    对话框:

    1
    const res = confirm(message)	// 根据用户点击确定或取消结果:true或者false

    弹出输入对话框(defaultValue为默认值占位值,可选):

    1
    2
    3
    4
    5
    prompt(message, defaultValue)	// 返回值为用户的输入文本

    // 参考实例
    const res = prompt('请输入姓名:', 'Alice')
    console.log('用户的输入结果:', res)

获取窗口尺寸

window 对象包含一些存储窗口尺寸的只读属性

属性 描 述
innerWidth 窗口的内部宽度
innerHeight 窗口的内部高度
outerWidth 整个浏览器窗口的宽度
outerHeight 整个浏览器窗口的高度

参考用例:

1
2
3
4
console.log('窗口的内部宽度:', innerWidth)	// 1797
console.log('窗口的内部高度:', innerHeight) // 889
console.log('整个浏览器窗口的宽度:', outerWidth) // 1797
console.log('整个浏览器窗口的高度:', outerHeight) // 976

获取屏幕尺寸

访问 window 对象的 screen 属性会返回一个 Screen 对象,它包含一些屏幕尺寸相关的只读属性

属性 描 述
screen.width 屏幕的宽度
screen.height 屏幕的高度
screen.availWidth 屏幕上可用的宽度
screen.availHeight 屏幕上可用的高度(不包括任务栏)

参考实例:

1
2
3
4
console.log('屏幕的宽度:', screen.width)	// 1797
console.log('屏幕的高度:', screen.height) // 1011
console.log('屏幕上可用的宽度:', screen.availWidth) // 1797
console.log('屏幕上可用的高度:', screen.availHeight) // 976

Location 对象

访问 window 对象的 location 属性会返回一个 Location 对象,它包含有关文档当前 URL 位置的信息。

属性 描 述
location.href 包含整个 URL 的字符串
location.protocol 包含 URL 协议方案的字符串
location.hostname 包含 URL 域名的字符串
location.pathname 包含开头的 / 后跟 URL 路径的字符串
location.search 包含开头的 ? 后跟 URL 的“查询字符串”
location.hash 包含开头的 # 后跟 URL 的片段标识符

参考用例:

1
2
3
4
console.log('整个 URL:', location.href)
console.log('URL 协议:', location.protocol)
console.log('URL 域名:', location.hostname)
console.log('URL 路径:', location.pathname)

此外,Location 对象还包含对 URL 进行操作的方法。

其中,assign() 方法可以使浏览器加载并显示指定 URL 处的页面:

1
location.assign(url)

reload() 方法会重新加载当前 URL,就像点击了刷新按钮一样。

1
location.reload()

History 对象

访问 window 对象的 history 属性会返回一个 History 对象,可以通过它操作浏览器的历史记录。

方法 描 述
location.go() 移动到历史记录中相对于当前页面的位置,例如 -1 表示上一页,1 表示下一页。参数为 0 则会重新加载当前页面。
location.back() 转到历史记录中的上一页,相当于点击浏览器的“后退”按钮
location.forward() 转到历史记录中的下一页,相当于点击浏览器的“前进”按钮
location.pushState() 向浏览器的历史记录中添加一个条目
location.replaceState() 修改当前历史记录条目

DOM 操作

DOM 的英文全称为 Document Object Model(文档对象模型),它是浏览器为每个窗口内的 HTML 页面在内存中创建的表示文档的结构。通过 DOM,我们可以使用 JavaScript 来对页面中的元素进行操作。

常用的 DOM 属性

常用的 DOM 属性如下表所示:

属性 描 述
document.title 获取文档的标题文本
document.URL 获取文档的 URL
document.head 获取文档的 <head> 元素
document.body 获取文档的 <body> 元素
document.forms 获取文档的 <form> 元素列表
document.images 获取文档的 <img> 元素列表
document.links 获取文档的 <a> 元素列表
document.scripts 获取文档的 <script> 元素列表

常用的 DOM 方法

我们可以使用下面这些方法从当前文档中获取元素节点:

方法 描 述
document.getElementById() 通过 id 属性获取元素
document.getElementsByClassName() 通过 class 属性获取元素列表
document.getElementsByTagName() 通过标签名获取元素列表
document.getElementsByName() 通过 name 属性获取元素列表
document.querySelector() 通过选择器获取第一个匹配的元素
document.querySelectorAll() 通过选择器获取所有匹配的元素列表

除了获取已有的元素节点,我们还可以使用下面这些方法创建新节点:

方法 描 述
document.createElement() 创建元素节点
document.createTextNode() 创建文本节点

基本 DOM 操作

常用的元素节点属性如下表所示:

属性 描 述
parentElement 获取父级元素
previousElementSibling 获取同级的前一个元素
nextElementSibling 获取同级的后一个元素
children 获取子级元素列表
firstElementChild 获取第一个子级元素
lastElementChild 获取最后一个子级元素

常用的元素节点方法如下表所示:

方法 描 述
cloneNode() 返回当前节点的副本(如果传入一个参数 true 则连同后代节点一起复制)
remove() 删除当前节点本身
removeChild(node) 从当前节点的子级列表中删除子级节点 node
replaceWith(node1, node2, ...) 将当前节点替换为一组其它节点或文本
prepend(node1, node2, ...) 在当前节点的子级列表开头添加一组新的子级节点或文本
append(node1, node2, ...) 在当前节点的子级列表末尾添加一组新的子级节点或文本
before(node1, node2, ...) 在当前节点的前面添加一组新的同级节点或文本
after(node1, node2, ...) 在当前节点的后面添加一组新的同级节点或文本
insertBefore(node, reference) 在子级节点 reference 的前面插入一个新节点 node

元素节点的方法参考以下示例:

在这个示例中:

  1. 使用 remove() 方法删除了 id="js"<li> 元素。
  2. 创建了一个新的 <li> 元素,并插入到 id="css"<li> 元素的前面。
  3. <h2> 元素的前面添加了一行文本。
  4. <h2> 元素复制,并添加到 <body> 元素内部的末尾位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文档</title>
</head>
<body>
<h2>Web 开发三剑客</h2>
<ul>
<li id="html">HTML</li>
<li id="css">CSS</li>
<li id="js">JavaScript</li>
</ul>
<script>
// 删除元素节点
const js = document.getElementById('js')
js.remove()

// 创建元素节点
const es = document.createElement('LI')
es.prepend('ES6')

// 插入新节点
const ul = document.getElementsByTagName('ul')[0]
const css = document.getElementById('css')
ul.insertBefore(es, css)

// 在节点的前面添加文本
const h2 = document.getElementsByTagName('h2')[0]
h2.before('Vue 是一套用于构建用户界面的渐进式框架。')

// 复制节点并在 body 中添加
const clone = h2.cloneNode(true)
document.body.append(clone)
</script>
</body>
</html>

预览效果:

图片描述

定时器

js中定时器有一次性定时器和重复执行定时器。

一次性定时器

全局 setTimeout() 函数设置一个定时器,一旦倒计时完成,就会执行一段指定的代码。

需要注意,定时器函数一般为异步函数。

设置定时器的方法如下:

1
2
3
4
5
6
7
8
// 使用格式:
setTimeout(functionRef, delay, param1, param2, /* …, */ paramN)

// 参考示例:
setTimeout(() => {
console.log('延迟一秒')
}, 1000)
console.log('其他代码')

setTimeout() 函数的返回值是一个正整数值,它代表了这个定时器的 ID。我们可以将这个值传递给 clearTimeout() 函数以取消定时。

1
2
3
4
5
6
7
8
9
10
11
12
// 参考示例:
const timer1 = setTimeout(() => {
console.log('延迟一秒')
}, 1000)
const timer2 = setTimeout(() => {
console.log('延迟两秒')
}, 2000)
const timer3 = setTimeout(() => {
console.log('延迟三秒')
}, 3000)
// 取消第二个定时器
clearTimeout(timer2)

重复定时器

全局 setInterval() 函数设置一个定时器,用于重复执行一段指定的代码,每次执行之间有固定的时间间隔。

其使用格式如下:

1
2
3
4
5
6
7
8
// 使用方法:
setInterval(functionRef, delay, param1, param2, /* …, */ paramN)

// 参考示例:
setInterval(() => {
console.log('重复执行')
}, 1000)
console.log('其他代码')

本地存储

本地存储是指在客户端存储数据。HTML5 为我们提供了两种 API,分别是 localStoragesessionStorage。二者的使用方法类似,都可以用来存储客户端临时信息,并且二者存储的数据格式均为 key/value 对的形式。

localStorage API

localStorage 对象是 HTML 5 新增的特性,主要用于本地存储。

在网络发展的早期,当没有其他选择时,cookie 被用于一般客户端数据存储目的。而在现在,更加推荐使用 localStorage 等现代存储 API。

localstoragecookie 主要有以下区别:

  • localStorage 解决了早期使用 cookie 存储遇到的存储空间不足的问题( 每条 cookie 的存储空间为 4k )
  • localStorage 一般浏览器支持的是 5M 大小,具体存储大小根据浏览器的不同会有所不同。
  • 相较于 cookie 而言,localStorage 中的信息不会被传输到服务器。

localStorage 对象提供的方法如下:

方法 说明
setItem(key, value) 保存数据到本地存储
getItem(key) 根据指定 key 从本地存储中获取数据
removeItem(key) 根据指定 key 从本地存储中移除数据
clear() 清除所有保存数据

存储数据

1
2
localStorage.setItem(key, value)	// 方法一
localStorage.key = value // 方法二,和方法一效果一样

读取数据

1
2
localStorage.getItem(key)	// 方法一
localStorage.key // 方法二,等效于前者

删除数据

1
2
// 根据指定名称从本地存储中移除
localStorage.removeItem(key)

上面的key一般是一个字符串。

清空数据

1
2
// 清除本地存储中所有数据
localStorage.clear()

sessionStorage API

localStoragesessionStorage 对象作为 HTML5 新增的特性,都可以用来存储客户端临时信息,并且二者存储的数据格式均为 key/value 键值对数据。

sessionStorage 对象提供的方法与 localStorage 对象相同,具体如下:

方法 说明
setItem(key, value) 保存数据到本地存储
getItem(key) 根据指定 key 从本地存储中获取数据
removeItem(key) 根据指定 key 从本地存储中移除数据
clear() 清除所有保存数据

那么localStoragesessionStorage 二者有什么区别呢?

它们的区别在于:

  • localStorage 的生命周期是永久的,除非用户清除 localStorage 信息,否则这些信息将永远存在。
  • sessionStorage 的生命周期是临时的,一旦当前窗口或标签页被关闭了,那么通过它存储的数据也就被清空了。

由于具体的调用方法和localStorage完全一致,使用方法这里省略。

事件处理

事件是指用户进行了某些操作时触发的“信号”,例如点击鼠标、按下键盘、输入文字等。我们可以绑定相应的事件处理函数来进行处理。

  • DOM 0 级事件与 DOM 2 级事件
  • 鼠标事件
  • 键盘事件
  • 表单事件
  • 事件对象

DOM 0 级事件

DOM 0 级事件是直接使用 HTML 属性或 DOM 对象属性来指定相应的事件处理函数。例如,click 是当鼠标点击时会触发的事件。我们可以在 HTML 标签里直接写 onclick 属性或者在 JavaScript 中使用 onclick = function(){}

直接将节点的onclick绑定为一个函数,点击事件就只能执行一个函数。但如果添加事件监听,就能同时执行多个事件所绑定的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 直接绑定
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文档</title>
</head>
<body>
<input id="btn" type="button" value="按钮" onclick="alert('欢迎来到蓝桥云课')" />
<script>
const el = document.getElementById('btn')
el.onclick = function () {
alert('你好!蓝桥')
}
el.onclick = function () {
alert('嗨!蓝桥')
} // 再次绑定就会被覆盖
</script>
</body>
</html>

DOM 2 级事件

DOM 2 级事件可以绑定多个事件处理函数。所有的 DOM 节点都有两个方法,分别是 addEvenetListener()removeEventListener()

语法格式:

1
2
target.addEvenetListener(type, listener) // 添加事件
target.removeEventListener(type, listener) // 移出事件

listener是一个函数,如果要移除他需要保证removeEventListener 中传入的是同一个函数名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文档</title>
</head>
<body>
<input id="btn" type="button" value="按钮" />
<script>
const btn = document.getElementById('btn')
btn.addEventListener('click', handler)
function handler() {
alert('已点击')
btn.removeEventListener('click', handler)
}
</script>
</body>
</html>

执行后的效果如下:

图片描述

鼠标事件

常用的鼠标事件如下表所示:

事件 说明
click 鼠标点击事件
mousedown 鼠标按下事件
mouseup 鼠标松开事件
mouseover 鼠标移入事件
mouseout 鼠标移出事件
mousemove 鼠标移动事件

click 事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文档</title>
<style>
div {
width: 200px;
height: 200px;
background-color: #b8b5ff;
}
</style>
</head>
<body>
<div id="item"></div>
<script>
const el = document.getElementById('item')
// 鼠标点击
el.addEventListener('click', function () {
el.style.background = '#ffefa1'
})
</script>
</body>
</html>

mousedownmouseup 事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文档</title>
<style>
div {
width: 200px;
height: 200px;
background-color: #b8b5ff;
}
</style>
</head>
<body>
<div id="item"></div>
<script>
const el = document.getElementById('item')
// 鼠标按下
el.addEventListener('mousedown', function () {
el.style.background = '#ffefa1'
})
// 鼠标松开
el.addEventListener('mouseup', function () {
el.style.background = '#b8b5ff'
})
</script>
</body>
</html>

mouseovermouseout 事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文档</title>
<style>
div {
width: 200px;
height: 200px;
background-color: #b8b5ff;
}
</style>
</head>
<body>
<div id="item"></div>
<script>
const el = document.getElementById('item')
// 鼠标移入
el.addEventListener('mouseover', function () {
el.style.background = '#ffefa1'
})
// 鼠标移出
el.addEventListener('mouseout', function () {
el.style.background = '#b8b5ff'
})
</script>
</body>
</html>

键盘事件

常用的键盘事件有以下两个:

事件 说明
keydown 键盘按下会触发的事件
keyup 键盘松开会触发的事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文档</title>
</head>
<body>
<input type="text" value="请输入内容" id="phone" />
<script>
const el = document.getElementById('phone')
// 键盘按下
el.addEventListener('keydown', function () {
el.style.color = '#00adb5'
})
// 键盘松开
el.addEventListener('keyup', function () {
el.style.color = '#000000'
})
</script>
</body>
</html>

表单事件

在 JavaScript 中,常用表单事件如下表所示:

事件 说明
focus 表单元素聚焦时触发的事件
blur 表单元素失焦时触发的事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文档</title>
</head>
<body>
姓名:<input type="text" id="username" value="输入你的名字" />
<script>
const el = document.getElementById('username')
// 当聚焦到该输入框时,把输入框的内容置为空,并设置字体颜色为蓝色
el.addEventListener('focus', function () {
if (el.value == '输入你的名字') {
el.value = ''
}
el.style.color = '#77acf1'
})
// 当失去焦点时,显示输入框的默认内容
el.addEventListener('blur', function () {
if (el.value == '') {
el.value = '输入你的名字'
}
el.style.color = '#000000'
})
</script>
</body>
</html>

事件对象

事件函数默认能接受到一个可选参数:事件对象,通过事件对象可以得到更多关于该类型事件的信息。例如鼠标事件可以拿到鼠标的位置坐标,键盘事件能拿到对应按下的键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文档</title>
</head>
<body>
<input id="btn" type="button" value="按钮" />
<script>
const el = document.getElementById('btn')
el.addEventListener('click', function (ev) {
console.log(`这是一个 ${ev.type} 事件`) // 在控制台打印事件类型
})
</script>
</body>
</html>

鼠标事件对象

鼠标事件处理函数接收到的鼠标事件对象还包含一些其它属性:

属性 说明
button 触发鼠标事件时按下的按钮
clientX 鼠标指针在窗口可视区域中的 X 坐标
clientY 鼠标指针在窗口可视区域中的 Y 坐标
pageX 鼠标指针相对于整个页面的 X 坐标(考虑滚动条)
pageY 鼠标指针相对于整个页面的 Y 坐标(考虑滚动条)
movementX 鼠标指针相对于上次 mousemove 事件位置的 X 坐标
movementY 鼠标指针相对于上次 mousemove 事件位置的 Y 坐标

参考以下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文档</title>
<style>
div {
width: 200px;
height: 200px;
background-color: #b8b5ff;
}
</style>
</head>
<body>
<div id="item"></div>
<script>
const el = document.getElementById('item')
el.addEventListener('click', function (ev) {
console.log('页面中鼠标指针的 X 坐标:', ev.pageX)
console.log('页面中鼠标指针的 Y 坐标:', ev.pageY)
})
</script>
</body>
</html>

键盘事件对象

键盘事件处理函数接收到的键盘事件对象包含一些按键信息相关的属性:

属性 说明
code 键盘上的按键的代码值
key 按键产生的字符(考虑大小写)
shiftKey 是否按下 Shift 键
ctrlkey 是否按下 Ctrl 键
altkey 是否按下 Alt 键

参考以下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文档</title>
</head>
<body>
<input type="text" id="item" />
<p id="msg"></p>
<script>
const el = document.getElementById('item')
el.addEventListener('keydown', function (ev) {
// 判断是否按下 Ctrl 键
if (ev.ctrlKey) {
msg.innerHTML = '请不要按下 Ctrl 键'
msg.style.color = 'red'
} else {
// 当没有按下 Ctrl 键时,输出按键产生的字符
msg.innerHTML = '按键产生的字符:' + ev.key
msg.style.color = 'black'
}
})
</script>
</body>
</html>

AJAX

AJAX 的英文全称为 Asynchronous JavaScript And XML,其中 Asynchronous 是异步的意思。

何为异步呢?它是指通过 AJAX 向服务器请求数据,在不刷新整个页面的情况下,更新页面上的部分内容。

其工作原理图如下所示:

图片描述

使用AJAX请求的功能如果餐厅中的服务员,能在不阻塞主要流程的情况下,让服务员帮你去做某件事情。如果这件事情你自己去做的话,就会阻塞你的事件进程了。

常用的三种AJAX:

  • XMLHttpRequest API
  • Fetch API
  • Axios

XMLHttpRequest API

为了通过 AJAX 异步请求数据,一种传统的方法是使用 XMLHttpRequest API。

创建 AJAX 的基本步骤如下:

  1. 创建 XMLHttpRequest 对象
1
const httpRequest = new XMLHttpRequest()
  1. 向服务器发送请求
1
2
3
4
// 规定发送请求的一些要求
httpRequest.open(method, url, async)
// 将请求发送到服务器
httpRequest.send()

open() 方法中的参数说明如下:

  • method:请求的类型,常见的有 GETPOST
  • url:请求的 URL 地址。
  • async(可选):设置同步或者异步请求,其值为布尔类型,默认为 true。当为 true 时,使用异步请求;当为 false 时,使用同步请求。
  1. 获取服务器响应状态 我们使用 HTTP 请求数据后,会反馈给我们相应的请求状态。我们使用 onreadystatechange 去检查响应的状态,当 httpRequest.readyState 为 4 并且 httpRequest.status 等于 200 时,说明数据请求成功。

其使用如下:

1
2
3
4
5
6
7
8
9
10
// 检查响应的状态
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState === 4) {
if (httpRequest.status == 200) {
// 请求成功执行的代码
} else {
// 请求失败执行的代码
}
}
}

新建一个 index.html 文件,在 <script> 标签内写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const xhr = new XMLHttpRequest()
// 规定发送请求的一些要求
xhr.open('GET', 'https://jsonplaceholder.typicode.com/users', true)
// 将请求发送到服务器
xhr.send()
// 检查响应的状态
xhr.onreadystatechange = function () {
console.log(xhr.readyState)
console.log(xhr.status)
if (xhr.readyState === 4) {
if (xhr.status == 200) {
// 请求成功执行的代码
console.log('请求成功')
console.log(JSON.parse(xhr.responseText))
} else {
// 请求失败执行的代码
console.log('请求失败')
}
}
}

输出结果如下:

图片描述

在控制台中输出的 200 是 HTTP 的响应状态码,该状态码还有其他取值,可以阅读 HTTP response status codes 了解更多。

而穿插在 200 之后的数字 234readyState 属性的值,它的取值有以下几种:

  • 0 代表未初始化请求。
  • 1 代表已与服务器建立连接。
  • 2 代表请求被接受。
  • 3 代表请求中。
  • 4 代表请求完成。

Fetch API

Fetch API 提供了用于通过网络获取资源的接口,它是 XMLHttpRequest API 的更强大、更灵活的替代品。其使用方式如下:

1
const response = await fetch(url)

其中,fetch() 是一个全局函数,它接收要请求的 URL 作为参数,并返回一个 Promise 对象。

该异步操作的结果是一个 Response 对象,我们可以使用 await 关键字获取。它提供了多种方法来解析不同格式的正文内容:

  • arrayBuffer():二进制数据。
  • blob():二进制数据。
  • formData():HTML 表单数据。
  • json():JSON 格式数据。
  • text():纯文本数据。

下面是一个基本的使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
async function getData() {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
if (!response.ok) {
// 请求失败执行的代码
console.log('请求失败')
} else {
// 请求成功执行的代码
console.log('请求成功')
const json = await response.json()
console.log(json)
}
}
getData()

默认情况下,fetch() 发出 GET 请求,但我们可以使用 method 选项来使用不同的请求方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function getData() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify({ title: 'foo', body: 'bar', userId: 1 }),
headers: { 'Content-type': 'application/json; charset=UTF-8' },
})

if (!response.ok) {
// 请求失败执行的代码
console.log('请求失败')
} else {
// 请求成功执行的代码
console.log('请求成功')
const json = await response.json()
console.log(json)
}
}
getData()

在上面的代码中:

  • method 选项用于设置请求方式。
  • body 选项用于设置发送到服务器的内容。
  • headers 选项用于设置 HTTP 请求头。

Axios

一个非常主流的AJAX的封装插件—— Axios

Axios 是一个基于 Promise 语法的、用于浏览器和 Node.js 的 HTTP 库。简单的理解就是对 AJAX 的封装,且具有易用、简洁、高效等特点。

它本身具备以下功能:

  1. 可以从浏览器中创建 XMLHttpRequest。
  2. 能从 Node.js 创建 HTTP 请求。
  3. 支持 Promise API。
  4. 能够拦截请求和响应。
  5. 可以转换请求和响应数据。
  6. 可以取消请求。
  7. 可以自动转换 JSON 数据。
  8. 在客户端支持防止 CSRF/XSRF 攻击。

为了使用 Axios,我们需要使用 <script> 标签进行引入:

1
<script src="https://unpkg.com/axios@1.7.7/dist/axios.min.js"></script>

新建一个 test.json 文件,并写入以下数据,作为接下来使用 Axios 请求的数据文件:

1
2
3
{
"msg": "Hello Axios!"
}

新建一个 index.html 文件,写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文档</title>
<!-- 引入 Axios 的 CDN -->
<script src="https://unpkg.com/axios@1.7.7/dist/axios.min.js"></script>
</head>
<body>
<script>
axios.get('./test.json').then((res) => {
console.log(res)
})
</script>
</body>
</html>

在上面代码中,我们使用 Axios 发送一个简单的 AJAX 请求,用于获取 test.json 中的数据,并输出在控制台。

可以看到,通过 Axios 获取到的数据实际上是一个对象,真正需要的数据是该对象的 data 属性值。

上面这个例子只是 Axios 众多使用方式中的一种,它主要是用于执行 GET 请求。

下面我们看几个它比较常用的使用方式:

  1. 执行 GET 数据请求:
1
2
3
4
5
6
7
8
9
10
11
12
axios
.get('url', {
params: {
id: '接口配置参数(相当于url?id=xxxx)',
},
})
.then(function (res) {
console.log(res) // 处理成功的函数 相当于 success
})
.catch(function (error) {
console.log(error) // 错误处理 相当于 error
})
  1. 执行 POST 数据请求并发送数据给后端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
axios
.post(
'url',
{ data: {} },
{
headers: 'xxxx', // 头部配置
}
)
.then(function (res) {
console.log(res) // 处理成功的函数 相当于 success
})
.catch(function (error) {
console.log(error) // 错误处理 相当于 error
})
  1. 通用方式(适用于任何请求方式):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//-------- GET --------//
axios({
method: 'get',
url: 'xxx',
cache: false,
params: {
id: 123,
},
headers: 'xxx',
})
//-------- POST --------//
axios({
method: 'post',
url: 'xxx',
data: {
firstName: 'Tom',
lastName: 'Sun',
},
})

其中需要注意的是,GET 和 POST 请求中向后端传递参数的配置项名字不同:GET 请求需要使用 params,POST 请求需要使用 data 发送数据。

作为一个独立的强大的 HTTP 库,Axios 的功能远不止这些,可以通过 Axios 的官网学习。

判断一个数据的类型,常用的方法有以下几种:

  • typeof
  • instanceof
  • Object.prototype.toString.call(xxx)

下面来分别分析一下这三种方法各自的优缺点

typeof

typeof的本意是用来判断一个数据的数据类型,所以返回的也是一个数据类型。但是会遇到下面这些问题:

  • 无法判断 null
  • 无法判断除了 function 之外的引用类型。
1
2
3
4
5
6
// 无法判断 null。
console.log(typeof null); // 输出 'object',原因在文章末尾解释。

// 无法判断除了 function 之外的引用类型。
console.log(typeof []); // 'object'
console.log(typeof {}); // 'object'

incetance of

可以看到,type of无法精确判断对象的引用类型。所以在判断一个对象的引用类型时一般使用incetance of关键字。

1
2
console.log([] instanceof Array); // true
console.log(str1 instanceof String); // false,无法判断原始类型。

但是incetance of无法准确判断原始数据类型,只能用来判断数据是否是某个类的引用。到这里就能发现,如果把incetance oftype of结合起来基本就能判断所有的数据类型了。

但是,别忘记还有一个null,对于null还需要进行特殊的处理。

1
2
3
4
5
typeof null;	// object

if (target === null) {
return "null";
}

结合这两种方法基本已经掌握了判断数据类型的手段了,但是如果去写一下你还是会发现很麻烦,你必须枚举每一种类型利用trueorfalse判断数据类型。

这里的null必须单独判断,因为这是第一版JavaScript留下来的一个bug。

JavaScript 中不同对象在底层都表示为二进制,而 JavaScript 中会把二进制前三位都为 0 的判断为 object 类型,而 null 的二进制表示全都是 0,自然前三位也是 0,所以执行 typeof 时会返回 'object'

这个 bug 牵扯了太多的 Web 系统,一旦改了,会产生更多的 bug,令很多系统无法工作,也许这个 bug 永远都不会修复了。

Object.prototype.toString.call(xxx)

这个时候就不得不提到下面这种方法了:

1
Object.prototype.toString.call([])	// [object Array]

这个方法会返回统一格式的字符串:[object Xxx]。然后再取出后面的xxx即可得到准确的数据类型。对于取出后面的xxx可以使用多种方法,包括但不限于字符切片、正则表达式。

这里调用call()方法是为了让this指向数组对象自身。

String 对象扩展

模版字符串

类似字符串的写法,用 ` 来包裹字符串,优点是可以不用反斜杠就能在代码中多行编辑。对于模版字符串来说,反引号内任何空格、换行符都不会被省略。并且可以使用占位变量的写法:

1
2
3
4
5
6
7
8
9
const value = 114514
// 传统派写法
const str = "第一行\n\
第二行:"
// 模版字符串写法明显简洁
const str2 = `第一行
第二行:${value}`
console.log(str, value, '\n')
console.log(str2)

输出

截屏2024-12-23 00.19.17

indexOf() 与 lastIndexOf()

indexOf()

使用格式:

1
str.indexOf(searchString, position)

该方法用于返回搜索字符串的索引位置,positoin为可选参数(起始位置),也就是从头开始寻找。下面是代码实例:

1
2
3
const str = 'HelloJavaScript'
console.log('a 首次出现的位置:', str.indexOf('a'))
console.log('a 第二次出现的位置:', str.indexOf('a', 7))

输出

输出结果

lastIndexOf()

如果说indexOf()是从左往右在字符串中寻找目标,那么lastIndexOf()就是从右往左,也就是从后面往前找。使用方法:

1
str.lastIndexOf(searchString, position)

与之对应的,position是可选参数(起始位置),默认从最后开始寻找。

includes()

该方法用于判断某字符串是否“包含”在内,如果存在则返回true否则false。功能和正则表达式的test()方法一致,区别在于可以指定查找开始的索引位置。test()方法做不到这一点。

1
str.includes(searchString, position)

演示:

1
2
const str = 'HelloJavaScript'
console.log('str 字符串中是否存在 Java:', str.includes('Java'))

输出:

输出

startsWith() 与 endsWith()

startsWith()用于判读字符串的开头是否是某个字符串,endsWith()方法用于判断末尾是否是某个字符结尾,返回true或者false

实例:

1
2
3
4
const str = 'LanQiao Courses'
console.log('str 字符串中是否存在 Java:', str.includes('Java'))
console.log('str 字符串的开头是否存在字符 Lan:', str.startsWith('Lan'))
console.log('str 字符串的结尾是否存在字符 Course:', str.endsWith('Course'))
输出

总结

String类在ES6中有如下扩展方法:

  • indexOf() & lastIndexOf():从某个位置左往右或从右往左查找字串索引。
  • startsWith() & endsWith():查找是否以某个字符串开头或结尾。
  • Includes():从某个位置开始,判断字符串是否为子串。

Array 对象扩展

扩展运算符

使用...在一个数组对象的前面,可以将这个数组的拆开后均摊出来,使用方法:

1
const variableName = [...value]

value是一个数组,使用...可以将它拆开后摊到数组中,于是乎variableName变成了数组。

使用实例:

1
2
3
const animals = ['兔子🐰', '猫咪🐱']
const zoo = [...animals, '老虎🐯', '乌龟🐢', '鱼🐟']
console.log(zoo)

输出结果:

图片描述

Array.of()

该方法用于创建一个数组:

1
Array.of(元素0, 元素1, /* ... */, 元素N)

返回一个数组,和正常创建的数组一样。

Array.from()

该方法可以将一个特定格式的对象(甚至是更多数据结构的可迭代器)转换成数组作为返回值,第二个可选参数是类似Array.prototype.map()方法的函数,可以对数组迭代一遍进行改造:

1
cosnt arr = Array.from(待转换的对象, mapFn());

被转换的对象需要如下格式:

1
2
const arrLike = { 0: '🍎', 1: '🍐', 2: '🍊', 3: '🍇', length: 4 }
const arr = Array.from(arrLike)

输出:

图片描述

关于Arrray.from()更加详细的使用建议参考MDN:

  • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from

熟练掌握后就能写出下方图片中两种很高效的数组创建写法:

截屏2025-01-17 03.03.13

indexOf() 和 lastIndexOf()

使用方法和字符串的查找字串返回索引一样:

1
2
arr.indexOf(searchElement, fromIndex)
arr.lastIndexOf(searchElement, fromIndex)

需要说明的是,前者是从前往后,后者是从后往前查找返回找到的索引,如果没有返回-1

find() 和 findLast()

该方法不同于indexOf()的地方在于,可以寻找第一个满足构造方法中条件的值。如果查找失败返回undefined

1
2
arr.find(callbackFn, thisArg)
arr.findLast(callbackFn, thisArg)

使用方法:

1
2
3
4
5
const arr = [1, 3, 4, 5]
const result = arr.find(function (value) {
return value > 2
})
console.log('find() 的结果:', result)

findLast方法是从后往前找。

findIndex() 和 findLastIndex()

这两个方法用于查找满足构造函数的值在数组中的索引。匹配失败返回-1。

使用方法:

1
2
arr.findIndex(callbackFn, thisArg)
arr.findLastIndex(callbackFn, thisArg)

对于callbackFn可以按顺序传入下面的参数:

  • element,数组中元素迭代的值。
  • index,迭代值的数组下标。
  • array,被操作数组。

实例:

1
2
3
4
5
const arr = ['小猫', '兔子', '小狗', '兔子']
const result = arr.findIndex(function (value) {
return value == '兔子'
})
console.log('findIndex() 的结果:', result)

输出:

图片描述

includes()

该方法判断数组中是否有某个元素,返回布尔值。和字符串中的includes()方法一样。

1
arr.includes(searchElement, fromIndex)

some()

该方法用于判断数组中是否有满足某个条件的值,返回布尔值。

1
arr.some(callbackFn, thisArg)

回调函数的参数也是按照elementindexarray的顺序可选传入的。

实例:

1
2
3
4
5
const arr = [1, 3, 4, 5]
const result = arr.some(function (value) {
return value > 3
})
console.log('存在大于 3 的元素?', result)

输出:

图片描述

every()

判断数组中所有的元素是否满足某个条件。参数中callbackFn的使用方法和之前一致,不多赘述。

1
arr.every(callbackFn, thisArg)

参考实例:

1
2
3
4
5
const arr = [1, 3, 4, 5]
const result = arr.every(function (value) {
return value > 3
})
console.log('所有元素均大于 3 ?', result)

输出:

图片描述

sort()

对数组进行排序:

1
arr.sort(compareFn)

实例:

1
2
3
4
5
const arr = [2, 3, 4, 8, 1]
const result = arr.sort(function (a, b) {
return a - b
})
console.log(result)

对于迭代器中传入的ab有如下解释,如果返回值为负值表示ab前,正值表示ab后,0位置不变。

reserve()

该方法可以反转数组。使用格式:

1
arr.reverse();

实例:

1
2
3
const arr = ['一', '二', '三', '四']
arr.reverse()
console.log(arr)

输出:

图片描述

fill()

该方法用于指定一个值对数组进行切片填充。

1
array.fill(value, start, end);

如果不指定startend,默认填充整个数组。

参考:

1
2
3
const arr = ['🐱', '🐶', '🐰']
arr.fill('🐷')
console.log(arr)

输出:

图片描述

map()

该方法可以对数组中的所有元素进行操作后返回成一个新的数组。回调函数的参数表和上方一致。

使用方法:

1
arr.map(callbackFn, thisArg)

参考:

1
2
3
4
5
const arr = [1, 4, 9, 16]
const result = arr.map(function (x) {
return x * 2
})
console.log(result)

输出:

图片描述

reduce() 和 reduceRight()

1
2
arr.reduce(callbackFn, initialValue)
arr.reduceRight(callbackFn, initialValue)

该方法可以指定一个初始值,迭代数组后返回计算出来的最终值。

实例:

1
2
3
4
5
const arr = [1, 2, 3, 4]
const sum = arr.reduce(function (acc, cur) {
return acc + cur
}, 0)
console.log(sum) // 10

比如这段代码,指定了初始值为0,每次迭代将这个值加上迭代的元素,最终得到求和的值返回赋给sum

输出:

1
10

splice()

首先来看一下MDN官方的接口文档是怎么写的:

1
2
3
4
5
splice(start)
splice(start, deleteCount)
splice(start, deleteCount, item1)
splice(start, deleteCount, item1, item2)
splice(start, deleteCount, item1, item2, /* …, */ itemN)

可见第一个参数是开始的位置,第二个参数是删除的数量,第三个参数开始全是删除后在该位置插入的元素,并且需要注意该方法会将删除的元素作为返回值返回,且直接操作于原数组

由此可见,Array.prototype.splice()接口可以胜任数组中子元素的删除、增加、替换等操作。具体实现方法请看官方文档,这里演示几个简单的操作:

  • 在索引2处移除0个元素,并插入drum

    可以看到,最终在原来数组索引2元素'mandarin的前面插入了一个元素,使得新插入的元素索引为2,这个操作等价于replace()

1
2
3
4
5
const myFish = ["angel", "clown", "mandarin", "sturgeon"];
const removed = myFish.splice(2, 0, "drum");

// myFish 是 ["angel", "clown", "drum", "mandarin", "sturgeon"]
// removed 是 [],没有移除的元素
  • 在索引2处移除一个元素:

如果需要更强大的移除操作,使用Array.prototype.filter()才是上上策。

1
2
3
4
5
const myFish = ["angel", "clown", "mandarin", "sturgeon"];
const removed = myFish.splice(2, 1);

// myFish 是 ["angel", "clown", "sturgeon"];
// removed 是 ["mandarin"]
  • 在索引2处移除0个元素,并插入”parrot”,“anemone”和”blue”:
1
2
3
4
5
const myFish = ["angel", "clown", "trumpet", "sturgeon"];
const removed = myFish.splice(0, 2, "parrot", "anemone", "blue");

// myFish 是 ["parrot", "anemone", "blue", "trumpet", "sturgeon"]
// removed 是 ["angel", "clown"]

entries()、keys()、values()

使用arr.entries()可以得到包含arr键值对的二维数组。使用keys()可以得到一个包含键的数组,values()得到一个包含所有值的数组。利用这三个数组可以进行针对性的迭代。

特别的,直接输出entries()得到的是一个迭代器,不过你可以直接将它使用扩展运算符均摊到数组中查看。

1
2
const arr = ['🐱', '🐶', '🐰', '🐍', '🐦', '🐟']
console.log(arr.entries())

输出:

1
> Array Iterator {}

使用扩展运算符:

1
2
const arr = ['🐱', '🐶', '🐰', '🐍', '🐦', '🐟']
console.log([...arr.entries()])

得到结果:

图片描述

还可以使用for ... of进行迭代:

1
2
3
4
const arr = ['🐱', '🐶', '🐰', '🐍', '🐦', '🐟']
for (const item of arr.entries()) {
console.log(item)
}
图片描述

总结

  • 扩展运算符
  • 两种创建数组的方法:
    • Array.of() 方法:将一组指定的值转换为数组。
    • Array.from() 方法:将类数组对象或者可迭代对象转换为数组。
  • 六种数组查找方法:
    • indexOf()lastIndexOf() 方法:查找指定元素的下标值。
    • find()findLast() 方法:返回数组中满足指定条件的元素的值,未找到则返回 undefined
    • findIndex()findLastIndex() 方法:返回数组中满足指定条件的元素的索引,未找到则返回 -1
  • 三种数组测试方法:
    • includes() 方法:判断数组中是否包含某个元素。
    • some() 方法:测试数组中是否存在至少一个元素满足特定要求。
    • every() 方法:测试数组中是否所有的元素均满足特定要求。
  • 数组实例的其他方法:
    • sort() 方法:给数组中的元素进行排序。
    • reverse() 方法:将数组中的元素进行逆序排列。
    • fill() 方法:用一个固定值去填充数组中指定索引位置的数组值。
    • map() 方法:对数组中的每个元素执行一次回调函数,返回由执行结果构成的新数组。
    • reduce()reduceRight() 方法:依次对数组的每个元素执行回调函数,并传入前一次执行的返回值。
    • entries()keys()values() 方法:返回一个数组迭代器对象。

Promise 对象

Promise对象用于解决Javascript中的地狱回调问题,有效的减少了程序回调的嵌套调用。

创建

如果要创建一个Promise对象,最简单的方法就是直接new一个。但是,如果深入学习,会发现使用Promise下的静态方法Promise.resolve()也能创建一个Promise对象:

1
2
3
4
5
6
7
// 创建方法一
new Promise((resolve, reject) => {
// 此处做一个异步的事情
});

// 创建方法二
Promise.resolve(p) // p 可以是一个Promise,也可以是一个普通的数值。

使用方法二创建Promise时,可以传入一个普通的值,或一个Promise对象。最后都会作为一个Promise返回出来。如果传入的是一个普通的值,产生的Promise的值就会将这个值传入resolve方法发送给下一个then

使用

对于Promise对象的使用,参考下方的案例,对于Promise的使用,理解返回值、参数、两个回调之间的关系后会有一定的帮助。

第二种写法的区别主要在于直接在第一次定义Promise的同时把下一次then中的回调也顺便地写好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 案例一
const n = 6
const p = new Promise((resolve, reject) => {
setTimeout(() => {
if (n > 5) {
resolve(n)
} else {
reject('必须大于 5!')
}
}, 1000)
})
p.then(
(v) => {
console.log(v)
},
(e) => {
console.log(e)
}
)
// 案例二
const pFn = function() {
return Promise.resolve('解决!').then(
v => {
console.log('接收到', v);
}
)
}
const p = pFn()

Promise.all() 方法

该方法用于一次性执行全部传入的[p1, p2, p3]对象,当全部执行成功后才会进入到第一个执行成功的then方法中。其中,任何一个失败了则会进入到then的失败回调中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 语法演示的伪代码
Promise.all([p1, p2, p3]).then(
(v) => {
// 所有请求成功后的操作步骤
},
(e) => {
// 某一个请求失败后的操作步骤
}
)

// 演示案例
function p(n) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (n > 0) {
resolve(n)
} else {
reject('不能小于 0!')
}
}, 1000)
})
}
Promise.all([p(5), p(6), p(7)]).then(
(v) => {
console.log(v)
},
(e) => {
console.log(e)
}
)

Promise.race() 方法

如果race的字面意思竞赛,该方法也是传入一个Promise对象的数组,不同点在于:先成功的Promise将直接进入到then的成功回调中。如果失败了,也直接进入到失败的then回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function loadData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('请求成功')
}, 3000)
})
}
function timeOut() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求超时')
}, 5000)
})
}
Promise.race([loadData(), timeOut()]).then(
(v) => {
console.log(v)
},
(e) => {
console.log(e)
}
)

async 和 await 关键字

这两个关键字是Promise方法的语法糖,底层的实现还是Promise对象的那一套。优点在于能使异步编程的可读性进一步加强,使其更接近于同步执行的语法。

  • async 关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// async 语法糖的写法
async function fn() {
return '12345'
}
fn().then((v) => {
console.log(v)
})
// 等同于下方的写法
function fn() {
return Promise.resolve('12345')
}
fn().then((v) => {
console.log(v)
})
  • await 关键字

这个关键字必须在async函数中使用。用于“等待” await后的表达式执行,并接受该表达式的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 函数 p() 返回的是一个 Promise 对象,
// 延时 1 秒后执行成功回调函数,相当于模拟一次异步请求
function p(msg) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 将函数 p() 的实参值 msg 作为执行成功回调函数的返回值
resolve(msg)
}, 1000)
})
}

// 一个用于正常输出内容的函数
function log() {
console.log('2. 正在操作')
}

async function fn() {
console.log('1. 开始')
await log()
let p1 = await p('3. 异步请求')
console.log(p1)
console.log('4. 结束')
}
fn()

最后的执行顺序参考下图:

图片描述

Proxy 代理

通过Proxy代理可以为对象拦截一些特定的操作,proxy对象对于原对象的操作最终会转发给原对象,并且proxy对于原对象的值都只是引用的。

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 伪代码
const proxy = new Proxy(target, handler)

// 实际例子
const target = {}
const proxy = new Proxy(target, {})

proxy.name = '闷墩儿'
console.log(proxy.name)
console.log(target.name)

target.name = '憨憨'
console.log(proxy.name)
console.log(target.name)

其中最常用的拦截方法:

拦截方法 方法说明
get(target, propKey, receiver) 拦截对象属性的读取。
set(target, propKey, value, receiver) 拦截对象属性的设置。
has(target, propKey) 拦截 propKey in proxy 的操作。
ownKeys(target) 拦截 Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in 循环,返回一个数组。

get 方法

通过在handler对象中 加入get方法来使用,该方法会在请求原对象(target)的某一键(propKey)的值时调用,并且原对象和键都会作为get的回调参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
const dog = { name: '闷墩儿' }
const proxy = new Proxy(dog, {
get(target, propKey) {
// 遍历目标对象的属性键值
if (propKey in target) {
return target[propKey] // 返回相应的属性值
} else {
throw new ReferenceError(propKey + ' 属性不存在')
}
},
})
console.log('访问 dog 对象中的 name 属性值为:' + proxy.name)
console.log('访问不存在的 age 属性:' + proxy.age)

set 方法

set会在你想设置原对象(target)的某一键(propKey),并将该键对应的值设置成你传入的值(value)时调用。额外需要知道的是返回值为设置成功与否的boolean值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const validator = {
set(target, propKey, value) {
if (propKey === 'age') {
// 判断 age 属性值是否时数字
if (!Number.isInteger(value)) {
throw new TypeError('狗狗的年龄只能是整型哦!')
}
}
target[propKey] = value
return true
},
}

const dog = new Proxy({}, validator)
dog.age = '22'

has 方法

该方法在使用in查询属性时调用,该方法可以解决继承时属性继承出现的问题:

场景一中:valueOf实际上是Object的属性,因为dog默认继承自Object所以该属性默认也是dog的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 场景一:解决的问题
const dog = { name: '闷墩儿' }
console.log('name' in dog)
console.log('valueOf' in dog)

// 场景二:使用实例
const dog = { name: '闷墩儿', age: 2 }
const handler = {
has(target, propKey) {
if (propKey == 'age' && target[propKey] < 5) {
console.log(`${target.name}的年龄小于 5 岁哦!`)
return true
}
},
}
const proxy = new Proxy(dog, handler)

console.log('age' in proxy)

ownKeys

在使用迭代方法例如for...in迭代对象的键时可以使用ownKeys拦截该迭代,并返回你想给的迭代数组。

注意,你给的数组中的元素如果不是原对象的属性,将不会被迭代。

1
2
3
4
5
6
7
8
9
10
let dog = { name: '闷墩儿', age: 2, food: '狗罐头' }
const proxy = new Proxy(dog, {
ownKeys() {
return ['name', 'color']
},
})

for (let key in proxy) {
console.log(key) // 输出 name
}

Set 对象

添加元素

1
set.add(value)

常用方法

方法 描述
has() 判断 Set 对象中特定元素是否存在
delete() Set 对象中删除指定元素
clear() 清空 Set 对象

遍历方法

很容易想到使用set.forEach(callBackFn, thisArg)方法来进行遍历,其中callBackFn回调的形式如下:

1
2
3
4
5
6
set.forEach(function (value, key, set) {
// value为set中的元素值
// key与value相同
// set对象本身
}, thisArg)
// thisArg 为this对象,为可选参数

回调的参数依次为:

  • value
  • key
  • set

其中,为了和其他有key的对象保持一致,这里使用的value占位了第二个参数,所以key就是value的值。

Map 对象

创建方法

1
2
3
4
5
6
7
// 伪代码:
new Map()
new Map(可迭代对象)

// 实际代码的演示:
const map = new Map([['book', 3], ['pen', 5]])
console.log(map)

通常会传入一个二维数组作为可迭代对象,每个一位数组都是一个两元素的小数组,作为可迭代对象的键值对。

添加元素

1
2
3
4
5
6
7
8
9
// 伪代码
map.set(键, 值);

// 实际代码的演示:
const map = new Map()
map.set([1, 2, 3], '书籍')
map.set(false, '日用品')
map.set(3, '化妆品')
console.log(map)

获取元素

1
2
3
4
5
6
7
8
9
10
// 伪代码:
map.get(key)

// 实际演示:
const map = new Map()
map.set(false, '日用品')
console.log(map)

const item = map.get(false)
console.log(item)

常用方法

方法 描述
has() 判断 Map 对象中指定键对应的条目是否存在
delete() Map 对象中删除指定键对应的条目
clear() 清空 Map 对象

对应的实例:

1
2
3
4
5
6
7
8
9
10
11
12
let bookstore = new Map()
bookstore.set('《活着》', '余华')
bookstore.set('《平凡的世界》', '路遥')
bookstore.set('《三体》', '刘欣慈')
bookstore.set('《猫和老鼠》', '电影')
console.log('《活着》是否存在:', bookstore.has('《活着》'))

bookstore.delete('《猫和老鼠》')
console.log('《猫和老鼠》是否存在:', bookstore.has('《猫和老鼠》'))

bookstore.clear()
console.log(bookstore)

遍历方法

其中callBackFn回调中的参数依次为value,key,map自身,可以看得出来,ES6forEach方法参数上的统一性。

1
2
3
4
5
6
7
8
9
// 伪代码:
map.forEach(callbackFn, thisArg)

// 参考示例:
const userName = new Map([[1, '小红'], [2, '小蓝'], [3, '小白']])
userName.forEach(function (value, key) {
console.log('当前条目的键为:', key)
console.log('当前条目的值为:', value)
})

结语

实际上,还有weakSetweakMap这两种垃圾回收机制更强的弱引用SetMap,本文不再展开。

函数扩展

默认参数

通用的写法:

1
2
3
function func(a, b, ..., c = '默认值c', d = '默认值d', ...) {
...
}

其中,需要注意的是,有默认值的尽量写在后面并且所有形参参数不允许重复申明。具体例子:

1
2
3
4
5
6
7
8
9
10
11
12
function test() {
return 13
}

// 函数可以作为形参的接收
function func(words, name = '🍎', age = test()) {
console.log(words, name, age)
}

func('请给我一个')
func('请给我一个', '🍐')
func('请给我一个', '')

对于没有传入的参数,如果没有默认值,默认是undefined

剩余参数

可以使用均摊符号...来接受最右边的所有参数,所有接受的参数会以数组的形式放入该变量。

1
2
3
4
5
function func(a, ...rest) {
console.log(rest)
}

func(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

但是注意不能这样写,控制台会报错:

1
2
3
function func(a, ...rest, b) {
console.log(rest)
}

箭头函数

函数的语法糖,写法如下:

1
2
3
4
let sum = (a, b) => {
return a + b
}
console.log(sum(1, 2))

单参数或单返回值的两种缩写:

1
2
3
4
5
6
7
8
9
// 单个参数的缩写
let sum = a => {
return a * 2
}
console.log(sum(2))

// 单个返回值进一步缩写,省略return和{}
let sum2 = a => a * 2
console.log(sum2(3))

特别声明,如果返回对象时想使用缩写必须用小括号包住,防止被当成函数体处理:

1
2
let student = () => ({ name: "小蓝" })
console.log(student())

剪头函数同样可以使用函数的均摊语法,默认参数语法。

对象扩展

字面量扩展

属性的简洁表示

对于参数和对象值重复的申明,可以使用语法糖:

1
2
3
4
5
6
7
8
9
10
11
// ES6写法
const name = '闷墩儿'
const age = 2
const dog = { name, age }
console.log(dog)

// 等价于下方的写法
const name2 = '闷墩儿'
const age2 = 2
const dog2 = { name2: name2, age2: age2 }
console.log(dog2)

方法的简洁表示

对于方法也有更加简单的写法,相比于ES5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ES6写法
const name = '闷墩儿'
const dog = {
run() {
return name + '在公园里奔跑!'
},
}

// ES5写法
// const name = '闷墩儿'
// const dog = {
// run: function () {
// return name + '在公园里奔跑!'
// },
// }

属性名表达式

属性名可以使用[]方括号的写法,和Python 中的写法差不多:

1
2
3
4
5
6
7
const ch = '2'
const key = `name${ch}`
const dog = {
[key]: "闷墩儿",
}

console.log(dog[`name${ch}`])

对象扩展运算符

使用...均摊符号,可以将对象均摊出来,不仅可以用于快速复制一个对象,还可以用于合并对象:

1
2
3
4
let obj1 = { species: '柯基', name: '闷墩儿', age: 2 }
let obj2 = { food: '狗粮' }
let obj3 = { ...obj1, ...obj2 }
console.log(obj3)

对应重复的key,合并时会被后面的对象所覆盖。

对象新增方法

Object.is() 方法

直接用=====判断变量相等与否会产生一些问题:

1
2
3
4
5
6
7
console.log(-0 == +0) // true
console.log(-0 === +0) // true

console.log(NaN == NaN) // false
console.log(NaN === NaN) // false

console.log(7 == '7') // true

但使用Object.is()就能解决:

1
2
3
console.log(Object.is(-0, +0)) // false
console.log(Object.is(NaN, NaN)) // true
console.log(Object.is(7 == '7')) // false

Object.assign() 方法

该方法用于将对象合并起来,并且是浅拷贝:

1
2
3
4
5
6
7
8
let obj1 = { name: '闷墩儿', food: '狗粮' }
let obj2 = { age: 2, hobby: '跑圈圈' }
let obj3 = { color: '黑白黄' }
Object.assign(obj1, obj2, obj3) // 将 obj2 和 obj3 合并到 obj1 中
console.log(obj1)
obj2.hobby = '游泳'
console.log(obj2)
console.log(obj1)

面相对象编程

类的申明

在ES6中申明一个类的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyClass {
// constructor() 方法是类的默认构造方法
constructor(num) {
this.num = num
this.enginesActive = false
}
// 相当于 MyClass.prototype.startEngines
startEngines() {
console.log('num =', this.num)
console.log('starting ...')
this.enginesActive = true
}
}

const myclass = new MyClass(1)
myclass.startEngines()

类的表达式

类和函数都有两种存在形式:

  • 声明形式(例如 functionclass 关键字)。
  • 表达式形式(例如 const A = class{})。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ES6 语法
let DogType = class {
constructor(name) {
this.name = name
}
sayName() {
console.log(`大家好!我是一只小${this.name}。`)
}
}

let dog = new DogType('柯基')
dog.sayName()
console.log(dog instanceof DogType)
console.log(dog instanceof Object)

命名表达式

和函数一样,可以给表达式重新命名:

1
2
3
4
5
6
7
8
9
10
let DogName = class MyClass {
constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}
console.log(typeof DogName) // function
console.log(typeof MyClass) // undefined

MyClass是只存在类内部的标识符,在类外部不存在。

类的继承

extends 关键字

使用extends关键字来继承类:

1
class child_class_name extends parent_class_name {}

extends 接表达式

extends不仅可以接类,还可以紧跟一个表达式:

1
2
3
4
5
6
7
8
9
10
11
function func(message) {
return class {
say() {
console.log(message)
}
}
}
class Person extends func('欢迎来到蓝桥云课!') {}

person = new Person()
person.say()

本质是还是在继承类。

super 关键字

继承了父类后如果想要重写构造函数,必须在所有this调用前调用一遍super(),如果不写系统也会自动生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Animal {
constructor(name, age, speed) {
this.name = name
this.age = age
this.speed = speed
}
run() {
console.log(`${this.age}岁的${this.name}酷跑了 ${this.speed} 公里。`)
}
stop() {
console.log(`${this.name}停止了奔跑。`)
}
}

class Dog extends Animal {
constructor(name, age, speed, species) {
super(name)
this.species = species
}
run() {
console.log(`${this.name}是一只奔跑的${this.species}`)
}
}

let dog = new Dog('闷墩儿', '一', 5, '狗')
dog.run()

类的属性和方法

静态属性和方法

1
2
3
4
5
6
7
8
class Dog {
static dogName = '闷墩儿'
static show() {
console.log(`我叫:${this.dogName}`)
}
}
console.log(Dog.dogName) // 闷墩儿
Dog.show()

类的静态成员变量可以被继承。

私有属性和方法

使用下方写法可以申明一个私有成员变量:

1
2
3
4
// 私有属性
#propertiesName
// 私有方法
#methodName()

Vim学习随笔,记录关键知识点📝
0%