封面作者:NOEYEBROW
内容并非完全线性, 如有不解可暂时跳过
⭐基础 JavaScript 是一种弱类型 、解释型 的脚本语言 , 主要 用于网页开发, 既可以面向对象编程, 也可以面向过程编程(在前端开发中, 一般都是面向对象编程)
面向对象 的编程语言中, 每个对象都是一个某一主题的功能中心; 完成某个任务的过程就是调用各种对象的属性和方法(功能)进行处理; 具有灵活、易维护、易开发、封装性 (可复用)、继承性 、多态性 的特点
而面向过程 的编程语言, 如 C、MATLAB, 强调解决问题的各个步骤, 每个步骤都被设计为一个函数, 然后依此调用这些函数, 即先干什么再干什么; 而面向对象的编程强调解决问题的对象, 即先找谁干什么, 再找谁干什么; 面向过程一般具有高性能 的特点
运行和调试 JavaScript 程序需要在一定的环境, 如 浏览器、Node.js、Deno、Bun 中运行, 在基础部分, 我们主要以 浏览器 为例; 有两种方式可以将 JavaScript 代码引入 HTML:
1 2 3 4 5 6 7 8 9 <script > alert ('这是内联形式引入的JavaScript代码' ); </script > <script src ="demo.js" > </script >
1 2 alert ('这是外部形式引入的JavaScript代码' );
JavaScript 中每节代码末尾的 ; 是可以省略的, 后面的代码都将以省略 ; 的形式书写
断点调试的方法
在浏览器中打开文件
通过 F12 或 ctrl + shift + i 打开开发者工具
在 Sources 中找到要调试的 JavaScript 文件
点击行号左侧添加断点
刷新页面
使用 F10 或 F11 单步调试, 后者会进入函数内部
defer 属性
defer 属性用于延迟脚本的执行, 即脚本会在文档解析完毕后再执行, 即使脚本在页面中的位置靠前
可用于避免代码内容获取不到页面元素、代码阻塞页面渲染等问题
对于 module 类型的脚本, defer 属性是默认的
1 <script defer src ="script.js" > </script >
基本输入输出
方法
描述
alert()
在浏览器中弹出提示框, 显示指定内容
document.write()
在 HTML 文件中添加指定内容 位置取决于 script 标签的位置
console.log()
在浏览器的控制台中输出指定内容 一般用于调试程序, 应在正式上线前删除
res = prompt('提示内容')
在浏览器中弹出提示框, 用户可以输入内容 输入的字符串将被赋值给 res
res = confirm('提示内容')
在浏览器中弹出提示框, 用户可以点击确定或取消 确定则将 true 赋值给 res, 取消则将 false 赋值给 res
要通过 prompt 获取数值型数据, 可以用 num = +prompt('提示内容') 或 num = Number(prompt('提示内容')) 进行类型转换 详见后文
注释 通过注释可以屏蔽代码被执行或者添加备注信息, JavaScript 支持三种形式注释语法:
单行注释 : 使用 // 注释单行代码, VScode 中快捷键为 ctrl + /
多行注释 : 使用 /* */ 注释多行代码, VScode 中快捷键为 shift + alt + a
文档注释 : 使用 /** */ 注释函数或类, 用来声明函数或类的作用、参数、返回值等信息
文档注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function plus (a, b ) { return a + b } function getName (obj ) { return obj.name + obj.age }
语句
描述
@param {type} name description
参数及其类型和描述
@returns {type} description
返回值及其类型和描述
@example
示例
@author name <contact>(可选)
作者及联系方式
@license license
许可证
{} 中可以是 *、string、boolean、object、function 等数据类型, 也可以是 Date、Array、RegExp 等对象, 还可以是 'GET' | 'POST'、1 | 0 等特定值
变量 计算机存储数据的容器
声明
用 let 变量名 声明可变变量
用 const 变量名 声明不可变变量, 即常量(不可变 只表示变量名指向的内容不可变, 详见数据类型 )
早期用 var 变量名 声明变量, 现在不推荐使用
变量名可以: 包含字母、数字、下划线、$ 在较新的浏览器中可以包含中文
变量名不能: 以数字开头、是 JavaScript 内置的关键字, 如 let, 或保留字, 如 int
变量名应该: 有一定意义、用驼峰命名法 userName、sumScoreMath
变量名区分大小写
赋值
在声明变量后赋值, 如 let name; name = '小叶子'
在声明变量的同时赋值, 如 let name = '小叶子'
常量必须在声明的同时赋值, 如 const PI = 3.14, 且不允许重新赋值
能用 const 就不要用 let
函数内部声明的变量, 称为局部变量 , 只能在函数内部使用; 详见作用域
数据 JavaScript 中, 声明变量时无需指定数据类型, 系统会根据变量的值自动判断数据类型, 这种特性称为弱类型语言 , 可以通过 typeof 检测数据类型, 如 console.log(typeof 变量名) 或 console.log(typeof(变量名))
基本数据类型 也称为包装类型
类型
说明
数值型 number
JavaScript 中的数值类型包含整数和浮点数
字符串型 string
通过单引号 ''、双引号 ""、反引号 `` 包裹的数据都叫字符串
布尔型 boolean
表示真或假的数据, 有两个固定的值 true 或 false
未定义型 undefined
表示声明了变量但未赋值, 如 let name = '' 为 string 型, 而 let name 为 undefined 型
空值型 null
表示声明了变量并赋值, 但值为空, 如 let name = null
因历史原因, typeof null 的结果是 object 而非 null
转义符 \ 可以将表意字符转换成普通字符, 如 \' 表示单引号
需要在字符串内使用引号时, 应用不同的引号, 或者使用转义符
除上述的基本数据类型外, 还有 Symbol 和 BigInt 两种类型, 会在后面介绍
由于小数(浮点数)在计算机中是以二进制存储的, 所以在计算时会有精度问题, 如 0.1 + 0.2 结果为 0.30000000000000004; 如果需要精确计算, 可以转换为整数再计算
比较大的数值可以使用分隔符 _ 分隔, 如 let num = 1_000_000
NaN, Not a Number
在数字计算失败时, 显示的结果是 NaN
NaN 和任何值都不相等, 包括它自己
任何对 NaN 的运算, 结果都是 NaN
NaN 与任何值比较, 结果都是 false
复杂数据类型 也称为引用类型
类型
说明
对象型 object
表示一组数据的集合, 类似于 C 语言中的结构体, 但功能更强大
数组型 array
表示一组数据的集合, 长度不固定, 也属于对象 类型数据
函数型 function
表示一段可执行的代码, 如 function getNumber() {一些代码}
栈内存 : 由系统自动分配, 存储基本数据类型的值和复杂数据类型的地址
堆内存 : 由程序员分配和释放, 存储复杂数据类型的值
基本数据类型 的变量存储的是数据的值 , 即变量名指向数据的值
复杂数据类型 的变量存储的是数据的地址 , 即变量名指向数据的地址
两种数据类型的变量名都指向栈内存 , 其中基本数据类型指向数据的值, 复杂数据类型指向数据的地址
复杂数据类型的数据的值存储在堆内存 , 通过存储在栈内存中的地址来进行访问
const 声明的变量, 只是不允许修改变量名指向的栈内存中的内容, 但是可以修改堆内存中的内容
JavaScript 中其实没有真正意义上的栈和堆, 这里借用了其他语言中的概念
上述特性示例 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 let obj1 = { name : '小叶子' , age : 18 } let obj2 = obj1obj2.age = 12 console .log (obj1.age ) const obj3 = { name : '小叶子' , age : 18 }obj3 = { name : '小叶子' , age : 12 } obj3.age = 12 console .log (obj3.age )
模板字符串
模板字符串用于方便地拼接字符串(而非使用多个加号, 如 console.log('我叫' + name + ', 今年' + age + '岁了'))
模板字符串必须使用反引号 `` 包裹, 字符串中的变量使用 ${变量名} 表示
用 '' 和 "" 时, 一对引号必须位于同一行, 而模板字符串 `` 可以换行
alert 和 console.log 中, 换行会如实显示
HTML 中(即 document.write 中)无论多少换行都会显示为一个空格, 应该用 <br> 换行
${} 中可以进行简单的运算, 如 ${age + years}、${name.toUpperCase()}
1 2 3 4 5 6 7 const name = '小叶子' const age = 18 const years = 3 console .log ('我叫' + name + ', 今年' + age + '岁了' )console .log (`我叫${name} , 今年${age} 岁了, ${years} 年后我就${age + years} 岁了` )
类型转换 隐式转换 某些运算符被执行时, 系统内部自动将数据类型进行转换, 这种转换称为隐式转换
通过 + 可以将字符串转换成数值类型, 如 +'1' 结果为 1, 此时 + 表示 正号(这个机制有时很有用, 如通过 prompt 获取数字型数据: let age = +prompt('请输入您的年龄'))
当字符串和数字进行 + 运算时, 数字会转换成字符串, 如 '1' + 1 结果为 '11'
当字符串和数字进行 -、*、/、% 运算时, 字符串会转换成数值类型, 如 '1' - 1 结果为 0, 其中 '' 会转换成 0
null 进行数字运算时, 会转换成 0
undefined 进行数字运算时, 会转换成 NaN
通过 ! 可以将数据转换成布尔类型, 如 !0 结果为 true, !1 结果为 false
显式转换 隐式转换规律不清晰, 为了避免因其带来的问题, 通常需要对数据进行显式转换
方法
描述
Number()
将数据转换成数值类型, 当转换失败时结果为 NaN
parseInt()
将数据只保留整数, 如 parseInt('12.8px') 结果为 12
parseFloat()
将数据只保留浮点数, 如 parseFloat('12.8px') 结果为 12.8
String()
将数据转换成字符串类型
变量.toString(进制)
将变量转换成字符串类型, 进制为可选参数 如 num.toString(2) 将数字转换成二进制字符串
Boolean()
将数据转换成布尔类型 只有 0、NaN、''、null、undefined 会转换成 false, 其他都会转换成 true
parseInt 和 parseFloat 只支持转换以数字开头的字符串, 如 '12px' 可以转换, 而 'px12' 不可以转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const num = 1 const str = '2' console .log (num + str) console .log (num - str) console .log (num + Number (str))
用 parseInt 实现秒数转时分秒
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 ="en" > <head > <meta charset ="UTF-8" > <title > 类型转换</title > </head > <body > <script > let sec = +prompt ('请输入秒数: ' ) function secToTime (sec ) { if (isNaN (sec) || sec < 0 ) { return '请输入正确的秒数' } let s = parseInt (sec % 60 ) let m = parseInt (sec / 60 % 60 ) let h = parseInt (sec / 60 / 60 % 24 ) s < 10 ? s = '0' + s : s m < 10 ? m = '0' + m : m h < 10 ? h = '0' + h : h return `${h} 时${m} 分${s} 秒` } alert (secToTime (sec)) </script > </body > </html >
进制 JavaScript 中的数值型数据不止可以是十进制, 还可以是二进制、八进制、十六进制, 分别用 0b、0o、0x 开头表示
1 2 3 4 5 6 7 8 9 10 const num10 = 324 const num2 = 0b101000100 const num8 = 0o504 const num16 = 0x144 console .log (num10, num2, num8, num16)
二进制运算符 下面的运算符会将操作数 (十进制) 转换成 32 位整数 (由 0 和 1 组成), 然后执行操作, 最后返回结果 (十进制)
运算符
说明
&: 按位与
1010 & 1100 结果为 1000
|: 按位或
1010 | 1100 结果为 1110
~: 按位非
~1010 结果为 0101
^: 按位异或
1010 ^ 1100 结果为 0110 异或门: 两个输入相同时输出为 0, 不同时输出为 1
<<: 左移
1010 << 1 结果为 10100 左移一位相当于乘以 2
>>: 右移
1010 >> 1 结果为 0101 右移一位相当于除以 2
>>>: 无符号右移
1010 >>> 1 结果为 0101 右移一位, 最高位补 0
&=: 按位与赋值
a &= b 相当于 a = a & b
|=: 按位或赋值
a |= b 相当于 a = a | b
^=: 按位异或赋值
a ^= b 相当于 a = a ^ b
<<=: 左移赋值
a <<= b 相当于 a = a << b
>>=: 右移赋值
a >>= b 相当于 a = a >> b
>>>=: 无符号右移赋值
a >>>= b 相当于 a = a >>> b
1 2 3 4 5 6 7 8 9 const a = 10 const b = 12 console .log (a & b) console .log (a | b) console .log (~a) console .log (a ^ b) console .log (a << 1 ) console .log (a >> 1 ) console .log (a >>> 1 )
数组 Array , 用于存放一组数据的集合, 长度不固定, 属于对象 类型数据
1 2 3 4 5 const arr = [] const arr = [1 , '小叶子' , true ]
数组索引 数组的每一个元素都有一个唯一的索引 也叫下标, 通过索引可以访问或修改数组中的元素 也叫数组单元, 索引从 0 开始
通过索引访问 数组中的元素, 如 arr[0] 获取数组中的第一个元素
通过索引修改 数组中的元素, 如 arr[0] = 1 将数组中的第一个元素修改为 1
通过索引添加 数组中的元素, 如 arr[6] = 1 将数组中的第五个元素添加为 1, 此时第四个元素不存在, 为 undefined
通过索引删除 数组中的元素, 如 delete arr[0] 删除数组中的第一个元素, 删除后该元素变为 undefined, 数组长度不变
索引的相关属性和方法
描述
arr.length
获取数组的长度 , 即数组中元素的个数
arr.indexOf('xxx')
获取数组中 'xxx' 这个特定元素的索引; 若有重复, 则返回最小索引; 若数组中不存在该元素, 则返回 -1
arr.lastIndexOf('xxx')
同上, 但有重复时返回最大索引
arr.findIndex()
查找元素, 返回符合测试条件的第一个 数组元素索引值 , 如果没有符合条件的则返回 -1
arr.slice(0, 2)
截取 数组中索引为 0 到 1 的元素, 不包含 2, 返回新数组; 不传参数时, 返回整个数组
findIndex() 的用法
1 2 3 4 let arr = [1 , 3 , 2 , 5 , 4 ]let ans = arr.findIndex (ele => ele > 3 )console .log (ans)
修改原数组
修改的相关属性和方法
描述
arr.push('啦啦啦')
向数组末尾添加 元素 '啦啦啦', 并返回新数组的长度
arr.pop()
删除 数组末尾 的元素, 并返回被删除的元素
arr.unshift('啦啦啦')
向数组开头添加 元素 '啦啦啦', 并返回新数组的长度
arr.shift()
删除 数组开头 的元素, 并返回被删除的元素
arr.reverse()
反转 数组
arr.sort()
对数组进行排序 , 默认按照字符编码 (注意不是数字数值) 升序排序
arr.splice()
从指定位置删除和(或)添加 元素
arr.fill(xxx, 1)
用 xxx 填充 索引 1 开始的所有元素, 包括 1
sort() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 let arr = [1 , 3 , 2 , 5 , 4 ]let ans = []ans = arr.sort ((a, b ) => { return a - b }) console .log (ans)
=> 称为箭头函数, 详见箭头函数
splice() 的用法
arr.splice(0, 2): 从索引 0 开始, 删除 2 个元素, 即 0 和 1arr.splice(0, 0, '啦啦啦'): 从索引 0 开始, 添加 '啦啦啦' 元素, 原来的元素依次后移arr.splice(0, 1, '啦啦啦'): 将索引 0 的元素替换 为 '啦啦啦'arr.splice(0, 2, '啦啦啦'): 从索引 0 开始, 删除 2 个元素, 然后添加 '啦啦啦' 元素
创建新数组
方法
描述
arr.concat(x, y, ...)
在 arr 末尾依此连接 数组 x 和 y, 并返回新数组
arr.map()
对数组中的每个元素进行处理 , 并返回一个新数组
arr.flat([depth])
将数组扁平化 , 即将多维数组转换成一维数组, depth 表示扁平化的层级
arr.flatMap()
对数组中的每个元素进行处理 , 并返回一个新数组, 与 map 不同的是, flatMap 会扁平化 结果数组
arr.reduce()
对数组中的每个元素进行累加 , 并返回一个值
arr.join()
将数组转换成字符串 , 可指定分隔符, 返回这个字符串
arr.forEach()
对数组中的每个元素进行调用 , 并返回 undefined
arr.filter()
对数组中的每个元素进行筛选 , 并返回一个新数组
arr.every()
检测 是否数组中每个元素都满足条件, 并返回 true 或 false
arr.some()
检测 是否数组中有元素满足条件, 并返回 true 或 false
arr.find()
检测并返回 数组中符合条件的第一个元素, 若没有则返回 undefined
Array.from(xxx)
将类数组或可迭代对象转换成数组 , 并返回这个数组
arr.includes(xxx)
检测 数组中是否包含 xxx, 并返回 true 或 false
arr.at(index)
获取 数组中指定索引的元素, 若索引为负数, 则从末尾开始计算
arr.toReversed()
ES2023 新增, 不修改原数组, 返回一个反转 后的新数组
arr.toSorted()
ES2023 新增, 不修改原数组, 返回一个排序 后的新数组
arr.toSpliced()
ES2023 新增, 不修改原数组, 返回一个截取 后的新数组
map()、join() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const arr = ['red' , 'blue' , 'pink' ]const newArr = arr.map (function (ele, index ) { return '<td>' + ele + '</td>' }) console .log (newArr) console .log (newArr.join ()) console .log (newArr.join ('' )) console .log (newArr.join ('<br>' ))
reduce() 方法
1 2 3 4 5 6 7 8 9 10 const arr = [1 , 2 , 3 , 4 , 5 ]const sum = arr.reduce (function (pre, cur, index, arr ) { return pre + cur }, 0 ) console .log (sum)
forEach() 方法
1 2 3 4 5 6 7 8 9 const arr = ['red' , 'blue' , 'pink' ]const newArr = arr.forEach (function (ele, index ) { console .log (ele) console .log (index) }) console .log (newArr)
forEach() 方法无法中断循环, 如需中断循环, 应使用 for 循环
filter() 方法
1 2 3 4 5 6 7 8 9 const arr = ['red' , 'blue' , 'pink' ]const newArr = arr.filter (function (ele, index ) { return ele.length > 3 }) console .log (newArr)
every()、some()、find() 方法
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 const arr = [1 , 2 , 3 , 4 , 5 ]let ans = arr.every (function (ele, index ) { return ele > 2 }) console .log (ans) let ans = arr.some (function (ele, index ) { return ele > 2 }) console .log (ans) let ans = arr.find (function (ele, index ) { return ele > 2 }) console .log (ans)
from() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let obj = document .querySelectorAll ('div' )Array .from (obj).forEach (ele => console .log (ele)) let str = 'hello' Array .from (str).forEach (ele => console .log (ele)) element.addEventListener ('click' , e => { if (Array .from (element.children ).indexOf (e.target ) === 0 ) { console .log ('点击了第一个子元素' ) } })
at() 方法
ECMAScript 2022 中引入了 Array.prototype.at 和 String.prototype.at 方法, 用于获取数组和字符串中的值
1 2 3 4 5 6 7 8 9 const arr = [1 , 2 , 3 , 4 , 5 ]console .log (arr.at (2 )) console .log (arr.at (-2 )) console .log (arr.at (-1 )) console .log (arr.at (5 ))
Array 表示数组构造函数 , arr 表示数组, 不能混用; 除了数组修改 这一节, 其他的实例方法请默认不会修改原实例
运算符
运算符
分类
说明
+
算术运算符
加法运算符 若包含字符串, 则表示拼接
-
算术运算符
减法运算符
*
算术运算符
乘法运算符
/
算术运算符
除法运算符
%
算术运算符
取模 余数 运算符, 常用于判断能否被整除
**
算术运算符
幂(指数)运算符, 相当于 ^
=
赋值运算符
将右侧的值赋值给左侧的变量
+=
赋值运算符
加法赋值, num += 1 等同于 num = num + 1
-=
赋值运算符
减法赋值, num -= 1 等同于 num = num - 1
*=
赋值运算符
乘法赋值, num *= 1 等同于 num = num * 1
/=
赋值运算符
除法赋值, num /= 1 等同于 num = num / 1
%=
赋值运算符
取模赋值, num %= 1 等同于 num = num % 1
++
自增运算符
将变量 的值加 1, num++ 等同于 num = num + 1
–
自减运算符
将变量 的值减 1, num-- 等同于 num = num - 1
>
比较运算符
表示是否大于, 1 > 2 结果为 false
<
比较运算符
表示是否小于, 1 < 2 结果为 true
>=
比较运算符
表示是否大于等于, 1 >= 2 结果为 false
<=
比较运算符
表示是否小于等于, 1 <= 2 结果为 true
==
比较运算符
表示是否等于 , 1 == '1' 结果为 true
===
比较运算符
表示是否严格等于 , 1 === '1' 结果为 false
!=
比较运算符
表示是否不等于 , 1 != '1' 结果为 false
!==
比较运算符
表示是否严格不等于 , 1 !== '1' 结果为 true
&&
逻辑运算符
逻辑与, 一假则假, true && false 结果为 false
||
逻辑运算符
逻辑或, 一真则真, true || false 结果为 true
!
逻辑运算符
逻辑非, 取反, 若 a = true 则 !a 为 false
推荐使用 === 和 !== 而非 == 和 !=
num++ 和 ++num 的区别在于前者先使用后加, 后者先加后使用 同C语言
用比较运算符比较字符串时, 会依此比较字符的 ASCII 码, 如 'a' < 'b'、'aa' < 'ab'、'bba' > 'bb' 的结果都为 true
优先级
优先级
运算符
说明
1
小括号
()
2
其他一元运算符
++、--、!、**
3
算术运算符
先 *、/、%, 后 +、-
4
大小比较运算符
>、<、>=、<=
5
相等比较运算符
==、!=、===、!==
6
逻辑运算符
先 &&, 后 ||
7
赋值运算符
=、+=、-=、*=、/=、%=
8
逗号运算符
,
使用 ** 时, 不能将一元运算符 +/-/~/!/typeof 放在底数之前, 例如, -2 ** 2 是无效的, 必须写成 - (2 ** 2) 或 (-2) ** 2
逻辑赋值
运算符
说明
示例
&&=
逻辑与赋值
a &&= b 等同于 a = a && b
||=
逻辑或赋值
a ||= b 等同于 a = a || b
??=
空值合并赋值
a ??= b 等同于 a = a !== null ? a : b
逻辑中断 逻辑运算符 && 和 || 有一个特殊的功能, 即逻辑中断
当 && 左侧为假时, 右侧的表达式不会被执行, 返回左侧值; 为真时, 返回右侧值
当 || 左侧为真时, 右侧的表达式不会被执行, 返回左侧值; 为假时, 返回右侧值
注意 : && 和 || 的返回值不是布尔类型, if 的判断条件中, 实际上将返回值隐式转换为了布尔类型
但是 : && 和 || 在决定返回值时, 会将数据隐式转换成布尔类型, 然后再进行判断
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 (function test (a, b ) { a = a || 1 b = b || 2 console .log (a, b) })() function test (a = false , b = 0 ) { let s = parseInt (b % 60 ) let m = parseInt (b / 60 % 60 ) let h = parseInt (b / 60 / 60 % 24 ) a && (s < 10 ? s = '0' + s : s) a && (m < 10 ? m = '0' + m : m) a && (h < 10 ? h = '0' + h : h) return `${h} 时${m} 分${s} 秒` } const dpr = window .devicePixelRatio || 1 console .log (1 && 2 ) console .log (1 || 2 ) console .log (false && 2 ) console .log (false || 2 ) console .log (0 && 2 ) console .log (0 || 2 ) console .log (1 < 2 && 2 ) console .log (1 < 2 || 2 )
操作符
操作符
说明
in
用于检测对象中是否有某个属性const name = 'name' in obj ? '有' : '无'
new
用于创建对象实例const obj = new Object()
typeof
用于检测数据类型const type = typeof obj // 'object'
instanceof
用于检测对象是否是某个类的实例const isArr = arr instanceof Array // true
delete
用于删除对象的属性delete obj.name, delete arr[0]
void
用于返回 undefinedconst result = void 0 // undefined
...
用于展开数组或对象const arr = [1, 2, 3]; const newArr = [...arr]
??
空值合并运算符, 用于判断左侧 是否为 null 或 undefined, 若是则返回右侧值const name = obj.name ?? '无名' 相比于 ||, ?? 不会将 0、''、false 等值判定为 false
?.
可选链运算符, 用于判断左侧 是否为 null 或 undefined, 若是则返回 undefinedconst name = obj?.name 若 obj 为 null 或 undefined, 则 name 为 undefined 还有 obj?.[name]、func?.(args)、obj?.name?.[name] 等用法
1 2 3 4 5 6 7 8 void function ( ) { console .log ('立即执行函数' ) }() button.onclick = () => void console .log ('点击了按钮' )
本部分内容为后文内容总结和补充, 可先略过; 完整详见MDN
语句和表达式
表达式 : 由变量、运算符组成的表示一个值 的式子, 如 1 + 1、a > b、a && b、func()
语句 : 控制某些行为的一段代码, 不一定有返回值, 如 alert()、if (a > b) {一些代码}、break
分支语句 : 根据条件执行不同的代码, 如 if、switch
循环语句 : 重复执行某些代码, 如 while、for
if 1 2 3 4 5 6 7 8 9 10 11 12 13 if (条件表达式) { } else { } const score = +prompt ('请输入您的成绩: ' )if (score >= 90 ) alert (`你的成绩是${score} , 成绩优秀` )else if (score >= 80 ) alert (`你的成绩是${score} , 成绩良好` )else if (score >= 60 ) alert (`你的成绩是${score} , 成绩及格` )else alert (`你的成绩是${score} , 成绩不及格` )
条件表达式中的结果会被自动转换成布尔类型, 相当于 Boolean(条件表达式)
switch 1 2 3 4 5 6 7 8 9 10 11 switch (表达式) { case 值1 : break case 值2 : break default : break }
break 用于退出 switch, 若没有 break, 则会依此执行后续所有 case
分支较少时, 一般使用 if else 而非 switch
用 switch 实现简单计算器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const num1 = +prompt ('请输入第一个数字: ' )const operator = prompt ('请输入运算符: ' )const num2 = +prompt ('请输入第二个数字: ' )switch (operator) { case '+' : alert (`${num1} + ${num2} = ${num1 + num2} ` ) break case '-' : alert (`${num1} - ${num2} = ${num1 - num2} ` ) break case '*' : alert (`${num1} * ${num2} = ${num1 * num2} ` ) break case '/' : alert (`${num1} / ${num2} = ${num1 / num2} ` ) break default : alert ('您输入的运算符有误' ) break }
三元表达式 一些简单的双分支可以用三元运算符代替 if else, 语法为 条件表达式 ? 为真时执行的代码 : 为假时执行的代码, 一般用于赋值
1 2 3 4 5 6 7 8 9 10 11 let score = +prompt ('请输入您的成绩: ' )let grade = score >= 60 ? '及格' : '不及格' score = +prompt ('请输入您的成绩: ' ) score >= 60 ? alert ('及格' ) : alert ('不及格' ) score = +prompt ('请输入您的成绩: ' ) alert (`您的成绩是${score} , 成绩${score >= 60 ? '及格' : '不及格' } ` )
用三元运算符实现数字补零
1 2 3 4 let num = +prompt ('请输入一个数字: ' )alert (num < 10 ? `你输入的数字是0${num} ` : `你输入的数字是${num} ` )
while
条件表达式的性质同 if 语句
使用 break 退出循环
使用 continue 结束本次循环, 即跳过循环体中 continue 之后的代码, 继续下一次循环
用 while 循环实现打印偶数
1 2 3 4 5 6 7 8 9 10 11 12 13 const num = +prompt ('请输入一个数字: ' )console .log (`小于${num} 的偶数有: ` )let i = 0 while (i <= num) { if (num % 2 === 0 ) { console .log (num) } i++ }
do while 先执行代码后判断条件, 即至少执行一次
实际开发中 do while 循环使用较少
for 1 2 3 for (起始值; 条件表达式; 变化量) { }
起始值 : 声明循环变量, 如 let i = 0
条件表达式 : 同 while 循环
变化量 : 循环体执行完毕后, 循环变量的变化, 如 i++
同样可以使用 break 和 continue
用 for 循环实现打印偶数
1 2 3 4 5 6 7 8 9 10 const num = +prompt ('请输入一个数字: ' )console .log (`小于${num} 的偶数有: ` )for (let i = 0 ; i <= num; i++) { if (num % 2 === 0 ) { console .log (num) } }
用 for 循环实现遍历数组
1 2 3 4 5 6 const arr = ['定风波' , '苏轼' , '莫听传林打叶声' , '何妨吟啸且徐行' , '竹杖芒鞋轻胜马' , '谁怕' , '一蓑烟雨任平生' , '料峭春风吹酒醒' , '微冷' , '山头斜照却相迎' , '回首向来萧瑟处' , '归去' , '也无风雨也无晴' ]for (let i = 0 ; i < arr.length ; i++) { console .log (arr[i]) }
用 for 循环计算数组统计量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const arr = [1 , 2 , 3 , 4 , 5 ]let sum = 0 for (let i = 0 ; i < arr.length ; i++) { sum += arr[i] } let avg = sum / arr.length console .log (avg)let max = arr[0 ]for (let i = 1 ; i < arr.length ; i++) { if (arr[i] > max) { max = arr[i] } } console .log (max)
相比于 while 循环, for 循环更加简洁, 且循环变量仅限于循环体内使用, 所以更加常用
无限循环 用 while(true) 或 for(;;) 实现无限循环, 需要通过 break 退出循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 while (true ) { let ans = confirm ('您是否同意用户协议?' ) if (ans) { alert ('谢谢' ) break } } for (;;) { let ans = confirm ('您是否同意用户协议?' ) if (ans) { alert ('谢谢' ) break } }
循环嵌套 在循环体中再嵌套循环, 注意: 循环变量不能相同
1 2 3 4 5 6 for (let i = 1 ; i <= 9 ; i++) { for (let j = 1 ; j <= i; j++) { console .log (`${j} * ${i} = ${i * j} ` ) } }
标签 在循环语句前加上标签, 可以在循环体内使用 break 和 continue 退出或跳过指定循环
1 2 3 4 5 6 7 8 9 outer : for (let i = 0 ; i < 3 ; i++) { for (let j = 0 ; j < 3 ; j++) { if (i === 1 && j === 1 ) { break outer } console .log (i, j) } }
函数 函数是执行特定任务的一个代码块, 用于将常用的代码封装起来, 从而方便重复使用, 前面用过的 alert() 等就是 JavaScript 内置的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function 函数名(形式参数1 = 默认值1 , 形式参数2 = 默认值2 , ...) { 函数体 return 返回值 } 函数名(实际参数1 , 实际参数2 , ...) const ans = 函数名(实际参数1 , 实际参数2 , ...)
函数名 : 函数的名称, 命名规则同变量名, 建议用动词开头, 提示函数功能
函数体 : 函数的功能代码, 运行到 return 时会结束函数 , 并返回返回值
形式参数 形参: 在声明函数时使用的变量, 只在函数内生效, 在函数体内只能引用形式参数
实际参数 实参: 在调用函数时使用的变量或表达式, 实际参数在函数内部被赋值给形式参数
返回值 : 函数执行完毕后对外输出 即赋给"函数名()" 的数据或变量
用函数实现打印偶数
1 2 3 4 5 6 7 8 9 10 11 12 13 function printEven (num ) { console .log (`小于${num} 的偶数有: ` ) for (let i = 0 ; i <= num; i++) { if (i % 2 === 0 ) { console .log (i) } } } const input = +prompt ('请输入一个数字: ' )printEven (input)
用函数实现冒泡排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function bubbleSort (arr ) { for (let i = 0 ; i < arr.length - 1 ; i++) { for (let j = 0 ; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1 ]) { let temp = arr[j] arr[j] = arr[j + 1 ] arr[j + 1 ] = temp } } } return arr } const input = [3 , 1 , 4 , 2 , 5 ]const ans = bubbleSort (input)
对象参数的默认值
1 2 3 4 function printInfo ({ name = '张三' , age = 18 , gender } = {} ) { console .log (`姓名: ${name} , 年龄: ${age} ${gender ? `, 性别: ${gender} ` : '' } ` ) }
函数的注意事项
如果声明相同的函数名, 则后面的函数会覆盖前面的函数, 且在严格模式下会报错
形参和实参的个数可以不同
形参多于实参时, 多余的形参值为默认值
实参多于形参时, 多余的实参会被忽略
函数可以调用外部声明的变量, 但函数内部声明的变量不能被外部调用, 详见作用域
函数会优先调用内部声明的变量, 如果没有才会调用外部声明的变量
回调函数 即回头再调用的函数 , 将 A 函数作为参数传递给 B 函数, 这个 A 函数就叫做回调函数 callback, A 函数会在 B 函数内部被调用; 多用匿名函数作为回调函数; 使用回调函数 A 时只需写函数名, 加上括号表示调用函数 A 的返回值, 即函数 A 会立即执行, 并以返回值作为参数传递给 B 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function fn ( ) { console .log ('我是一个回调函数...' ) return 1 } const btn = document .querySelector ('#btn' )btn.addEventListener ('click' , fn) btn.addEventListener ('click' , fn ()) btn.addEventListener ('click' , function ( ) { fn () }) btn.addEventListener ('click' , () => { fn () })
1 2 3 4 setInterval (function ( ) { console .log ('我是一个匿名回调函数...' ) }, 1000 )
函数表达式 将函数赋值给一个变量, 并通过变量调用匿名函数; 与具名函数的区别在于: 在书写 JavaScript 代码时, 可以先使用具名函数再声明, 但函数表达式必须先声明后使用
1 2 3 4 5 let fn = function ( ) {}fn ()
这个区别的实质是函数声明会被提升, 而函数表达式不会被提升, 后续会介绍
前面介绍的函数具有函数名, 叫做具名函数; 而有些函数没有函数名, 叫做匿名函数
匿名函数不能直接调用, 必须通过变量调用 (即函数表达式)、作为回调函数传递给其他函数、立即执行 (即立即执行函数 ) 等方式调用
立即执行函数 通过自执行调用匿名函数, 可以避免函数名和内部变量名污染全局变量, 必须加分号 可在前可在后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 (function (形参 ){函数体})(实参); (function (形参 ){函数体}(实参)); !function (形参 ){函数体}(实参); +function (形参 ){函数体}(实参); ~function (形参 ){函数体}(实参); (function fn (形参 ){函数体})(实参); function fn (形参 ){函数体}fn (实参)
定时函数
函数名
功能
setTimeout(函数, ms)
延时函数; 在指定的毫秒数后执行函数, 只执行一次, 返回定时器的ID
setInterval(函数, ms)
间歇函数; 每隔指定的毫秒数执行函数, 会一直执行, 返回定时器的ID
clearTimeout(定时器ID)
取消 setTimeout 或 setInterval 的执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let timer = setInterval (function ( ) { console .log (typeof timer) }, 1000 ) function timer ( ) { console .log ('Hello World' ) } let sayHello = setInterval (timer, 1000 )setTimeout (clearInterval (timer), 5000 )setTimeout (clearInterval (sayHello), 5000 )
用 setInterval 实现倒计时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let time = 10 function countDown ( ) { console .log (time) time-- if (time < 0 ) { clearInterval (timer) time = 10 timer = setInterval (countDown, 1000 ) } } let timer = setInterval (countDown, 1000 )
动态参数 arguments 是一个类数组对象, 包含了函数的所有参数, 可以通过索引访问参数, 也可以通过 length 属性获取参数的个数
1 2 3 4 5 6 7 8 function func ( ) { for (let i = 0 ; i < arguments .length ; i++) { console .log (arguments [i]) } } func (1 , true , 'xiaoyezi' )
剩余参数 语法符号 ... 用于获取函数的剩余参数, 所有多余的实参会存储到 ... 后的形参中, 返回一个真数组
1 2 3 4 5 6 7 8 function func (x, y, ...z ) { for (let i = 0 ; i < z.length ; i++) { console .log (z[i]) } } func (1 , 2 , 3 , 4 , 5 )
展开运算符 在函数外使用时, ... 用于展开数组、类数组对象、对象, 将其展开成多个参数(以逗号分隔), 不会影响原数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const arr = [1 , 2 , 3 ]console .log (...arr) Math .max (...arr) const arr1 = [1 , 2 , 3 ]const arr2 = [4 , 5 , 6 ]const arr3 = [...arr1, ...arr2] const obj1 = { name : 'xiaoyezi' , age : 18 }const obj2 = { ...obj1, gender : 'male' }const obj3 = { ...obj2 }
箭头函数 ES6 新增的一种函数声明方式, 使用 => 定义函数, 可以看作是匿名函数的简写
属于函数表达式, 因此不存在函数提升
只有一个参数时, 可以省略圆括号 ()
函数体只有一行代码时, 可以省略花括号 {}, 并自动做为返回值被返回
不能使用 arguments, 可以使用 ...
不能用作构造函数
不能作为对象的方法, 但能作为方法内的匿名函数, 此时箭头函数的 this 与方法的 this 指向相同
箭头函数没有自己的 this, 详见MDN , 且 this 值在函数声明时便固定
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 const func = ( ) => { console .log ('Hello World!' ) } const func = x => { return x + 1 } const func = x => x + 1 const func = x => ({ name : x }) console .log (func ('xiaoyezi' )) document .addEventListener ('click' , e => e.preventDefault ())const obj = { count : 10 , doSomethingLater ( ) { setTimeout (() => { this .count ++ console .log (this .count ) }, 1000 ) }, };
对象 对象是面向对象编程中的基本实体, 可以包含一系列数据和逻辑, 是一种数据类型
属性 : 对象中数据的存在形式, 相当于依附于对象的变量
方法 : 对象中代码的存在形式, 相当于依附于对象的函数
属性和方法也可以在对象声明后动态添加或修改
属性和方法的名称统称为键 , 值称为值 , 键 是应唯一的 否则后面的会覆盖前面的
属性和方法名如果打破了变量的命名规则, 如使用特殊符号 -、空格 等或以数字开头, 此时该名称必须用引号包裹, 如 'user-name': 'xiaoyezi'; 引用时只能用 xiaoyezi['user-name'], 不能用 xiaoyezi.user-name
JavaScript 中内置了一些对象, 称为内置对象 , 这些对象包含许多属性和方法, 可以直接使用
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 41 42 43 44 45 const emptyObj = {}const obj = { 属性: 值, 方法: function (形参, ... ) { return 返回值 } 方法(形参, ...) { return 返回值 } } const obj = { uname, age }obj.属性 obj['属性' ] obj.方法(实参1 , 实参2 , ...) obj['方法' ](实参1 , 实参2 , ...) obj.新属性 = 值 obj['新属性' ] = 值 obj.新方法 = function (形参, ... ) { return 返回值 } obj['新方法' ] = function (形参 ... ) { return 返回值 } delete obj.属性delete obj['属性' ]delete obj.方法delete obj['方法' ]
对象创建和使用示例
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 const xiaoyezi = { uname : '小叶子' , gender : '男' , printName : function (do = true ) { if (do ) { console .log (this .uname ) } }, getGender : function ( ) { return this .gender } } let name = xiaoyezi.uname console .log (xiaoyezi.gender ) xiaoyezi.printName () let gender = xiaoyezi.getGender () xiaoyezi.age = 18 xiaoyezi.printAge = function ( ) { console .log (this .age ) }
遍历对象 对象 非数组 内的属性和方法是无序的, 不能用 for 循环遍历, 可以用 for in 循环遍历
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 let xiaoyezi = { uname : '小叶子' , gender : '男' , printName : function (do = true ) { if (do ) { console .log (this .uname ) } }, getGender : function ( ) { return this .gender } } for (let key in xiaoyezi) { console .log (key, xiaoyezi[key]) key === 'gender' ? break : null }
不建议用 for in 遍历数组, 因为数组的键是数字, 会被隐式转换成字符串
环境对象 函数中的特殊变量 this 指向的是环境对象, 即函数所在的环境, 如果函数是在全局环境中声明的, 则 this 指向 window 对象; 通常 this 指向调用它的对象
对象
描述
window
浏览器窗口对象, 是 JavaScript 中内置的对象, 称为全局对象, 我们定义在全局作用域的 var 变量和函数实际上都是 window 对象的属性和方法; 详见 WebAPI 部分
document
文档对象, 是 JavaScript 中内置的对象, 称为文档对象模型, 是 DOM 的入口, 详见下一章
globalThis
通用的全局对象, 适用于浏览器、Node.js、Deno、Web Worker 等所有环境, 推荐统一使用 globalThis
1 2 3 4 5 6 7 8 9 10 11 function sayHi ( ) { console .log (this ) }let user = { sayHi : sayHi }let person = { sayHi : sayHi }sayHi () window .sayHi () user.sayHi () person.sayHi ()
console
方法
描述
console.log()
打印信息
console.info()
打印信息
console.warn()
打印警告
console.error()
打印错误
console.table()
打印表格
console.time()
计时开始
console.timeEnd()
计时结束
console.clear()
清空控制台
console.dir()
打印对象的详细信息
Math
属性
描述
Math.PI
圆周率
Math.E
自然对数的底数
Math.LN2
2 的自然对数
Math.LN10
10 的自然对数
Math.LOG2E
以 2 为底的 e 的对数
Math.LOG10E
以 10 为底的 e 的对数
Math.SQRT1_2
1/2 的平方根
Math.SQRT2
2 的平方根
方法
描述
Math.abs(x)
返回 x 的绝对值
Math.ceil(x)
向上取整: 返回大于等于 x 的最小整数
Math.floor(x)
同 parseInt(), 向下取整: 返回小于等于 x 的最大整数
Math.round(x)
返回 x 的四舍五入值, 注意: Math.round(-1.5) 是 -1
Math.max(x1, x2, ...)
返回 x1、x2、… 中的最大值
Math.min(x1, x2, ...)
返回 x1、x2、… 中的最小值
Math.pow(x, y)
返回 x 的 y 次幂, 即 xy
Math.sqrt(x)
返回 x 的平方根
Math.random()
返回 [0, 1) 之间的随机数
详见 MDN
Math.random() 使用示例
生成 [0, 100) 之间的随机整数: Math.floor(Math.random() * 100)
生成 [0, 100] 之间的随机整数: Math.floor(Math.random() * 101)
生成 [N, M] 之间的随机数: Math.random() * (M - N + 1) + N
随机抽取数组中的元素: arr[Math.floor(Math.random() * arr.length)]
将数字保留 x 位小数: num.toFixed(x), 返回字符串类型
Date Date 是 JavaScript 中内置的对象, 称为日期对象, 这个对象包含许多日期属性和方法; 使用它之前需要先用 new 关键字创建一个 Date 对象实例
实例化 : 通过 new 关键字创建一个对象实例, 即创建一个对象
任何变量都可以用 new 创建, 详见包装类型 和构造函数
时间戳 : 从 1970-01-01 00:00:00 至今的毫秒数
日期字符串 : yyyy-mm-dd hh:mm:ss 格式的字符串, 时分秒可省略
操作
描述
const date = new Date()
创建一个 Date 对象实例, 值为创建时的本地时间
const date = new Date(时间戳)
创建一个 Date 对象实例, 值为指定时间戳
const date = new Date('日期字符串')
创建一个 Date 对象实例, 值为指定日期
方法
描述
date.getFullYear()
返回年份, 如 2020
date.getMonth()
返回月份, 从 0 开始, 0 表示 1 月
date.getDate()
返回日期, 从 1 开始, 如 1 表示 1 日
date.getDay()
返回星期几, 从 0 开始, 0 表示星期天
date.getHours()
返回小时, 从 0 开始, 如 0 表示 0 点
date.getMinutes()
返回分钟, 从 0 开始, 如 0 表示 0 分
date.getSeconds()
返回秒数, 从 0 开始, 如 0 表示 0 秒
date.getMilliseconds()
返回毫秒数, 从 0 开始, 如 0 表示 0 毫秒
date.getTime()
返回时间戳, 如 1590000000000
date.toLocaleString()
返回本地时间, 如 2020/5/21 03:20:00
date.toLocalDateString()
返回本地日期, 如 2020/5/21
date.toLocalTimeString()
返回本地时间, 如 03:20:00
也可以写成 new Date().getFullYear() 等, 效果相同, 但是每次都会创建多余的对象实例, 不推荐
获取时间戳
1 2 3 4 5 6 7 8 9 const date = new Date ()let timestamp = date.getTime ()timestamp = Date .now () timestamp = +new Date ()
时间戳转换为时间
1 2 3 4 5 6 7 8 const time = Date .now () const d = parseInt (time / 1000 / 60 / 60 / 24 ) const h = parseInt (time / 1000 / 60 / 60 % 24 ) const m = parseInt (time / 1000 / 60 % 60 ) const s = parseInt (time / 1000 % 60 )
Object 对象的构造函数 , 以下是它的静态属性和方法
属性或方法
描述
Object.keys(对象)
返回对象的所有键, 返回值是数组
Object.values(对象)
返回对象的所有值, 返回值是数组
Object.entries(对象)
返回对象的所有键值对, 返回值是二维数组 如 [[key, value], [key, value], ...]
Object.assign(目标对象, 源对象1, 源对象2, ...)
将源对象的所有可枚举属性复制到目标对象 返回目标对象(同时也会修改目标对象)
Object.freeze(对象)
冻结对象, 使对象的属性和方法不可修改、删除或添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let obj = { name : 'xiaoyezi' , age : 18 } let keys = Object .keys (obj) let values = Object .values (obj) let entries = Object .entries (obj) const copy = Object .assign ({}, obj)Object .assign (copy, {gender : '男' })
Array 对象的静态和实例属性和方法见数组
String
包装类型 JavaScript 中的基本数据类型 Number、String、Boolean 等, 在底层都是用对象”包装”出来的, 具有对象的特征(有静态方法和静态属性), 称为包装类型; 一般用字面量创建包装类型, 但也可以通过 new 关键字实例化包装类型, 详见构造函数
属性或方法
描述
String(x)
将 x 转换为字符串类型
str.length
返回字符串的长度
str.trim()
去除 字符串两端的空格 , 返回新字符串
str.toUpperCase()
将字符串转换成大写 , 返回新字符串
str.toLowerCase()
将字符串转换成小写 , 返回新字符串
str.split('分隔符')
将字符串按照分隔符分割成数组 , 返回数组
str.substring(start, end)str.slice(start, end)
按照索引截取 字符串, 返回新字符串end 可选, 返回值不包含 end 索引的字符
str.startsWith('xxx', posititon)
判断字符串是否以 xxx 开头 , 返回布尔值position 可选, 表示从指定索引位置检测 xxx
str.endsWith('xxx')
判断字符串是否以 xxx 结尾 , 返回布尔值
str.includes('xxx', posititon)
判断字符串是否包含 xxx , 返回布尔值position 可选, 表示只从指定索引及其后方检测 xxx
str.indexOf('xxx', posititon)
返回 xxx 在字符串中首次出现的索引 , 没有则返回 -1position 可选, 表示只从指定索引及其后方检测 xxx
str.match('xxx')
查找 并返回字符串中首次 出现的 xxx, 没有则返回 null 支持正则表达式
str.replace('xxx', 'xx')
将字符串中首次 出现的 xxx 替换 为 xx, 返回新字符串 支持正则表达式
str.padStart(targetLength[, padString])
用 padString 填充 str 的前面 直到 str 的长度达到 targetLength 返回填充后的字符串
str.padEnd(targetLength[, padString])
用 padString 填充 str 的后面 直到 str 的长度达到 targetLength 返回填充后的字符串
padString 默认为空格, targetLength 如果小于 str 的长度, 则返回 str 本身
substr 与 substring 相同, 但已弃用; 字符串中的索引和数组类似, 从 0 开始; 中文字符只占一个索引和一个长度
padStart 方法
1 2 3 4 5 6 7 8 9 10 11 const arr = [1 , 2 , 3 , 4 , 5 ]for (const value of arr) console .log (value.toString ().padStart (3 , '0' ))for (const value of arr) { let str = value.toString () while (str.length < 3 ) str = '0' + str console .log (str) }
split 方法
1 2 3 4 5 6 7 let str = 'x; y; z' let arr = str.split ('' ) let arr = str.split (' ' ) let arr = str.split (';' )
Number
属性或方法
描述
num.toFixed(x)
将数字保留 x 位小数, 返回字符串类型; 四舍五入
num.toPrecision(x)
将数字转换为指定长度的字符串, 返回字符串类型
num.toString(x)
将数字转换为指定进制的字符串, 返回字符串类型
num.toExponential(x)
将数字转换为科学计数法的字符串, 返回字符串类型
JSON JavaScript Object Notation, JSON , 是一种轻量级的数据交换格式, 书写简单、一目了然, 常用于前后端数据交互; 它可以作为一个对象 、字符串 、.json 文件 存在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { "title" : "Blog" , "year" : 2024 , "active" : true , "others" : null , "tags" : [ ] , "category" : { } , "members" : [ { "name" : "xiaoyezi" , "mail" : "o.0@o0-0o.icu" } , { "name" : "xiaoyezi" , "mail" : "" } ] }
方法
描述
JSON.stringify(对象, 处理方法, 缩进)
将对象转换为 JSON 字符串; 缩进(空格)默认 0, 推荐 2
JSON.parse(JSON字符串)
将 JSON 字符串转换为对象
对象 内的不支持的数据类型会被忽略
数组 内的不支持的数据类型会被转换为 null
正则对象会被转换为空对象
JSON.stringify() 会忽略对象的不可遍历属性
处理方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 JSON .stringify (对象, ['属性1' , '属性2' , ...])JSON .stringify (对象, function (key, value ) { })
导入 .json 文件 1 2 3 4 5 6 7 8 9 10 11 $.ajax ({ type : "get" , url : "xxx.json" , dataType : "json" , success : function (data ) { }, error : function ( ) { alert ("请求失败" ) } })
⭐进阶 DOM Document Object Model, DOM , 文档对象模型, 是 JavaScript 操作网页内容的接口, 它将网页转换成一个多层节点结构, 每个节点都是一个 DOM 对象 , 从而可以用 JavaScript 操作这些对象, 从而改变网页的结构、样式和内容
document 对象是 DOM 的根节点 , 即 document 对象是 DOM 的入口
元素节点 : HTML 标签, 如 head、div、body 等都属于元素节点
属性节点 : HTML 标签中的属性, 如 a 标签的 href 属性、div 标签的 class 属性
文本节点 : HTML 标签的文字内容, 如 title 标签中的文字
所有 DOM 对象都有 nodeType 属性, 用于返回节点类型, 元素节点的 nodeType 值为 1, 属性节点的 nodeType 值为 2, 文本节点的 nodeType 值为 3
操作元素
获取元素
方法
描述
document.querySelector('CSS选择器')
返回第一个匹配的元素对象, 没有匹配的元素时返回 null
document.querySelectorAll('CSS选择器')
返回包含所有匹配的元素对象的 NodeList 对象, 没有匹配的元素时返回空的 NodeList 对象
document.documentElement
返回文档对象的根元素, 即 html 标签
document.head
返回文档对象的 head 元素
document.body
返回文档对象的 body 元素
document.title
返回文档对象的 title 元素
document.currentScript
返回当前正在执行的 script 元素 一般用于获取元素的属性
NodeList 和 HTMLCollection 对象类似数组, 内含以数字作为属性名排序的各个对象以及 lenth 属性, 但是不能使用数组的方法, 称为伪数组 ; 可以通过 for 循环, 用遍历数组的写法遍历它们
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const lis = document .querySelectorAll ('#list li' )for (let i = 0 ; i < lis.length ; i++) { console .log (lis[i]) } const li1 = document .querySelector ('#list li:first-child' ) const li2 = document .querySelector ('#list li:nth-child(2)' ) const li3 = document .querySelector ('#list li:last-child' )
如果用 element.querySelector() 获取元素, 只会在该元素内部查找, 而不会在整个文档中查找
用 querySelector 获取元素
1 2 3 4 5 6 7 8 <body > <h1 > DOM</h1 > <div > <span id ="someText" data-uname ="xiaoyezi" > 获取元素</span > <span > </span > <span class ="innerText" > 操作元素</span > </div > </body >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const h1 = document .querySelector ('h1' )const spanInDiv = document .querySelectorAll ('div span' )const span = document .querySelector ('#someText' )const span = document .querySelector ('.innerText' )const span = document .querySelector ('[data-uname="xiaoyezi"]' )const firstSpan = document .querySelector ('div span:first-child' )const secondSpan = document .querySelector ('div span:nth-child(2)' )const lastSpan = document .querySelector ('div span:last-child' )
其他不常用的获取元素方法
document.getElementById('id'): 返回指定 id 的元素对象, 没有匹配的元素时返回 nulldocument.getElementsByClassName('类名'): 返回包含所有匹配的元素对象的 HTMLCollection 对象, 没有匹配的元素时返回空的 HTMLCollection 对象document.getElementsByTagName('标签名'): 返回包含所有匹配的元素对象的 HTMLCollection 对象, 没有匹配的元素时返回空的 HTMLCollection 对象
常用属性 在元素的 HTML 标签中的书写的属性, 如 <img src="/images/00001.png" alt="小叶子">, 可以通过 element.属性名 获取和修改
属性或方法
描述
element.属性名
获取或设置元素的属性值, 如 src、href、title 等
element.getAttribute('属性名')
获取元素的指定属性值
element.setAttribute('属性名', '属性值')
设置元素的指定属性值
element.removeAttribute('属性名')
移除元素的指定属性
element.hasAttribute('属性名')
判断元素是否含有指定属性, 返回布尔值
1 2 3 4 5 6 7 const img = document .querySelector ('#favicon' )img.src = '/images/00002.png'
控制内容
属性或方法
描述
element.innerHTML
返回元素内的 HTML 片段, 包括所有的标签、空白、缩进; 设置 innerHTML 的值会解析 HTML 片段并替换元素的内容
element.innerText
返回元素及其所有子元素的文本内容, 没有 <script> 和 <style> 元素的标签和文本; 设置 innerText 的值会替换元素的内容为纯文本 不解析HTML标签
element.textContent
样式类似于 innerHTML, 但不显示 script 和 style 标签, 只显示其文本; 并且设置 textContent 的值也会替换元素的内容为纯文本
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 <div id ="demo" > 我是 <span > 小叶子</span > . <script > alert ('你好' )</script > <style > #demo span { color : red; } </style > </div > <script > const demo = document .querySelector ('#demo' ) console .log (demo.innerHTML ) console .log (demo.innerText ) console .log (demo.textContent ) </script >
控制样式
属性或方法
描述
element.style.样式名
元素的 style 对象的属性值 字符串
element.style.setProperty('样式名', '样式值')
设置元素的 style 对象的属性值 字符串
element.className
元素的 class 属性值 字符串
element.classList
元素的 class 属性值 DOMTokenList 对象
element.classList.add('类名')
添加类名
element.classList.remove('类名')
移除类名
element.classList.toggle('类名')
切换类名, 存在则移除, 不存在则添加
element.classList.contains('类名')
判断是否含有类名
element.classList.replace('旧类名', '新类名')
替换类名
特殊属性名的使用
部分属性名, 如 background-color, 含有特殊字符 -, 不能直接使用
可以用短驼峰命名法, 如 element.style.backgroundColor
也可以用引号包裹, 如 element.style['background-color']
但 --xxx 变量必须用 style.setProperty() 方法设置, 不能用以上两种方法; 且内容必须包含"", 如 element.style.setProperty('--xxx', '"#fff"')
样式优先级
!important > 内联样式 > id 选择器 > class 选择器 > 标签选择器
因为 element.style.样式名 是修改内联样式, 而另两者是修改 class
所以 element.style.样式名 优先级高于另两者
1 2 3 4 5 6 7 8 9 10 const demo = document .querySelector ('#demo' )demo.style .width = '100px' demo.classList .add ('demo' ) demo.className = ''
表单操作
form 元素对象的方法
描述
element.submit()
form 元素提交表单
element.reset()
form 元素重置表单
input 对象的属性和方法
描述
element.focus()
input 元素获取焦点
element.blur()
input 元素失去焦点
element.select()
input 元素选中内容
element.value
input 元素的值
element.innerHTML
得不到表单内容
element.checked
true 或 false
element.disabled
true 或 false
select 对象的属性和方法
描述
element.selectedIndex
select 元素选中项的索引
element.options
select 元素的所有选项
element.options[index]
select 元素的指定选项
element.options[index].selected
true 或 false
button 对象的属性和方法
描述
element.disabled
true 或 false
element.click()
button 元素点击
element.onclick = function() {}
button 元素点击事件
注意: value 属性才是用户输入的值, placeholder 是提示内容
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 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <input type ="text" value ="密码" > <button disabled > 按钮</button > <input type ="checkbox" class ="agree" > <script > const input = document .querySelector ('input' ) const btn = document .querySelector ('button' ) const checkbox = document .querySelector ('.agree' ) btn.disabled = false btn.onclick = function ( ) { if (input.type === 'password' ) { input.type = 'text' } else { input.type = 'password' } checkbox.checked = !checkbox.checked } </script > </body > </html >
自定义属性 HTML 标签可以添加自定义属性, 但要在属性名前加上 data- 前缀, 如 <img src="/images/00001.png" data-uname="xiaoyezi">, 引用时要加上 dataset 前缀, 如 img.dataset.uname
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const img = document .querySelector ('#favicon' )img.dataset .uname = 'leaf' if (img.dataset .uname === 'xiaoyezi' ) { console .log ('你好, 小叶子' ) } else { console .log ('你好, 陌生人' ) }
滚动事件相关
属性或方法
描述
element.clientWidht
返回元素的可视宽度像素数值 , 不包括边框; 只读
element.clientHeight
返回元素的可视高度像素数值 , 不包括边框; 只读
element.clientLeft
返回元素的相对于父元素的左侧外边距像素数值 , 不包括边框; 只读
element.clientTop
返回元素的相对于父元素的顶部外边距像素数值 , 不包括边框; 只读
element.offsetWidth
返回元素的可视宽度像素数值 , 包括边框; 只读
element.offsetHeight
返回元素的可视高度像素数值 , 包括边框; 只读
element.offsetLeft
返回元素的相对于父元素的左侧外边距像素数值 , 包括边框; 只读 可用于制作导航栏的下划线效果
element.offsetTop
返回元素的相对于父元素的顶部外边距像素数值 , 包括边框; 只读 可用于制作导航栏的下拉效果
element.getBoundingClientRect()
返回元素的 DOMRect 对象, 包含相对于视口的 top、left、right、bottom、width、height 等属性, 包括边框; 可读写
element.scrollTop
元素的顶部被滚出的部分的像素数值 , 可读写 ; 一般取 HTML 对象, 即 document.documentElement.scrollTop
element.scrollLeft
元素的左侧被滚出的部分的像素数值 , 可读写 ; 一般取 HTML 对象, 即 document.documentElement.scrollLeft
window.scrollTo(x, y)
将页面滚动到指定位置, 参数为 x 和 y 坐标数值, 单位为像素 例如, 执行 window.scrollTo(0, 0), 则页面滚动到顶部
设置滚动平滑过渡
1 2 3 html { scroll-behavior: smooth; }
元素拖拽 HTML5 提供了一系列的拖放事件, 用于实现元素的拖拽功能; 要实现拖拽, 首先要设置元素的 draggable 属性为 true, 然后监听相关事件 (如果内容较多可以使用事件委托)
详见MDN
事件
描述
回调函数参数
drag
当拖拽元素或选中的文本时触发
event (target 为拖拽元素)
dragstart
当开始拖拽元素时触发
event (target 为拖拽元素)
dragend
当拖拽操作结束时触发
event (target 为拖拽元素)
dragenter
当拖拽元素进入目标元素时触发一次
event (target 为目标元素)
dragover
当拖拽元素时每 100ms 触发一次
event (target 为目标元素)
dragleave
当拖拽元素离开目标元素时触发
event (target 为目标元素)
drop
当拖拽元素在可释放元素 上释放时触发
event (target 为目标元素)
操作系统向浏览器拖拽文件不属于 HTML5 拖拽事件, 但可以通过 drop、dragover、dragenter、dragleave 事件监听
上面的 event.target 是指当这些事件附加在被拖拽的元素上时的指向
如果需要向目标元素传递数据, 可以使用 dataTransfer 对象, 并在其身上设置 drop 或 dragover 事件
在事件处理函数中, 推荐使用 event.preventDefault() 阻止默认行为, 因为拖拽往往会有很多浏览器的默认行为
如果需要控制不同的元素是否可以接受拖拽的元素, 或设置不同的接受反应, 可以使用自定义属性
dataTransfer dataTransfer 对象用于保存拖拽元素的数据, 通过 event.dataTransfer 获取, 用于在拖拽元素和释放元素之间传递数据 (常常在 dragstart 事件中设置数据, 在 drop 事件中获取数据)
方法
描述
setData('数据类型', '数据')
设置拖拽元素的数据
getData('数据类型')
获取释放元素的数据
setDragImage('元素', x, y)
设置拖拽时的预览图片, x 和 y 为图片的偏移量
dropEffect
设置释放元素的效果, 如 copy, move, link 等
getData('数据类型')
获取释放元素的数据
上面的 数据类型 是自定义的, 用于区分不同的数据, 如 application/json, text/mydata 等; 可以将其视作 key
拖放文件 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 41 42 43 44 45 46 47 48 export default function DropFile ( ) { const element = useRef<HTMLDivElement | null >(null ) const [text, setText] = useState<string >('拖拽文件到此处' ) const onDragOver = (e: DragEvent ) => { e.stopPropagation () e.preventDefault () } const onDragEnter = (e: DragEvent ) => { e.stopPropagation () e.preventDefault () setText ('松开鼠标' ) } const onDragLeave = (e: DragEvent ) => { e.stopPropagation () e.preventDefault () setText ('拖拽文件到此处' ) } const onDrop = (e: DragEvent ) => { e.stopPropagation () e.preventDefault () const files = e.dataTransfer ?.files ?? [] setText (` 已拖拽 ${files.length} 个文件: ${files.map(file => { return `${file.name} - ${file.size} 字节` }).join('\n' )} ` ) } return ( <div onDrop ={onDrop} onDragOver ={onDragOver} onDragEnter ={onDragEnter} onDragLeave ={onDragLeave} ref ={element} > {text} </div > ) }
事件 指当系统或用户进行某些操作时 如点击, 自动调用相应的函数, 这些操作称为事件 , 相应的函数称为事件处理函数
事件类型
事件类型
描述
事件类型
描述
compositionstart
拼音输入开始事件 (中文输入中, 开始打字时触发)
compositionend
拼音输入结束事件 (中文输入中, 选词完成后触发)
click
鼠标点击事件
dblclick
鼠标双击事件
mouseover
鼠标移入事件
mouseout
鼠标移出事件
mouseenter
鼠标移入事件, 不会冒泡
mouseleave
鼠标移出事件, 不会冒泡
mousemove
鼠标移动事件
mousedown
鼠标按下事件
mouseup
鼠标松开事件
keydown
键盘按下事件
keyup
键盘松开事件
keypress
键盘按动事件
focus
元素获取焦点事件
blur
元素失去焦点事件
change
元素内容改变事件input 元素内容改变 并且失去焦点后触发
input
元素内容输入事件, 注意中文输入时拼音也会触发
load
所有资源加载完毕事件 含样式表图片等外部资源 通常添加到 window 对象
DOMContentLoaded
HTML 文档的加载完毕事件 不含图片等外部资源 通常添加到 document 对象
resize
浏览器窗口大小改变事件 通常添加到 window 对象
scroll
滚动条滚动事件 通常添加到 window 对象 但只要有滚动条就能用
select
文本选中事件
submit
表单提交事件
reset
表单重置事件
contextmenu
鼠标右键事件
error
资源加载错误事件
abort
资源加载终止事件
pageshow
页面显示事件
pagehide
页面隐藏事件
online
网络连接事件
offline
网络断开事件
message
消息通信事件
storage
本地存储事件
beforeunload
页面关闭事件
dragstart
拖拽开始事件
drag
拖拽事件
dragend
拖拽结束事件
dragenter
拖拽进入事件
dragover
拖拽悬停事件
dragleave
拖拽离开事件
drop
拖拽放置事件
touchstart
触摸开始事件
touchmove
触摸移动事件
touchend
触摸结束事件
touchcancel
触摸取消事件
animationstart
动画开始事件
animationend
动画结束事件
animationiteration
动画重复事件
transitionstart
过渡开始事件
transitionend
过渡结束事件
transitionrun
过渡运行事件
事件监听
元素对象事件监听方法
描述
element.addEventListener('事件类型', 事件处理函数, 选项)
为元素添加事件监听, 选项非必填
element.removeEventListener('事件类型', 事件处理函数, 选项)
为元素移除事件监听, 对匿名函数无效
element.onclick = function() {}element.onclick = null
为元素添加或移除点击事件监听
选项对象的属性
描述
capture
布尔值, 表示在捕获阶段 调用事件处理函数, 默认为 false
once
布尔值, 表示是否只调用一次事件处理函数, 默认为 false
passive
布尔值, 表示是否不会调用 preventDefault(), 默认为 false
true
只需要设置 capture 时, 等同于 { capture: true }
用 element.onclick 等 on 属性时, 元素的一个事件只能添加一个事件监听, 后面的会覆盖前面的
而 addEventListener, 一种事件可以添加多个事件监听
如果事件函数为某个对象原型的方法, 建议也放在匿名函数里; 否则要用 bind 方法绑定 this, 详见不同内容的弹出对话框 这个例子
1 2 3 4 5 6 7 8 9 10 11 const btn = document .querySelector ('#btn' )btn.addEventListener ('click' , function ( ) { console .log ('你好' ) })
事件监听实现随机点名
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 41 42 <!DOCTYPE html > <html > <body > <span > 名字是: </span > <span id ="name" > </span > <br > <button id ="btnStart" > 开始</button > <button id ="btnStop" disabled > 停止</button > <script > const name = document .querySelector ('#name' ) const btnStart = document .querySelector ('#btnStart' ) const btnStop = document .querySelector ('#btnStop' ) const names = ['小叶子' , '张三' , '李四' , '王五' , '赵六' , '田七' ] let history = -1 let timer = null btnStart.addEventListener ('click' , function ( ) { clearInterval (timer) timer = setInterval (function ( ) { let index = parseInt (Math .random () * names.length ) while (index === history) { index = parseInt (Math .random () * names.length ) } name.innerHTML = names[index] history = index }, 100 ) btnStart.disabled = true btnStop.disabled = false }) btnStop.addEventListener ('click' , function ( ) { clearInterval (timer) btnStop.disabled = true btnStart.disabled = false }) </script > </body > </html >
事件监听实现 tab 切换
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 41 42 43 44 45 46 <!DOCTYPE html > <html > <body > <style > .content { display : none; } .active { display : block; } </style > <div id ="tab" > <button > 按钮1</button > <button > 按钮2</button > <button > 按钮3</button > </div > <br > <div id ="content" > <div class ="content active" > 内容1</div > <div class ="content" > 内容2</div > <div class ="content" > 内容3</div > </div > <script > const buttons = document .querySelectorAll ('#tab button' ) const divs = document .querySelectorAll ('#content div' ) function toggle (index ) { if (index < 0 || index > divs.length - 1 ) return document .querySelector ('#content .active' ).classList .remove ('active' ) divs[index].classList .add ('active' ) } for (let i = 0 ; i < divs.length ; i++) { buttons[i].addEventListener ('click' , function ( ) { toggle (i) }) } </script > </body > </html >
事件监听实现返回顶部按钮
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 <!DOCTYPE html > <html > <body > <button id ="btn" style ="position:fixed;display:none;" > 返回顶部</button > <script > const content = document .querySelector ('#content' ) const btn = document .querySelector ('#btn' ) window .addEventListener ('scroll' , function ( ) { const scrollTop = document .documentElement .scrollTop if (scrollTop > 100 ) { btn.style .display = 'block' } else { btn.style .display = 'none' } }) btn.addEventListener ('click' , function ( ) { document .documentElement .scrollTop = 0 }) </script > </body > </html >
阻止默认行为 在创建事件监听后, 我们有时会希望阻止浏览器默认的行为, 如点击链接时不跳转, 这时可以在事件处理函数中使用事件对象的 event.preventDefault() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const form = document .querySelector ('form' )const input = document .querySelector ('input' )form.addEventListener ('submit' , function (event ) { if (input.value .length < 6 ) { event.preventDefault () alert ('密码长度不能小于 6 位' ) form.reset () } })
事件对象 任意事件类型被触发时与事件相关的信息会被以对象的形式记录下来, 这个对象称为事件对象, 可用于判断 keydown 事件的按键、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 const input = document .querySelector ('#input' )input.addEventListener ('keydown' , function (event ) { console .log (event) console .log (window .event ) console .log (event.key ) console .log (event.keyCode ) console .log (event.code ) })
事件对象的属性
描述
event.type
事件类型
event.target
事件目标对象 较新, 推荐
event.srcElement
事件目标对象 较老
event.currentTarget
事件当前对象
event.clientX / Y
鼠标相对于浏览器窗口可视区域的水平 / 垂直坐标
event.pageX / Y
鼠标相对于文档的水平 / 垂直坐标
event.offsetX / Y
鼠标相对于事件目标对象的水平 / 垂直坐标
event.altKey
是否按下 Alt 键
event.ctrlKey
是否按下 Ctrl 键
event.shiftKey
是否按下 Shift 键
event.button
鼠标按键, 0: 左键, 1: 中键, 2: 右键
event.keyCode
键盘按键的键码, 如 65, 已弃用, 请用 event.code
event.key
键盘按键的键名, 如 a
event.code
键盘按键的键码, 如 KeyA
详见 MDN
按下回车键时提交表单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const form = document .querySelector ('#form' )const textarea = document .querySelector ('#form textarea' )textarea.addEventListener ('keyup' , function (event ) { if (event.code === 'Enter' ) { if (event.ctrlKey ) { return } else { form.submit () form.reset () } } })
事件流 事件完整执行过程中的流动路径, 分为事件捕获 和事件冒泡 阶段; 添加事件监听时, 如果不做修改, 默认在事件冒泡阶段执行事件处理函数
古老的 onclick 事件监听只能在事件冒泡阶段执行事件处理函数
事件捕获 从 DOM 根元素开始去执行对应事件, 即点击内层元素时, 必须先依此执行外层元素的点击事件; 在下例中, 点击 div里的div 时, 会依此显示 document、div、div里的div
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <div id ="div1" > <div id ="div2" > </div > </div > <script > const div1 = document .querySelector ('#div1' ) const div2 = document .querySelector ('#div2' ) document .addEventListener ('click' , function ( ) { console .log ('document' ) }, true ) div1.addEventListener ('click' , function ( ) { console .log ('div' ) }, true ) div2.addEventListener ('click' , function ( ) { console .log ('div里的div' ) }, true ) </script >
事件冒泡 从事件目标元素开始向上执行对应事件, 即点击内层元素时, 会在执行后依此执行外层元素的点击事件; 在下例中, 点击 div里的div 时, 会依此显示 div里的div、div、document
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <div id ="div1" > <div id ="div2" > </div > </div > <script > const div1 = document .querySelector ('#div1' ) const div2 = document .querySelector ('#div2' ) document .addEventListener ('click' , function ( ) { console .log ('document' ) }) div1.addEventListener ('click' , function ( ) { console .log ('div' ) }) div2.addEventListener ('click' , function ( ) { console .log ('div里的div' ) }) </script >
event.stopPropagation 阻止事件传播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <div id ="div1" > <div id ="div2" > </div > </div > <script > const div1 = document .querySelector ('#div1' ) const div2 = document .querySelector ('#div2' ) document .addEventListener ('click' , function ( ) { console .log ('document' ) }) div1.addEventListener ('click' , function ( event ) { console .log ('div' ) event.stopPropagation () }) div2.addEventListener ('click' , function ( ) { console .log ('div里的div' ) }) </script >
此时, 点击 div里的div 时, 只会显示 div里的div、div, 不会显示 document; 而点击 div 时, 只会显示 div
事件委托 将事件添加到父元素上, 目标元素的事件通过冒泡传递给父元素, 然后根据事件对象 event.target 对象(即实际点击的那个元素对象), 来执行对应的目标元素的事件处理函数, 这样可以减少事件监听的数量, 提高性能
判断目标元素的方法
描述
event.target.nodeName
事件对象的 target 属性的 nodeName 属性, 即实际点击的那个节点 的标签名名
event.target.tagName
事件对象的 target 属性的 tagName 属性, 即实际点击的那个元素节点 的标签名
event.target.dataset.xxx
事件对象的自定义属性, 最直观, 推荐此方法 ; 注意: 它是字符串型数据, 必要可用 + 转换成数字
前两个的区别在于 nodeName 适用范围更广, 见本章开头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <ul id ="list" > <li > 1</li > <li > 2</li > <li > 3</li > <p > 4</p > </ul > <script > const list = document .querySelector ('#list' ) list.addEventListener ('click' , function (event ) { if (event.target .nodeName === 'LI' ) { event.target .style .color = 'red' } else if (event.target .tagName === 'P' ) { event.target .style .color = 'green' } }) </script >
事件委托实现 tab 切换
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 41 42 43 44 45 46 47 48 49 50 <!DOCTYPE html > <html > <body > <style > .content { display : none; } .active { display : block; } </style > <div id ="tab" > <button > 按钮1</button > <button > 按钮2</button > <button > 按钮3</button > </div > <br > <div id ="content" > <div class ="content active" > 内容1</div > <div class ="content" > 内容2</div > <div class ="content" > 内容3</div > </div > <script > const button = document .querySelector ('#tab' ) const divs = document .querySelectorAll ('#content div' ) function toggle (index ) { if (index < 0 || index > divs.length - 1 ) return document .querySelector ('#content .active' ).classList .remove ('active' ) divs[index].classList .add ('active' ) } button.addEventListener ('click' , function (event ) { if (event.target .nodeName === 'BUTTON' ) { const index = Array .from (button.children ).indexOf (event.target ) toggle (index) } }) </script > </body > </html >
Array.from() 方法从一个类似数组或可迭代对象创建一个新的, 浅拷贝的数组实例, 详见 MDN
节点操作 查找节点 与 document.querySelector() 不同, 这里会介绍一些通过元素节点间的关系, 从一个元素节点查找另一个元素节点的方法; 下面的方法可以嵌套使用
查找节点方法
查找元素节点方法
描述
element.parentNode
element.parentElement
返回元素的父节点
element.childNodes
element.children
返回元素的所有子节点 前者返回 NodeList 对象 后者返回 HTMLCollection 对象
element.firstChild
element.firstElementChild
返回元素的第一个子节点
element.lastChild
element.lastElementChild
返回元素的最后一个子节点
element.previousSibling
element.previousElementSibling
返回元素的前一个兄弟节点
element.nextSibling
element.nextElementSibling
返回元素的后一个兄弟节点
第二行只能返回元素 节点, 第一行可以返回任意节点; HTML 文档中不出现单独的文字时, 两者区别不大, 参见本章开头 ; 但要注意, element.parentElement 无法返回 document 对象, 而是返回 null
1 2 3 4 5 6 7 8 9 const div = document .querySelector ('div' )div.parentNode .children [0 ].innerHTML = '啦啦啦'
操作节点
方法
描述
document.createElement('标签名')
创建元素节点, 不写入 HTML 文档
element.append(元素对象, ...)
向元素的子元素列表末尾添加一个或多个子元素
element.prepend(元素对象, ...)
向元素的子元素列表开头添加一个或多个子元素
element.insertBefore(元素对象, 参考元素)
在元素的子元素列表中, 将一个子元素插入到参考元素的前面
element.removeChild(元素对象)
从元素的子元素列表中, 移除一个子元素
element.replaceChild(元素对象, 旧元素对象)
在元素的子元素列表中, 用新元素替换旧元素
element.cloneNode(true)
复制元素, 参数为 true 时复制元素及其子元素, 参数为 false 时只复制元素
element.remove()
移除元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const img = document .createElement ('img' )img.src = '/images/00001.png' const div = document .querySelector ('div' )div.appendChild (img) div.insertBefore (img, div.firstElementChild )
BOM Browser Object Model, BOM , 浏览器对象模型, 是 JavaScript 操作浏览器的接口, 它将浏览器的各个组成部分封装为对象, 提供各种操作浏览器功能的方法和接口
window 对象 是 BOM 的核心对象, 它表示浏览器的一个实例, 即一个 window 对象就是一个浏览器窗口, console、document 、location、history、alert 等都是 window 对象的属性
location 对象 表示当前页面的 URL 信息, 可以用它来获取或设置当前页面的 URL, 也可以用它来操作浏览器的历史记录
属性或方法
描述
location.href
返回或设置当前页面的 URL
location.protocol
返回或设置当前页面的协议, 如 http:、https:
location.host
返回或设置当前页面的主机名和端口号, 如 www.baidu.com:80
location.hostname
返回或设置当前页面的主机名, 如 www.baidu.com
location.port
返回或设置当前页面的端口号, 如 80
location.pathname
返回或设置当前页面的路径名, 如 /index.html
location.hash
返回或设置当前页面的哈希值, 如 #/profile
location.assign(URL)
加载新的页面, 参数为 URL
location.replace(URL)
替换当前页面, 参数为 URL
location.reload()
重新加载当前页面; 同 F5
location.reload(true)
重新加载当前页面, 强制从服务器加载, 不使用缓存; 同 Ctrl + F5
location.toString()
返回当前页面的 URL
location.valueOf()
返回当前页面的 URL
查询字符串
属性或方法
描述
location.search
返回或设置当前页面的查询字符串, 如 ?id=1&name=xiaoyezi
location.searchParams
返回当前页面的查询字符串对象
location.searchParams.get(参数名)
返回当前页面的查询字符串中指定参数名的参数值
location.searchParams.set(参数名, 值)
设置当前页面的查询字符串中指定参数名的参数值
location.searchParams.has(参数名)
判断当前页面的查询字符串中是否含有指定参数名的参数
location.searchParams.delete(参数名)
删除当前页面的查询字符串中指定参数名的参数
location.searchParams.append(参数名, 值)
向当前页面的查询字符串中添加指定参数名的参数
location.searchParams.entries()
返回当前页面的查询字符串中所有参数的键值对
location.searchParams.keys()
返回当前页面的查询字符串中所有参数的键
location.searchParams.values()
返回当前页面的查询字符串中所有参数的值
location.searchParams.length
返回当前页面的查询字符串中的参数的个数
navigator 对象 表示浏览器的信息, 如浏览器的名称、版本、语言、操作系统等
属性或方法
描述
navigator.userAgent
返回浏览器的 User-Agent 头部信息
navigator.appName
返回浏览器的名称
navigator.appVersion
返回浏览器的版本
navigator.language
返回浏览器的语言
navigator.platform
返回浏览器的操作系统
navigator.onLine
返回浏览器是否在线
navigator.cookieEnabled
返回浏览器是否启用了 cookie
navigator.wakeLock
wakeLock API
保持屏幕常量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 document .addEventListener ('visibilitychange' , () => { if (document .visibilityState === 'visible' ) { navigator.wakeLock .request ('screen' ) } }) let isWakeLock : boolean = false document .addEventListener ('visibilitychange' , () => { if (document .visibilityState === 'visible' && !isWakeLock) { navigator.wakeLock .request ('screen' ).then ((lock ) => { isWakeLock = true lock.addEventListener ('release' , () => { isWakeLock = false }) }) } })
history 对象 表示浏览器的历史记录, 可以用它来进行前进、后退、跳转等
属性或方法
描述
history.back()
返回上一页
history.forward()
前进到下一页
history.go(步数)
前进或后退指定步数, 如 1、-1
screen 对象 表示屏幕的信息, 如屏幕的宽度、高度、颜色深度等
属性或方法
描述
screen.width
返回屏幕的宽度
screen.height
返回屏幕的高度
screen.availWidth
返回屏幕的可用宽度
screen.availHeight
返回屏幕的可用高度
screen.colorDepth
返回屏幕的颜色深度
screen.pixelDepth
返回屏幕的像素深度
screen.orientation
返回屏幕的方向
localStorage 对象 HTML5 提供的本地存储方案, 可以用它来存储数据, 数据不会随着页面的刷新或关闭而丢失, 除非手动删除; 数据以键值对的形式存储, 键和值都是字符串 型数据, 容量约为 5MB; 同一个域名下的不同页面可以共享数据
方法
描述
localStorage.setItem(键, 值)
存储或修改数据
localStorage.getItem(键)
获取数据
localStorage.removeItem(键)
删除数据
localStorage.clear()
清空所有数据
非字符串内容必须以 json 字符串的形式存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const obj = { name : 'xiaoyezi' , age : 18 } localStorage .setItem ('obj' , JSON .stringify (obj))const obj = JSON .parse (localStorage .getItem ('obj' )) || {}localStorage .removeItem ('obj' )
sessionStorage 对象 跟 localStorage 类似, 但数据会随着页面关闭而丢失
方法
描述
sessionStorage.setItem(键, 值)
存储或修改数据
sessionStorage.getItem(键)
获取数据
sessionStorage.removeItem(键)
删除数据
sessionStorage.clear()
清空所有数据
正则表达式 Regular Expression , 简称 RegExp, 是一种多种语言使用的用来匹配字符串的规则, 常用于表单验证、爬虫、文本编辑器等; 正则表达式属于对象
属性或方法
描述
reg.test(字符串)
返回布尔值, 判断字符串是否匹配正则表达式
reg.exec(字符串)
返回数组, 值依次为匹配到的字符串、索引、原字符串
字符串.match(reg)
返回数组, 值依次为匹配到的字符串、索引、原字符串
字符串.replace(reg, 替换字符串)
返回字符串, 将匹配到的字符串替换为指定字符串
字符串.search(reg)
返回第一个匹配到的字符串的索引, 没有匹配到则返回 -1
字符串.split(reg)
返回数组, 将字符串按匹配到的字符串分割成数组
1 2 3 4 5 6 7 const reg = /xiaoyezi/ const reg = new RegExp ('xiaoyezi' )console .log (reg.test ('xiaoyezi' )) console .log (reg.test ('xiaoyezixxx' )) console .log (reg.test ('yezi' ))
语法规则
普通字符 : 匹配自身, 如 xiaoyezi、b、c、1、2、3 等
元字符 : 具有特殊含义的字符, 如 .、*、+、?、$、^、|、[、]、(、)、{、}、\ 等
空白字符 : 空格、制表符、换行符等, 如 \n、\t、\r、\f、\v、\s
非空白字符 : 除空白字符以外的任意字符
元字符
描述
元字符
描述
.
匹配除换行符以外的任意字符
*
匹配前面的字符 0 次或多次
^xxx
匹配字符串的开始
+
匹配前面的字符 1 次或多次
xxx$
匹配字符串的结尾
?
匹配前面的字符 0 次或 1 次
^xxx$
精确匹配
{n}
匹配前面的字符 n 次
[abc]
匹配方括号中的任意一个 字符
{n,}
匹配前面的字符 n 次或多次
[^abc]
匹配除方括号中的任意一个字符
{n,m}
匹配前面的字符 n 次到 m 次
[a-z]
匹配任意小写字母
x|y
匹配 x 或 y
[A-Z]
匹配任意大写字母
\b
匹配单词的结尾, 单词按空格分隔
[0-9] 或 \d
匹配任意数字
\B
匹配非单词的结尾
[^0-9] 或 \D
匹配除数字以外的任意字符
\s
匹配任意空白字符
[a-zA-Z0-9_] 或 \w
匹配任意字母数字下划线
\S
匹配任意非空白字符
[^a-zA-Z0-9_] 或 \W
匹配除字母数字下划线以外的任意字符
\特殊字符
通过转义字符匹配特殊字符
\n
匹配换行符
\t
匹配制表符
\r
匹配回车符
\f
匹配换页符
\v
匹配垂直制表符
(pattern)
详见这里
可以在这里 测试正则表达式, 在这里 查看正则表达式的常用规则
优先级 : \ > [] / () > * + ? {} > ^ $ \x > |
贪婪匹配 : 默认情况下, 正则表达式会尽可能多的匹配字符, 如 /\d+/ 会匹配 123, 而不是 1
非贪婪匹配 : 在贪婪匹配的基础上, 加上 ?, 则会尽可能少的匹配字符, 如 /\d+?/ 会匹配 1, 而不是 123
[] 等限定符 只匹配一个字符, 应配合 {} 等数量符 使用; 如应该用 ^\w+$ 检验一个字符串是否为纯字母数字下划线, 而非用 ^\w$
在书写和阅读时, 可以把限定符和数量符的组合看作一个小块, 然后拼接成放在 ^ 和 $ 之间的完整块
修饰符 在正则表达式的末尾添加修饰符, 可以改变正则表达式的匹配方式, 如 /abc/im 中, i 和 m 就是修饰符
修饰符
描述
i
忽略大小写
m
多行匹配; 即 ^ 和 $ 匹配每一行 以\n分界 的开头和结尾
s
使 . 匹配包括换行符 在内的所有字符
g
全局匹配; 即匹配所有符合条件的结果, 而不是第一个; 常用于 replace() 方法
代码执行机制
宿主环境 : JavaScript 运行的环境, 如浏览器、Node.js、Deno、Bun 等
浏览器内核 : 浏览器的核心, 也叫排版引擎、浏览器引擎、页面渲染引擎, 负责解析 HTML、CSS、JavaScript, 并渲染页面; 如 Chrome 的 Blink 内核、Safari 的 WebKit 内核
JavaScript 解释器 : 浏览器内核中的一部分, 也叫 JavaScript 引擎, 负责解释 JavaScript 代码并将其转换为机器码执行; 如 Chrome 的 V8 引擎、Safari 的 JavaScriptCore 引擎
每个宿主环境都有 JavaScript 解释器, Chrome浏览器、Edge浏览器、Node.js、Deno 为 V8 引擎, Safari浏览器、Bun 为 JavaScriptCore 引擎
宏任务和微任务
事件循环
严格意义上讲, 如今的 JavaScript 基础的知识绝大部分属于 ECMAScript, ES 的知识体系, 而 Web API 是浏览器对 ES 的扩展, 包括 DOM、BOM 等
但现在一般不对 ECMAScript 和 JavaScript 进行严格区分, 包括 浏览器、Node.js、Deno、Bun 等运行环境都可以说是使用了 JavaScript 语言
关于 JavaScript、ECMAScript 的关系和历史, 详见这篇文章
V8 引擎Chrome 浏览器的 JavaScript 引擎, 由 Google 公司使用 C++ 语言编写, 可以解释 JavaScript 和 WebAssembly 代码, 也被用于 Node.js、Deno 等环境; 可以在 GitHub 上查看 V8 的源代码(超过 100 万行); 以下是其主要运行机制, 可以参见这篇文章
Parser 解析器 : 将 JavaScript 代码解析为抽象语法树 AST词法分析 : 将代码分解为词法单元, 如 let、function、varName、varValue、+、; 等语法分析 : 将词法单元转换为 AST 可以在这个网站 查看指定代码的 AST; Vue 等代码也会被解析为 AST 在解析过程中, 全局对象 window 会被创建, var 声明的变量会被挂载到 window 上
Ignition 解释器 : 将 AST 转换为字节码, 执行字节码Bytecode: 一种中间代码, 类似于汇编语言 由于需要跨平台, 所以 V8 不会直接将 AST 转换为机器码, 而是转换为 Bytecode(类似于 Java 的 JVM) 但是字节码的效率比机器码低, 所以引入了下面的 TurboFan 编译器
TurboFan 编译器 : 将热点代码(HotSpot)的字节码编译为机器码, 执行机器码 为什么不直接全部转为机器码: 编译过程耗时且占用内存, 而且有些代码只执行一次, 不值得编译为机器码 有些时候 TurboFan 会将机器码再转为 Bytecode, 如 函数传参的类型发生变化 等情况 所以 TypeScript 虽然仍会被解析为 JavaScript, 但性能可能会稍好一些
PreParser 预解析器 : V8 引擎会先对代码进行预解析, 找出所有函数和变量 (但不一定解析其全部的逻辑), 然后再进行正式解析(针对运行的需要对函数和变量进行选择性解析); 这样可以提高解析速度, 但会占用更多内存
事件循环 JavaScript 是一门单线程语言, 即一次只能执行一条语句, 后面的语句必须等前面的语句执行完毕后才能执行, 这种执行机制称为同步执行 ; 但有时我们需要执行一些耗时的操作, 如 AJAX 请求、定时器等, 这时就需要异步执行 , 即不用等待前面的语句执行完毕就可以执行后面的语句
异步任务 : 调用后会消耗一定时间, 如 setTimeout、setInterval、fetch 请求, 但不会阻塞后续代码的执行, 并在将来任务完成后执行回调函数
同步任务都在执行引擎的主线程上执行, 形成一个执行栈 Execution Context Stack
异步代码会被放入宿主环境 (浏览器、Node.js 等)中进行计时或等待
异步任务完成后, 宿主环境会将回调函数放入任务队列 中
执行引擎会先执行 执行栈中的同步任务
同步任务执行完毕后会去任务队列中查看是否有异步任务 , 如果有则将其放入执行栈 中执行
以上过程称为事件循环 Event Loop
网页的多任务处理(计时器计时等)是由浏览器(宿主环境)完成的, 它负责向任务队列里添加任务
作用域 某个关键字、变量名、函数名的能够被访问到的范围; 一些变量只能在特定的范围内使用, 避免了命名冲突和逻辑混乱
全局作用域 : 作用于一个 .js 文件或一个 <script> 标签最外层的代码环境
局部作用域 : 分为函数作用域和块级作用域
函数作用域 : 函数内部的代码环境, 内部声明 的变量只能在函数内部使用, 包括 let、const、var
块级作用域 : 作用于 {} 内部的代码环境, 如 if、for, 内部声明的 let 和 const 变量无法在外部使用; var 没有块级作用域
处于全局作用域的变量叫全局变量, 处于局部作用域的变量叫局部变量
作用域链 : 程序会优先使用最近的局部变量, 若不存在则逐级向上查找, 直到全局作用域
如果一个变量没有声明, 直接赋值, 会自动变成全局变量, 包括函数内部
为了更好的性能和避免命名冲突, 应该尽量避免使用全局变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (function func ( ) { var a = 1 let b = 2 })(); for (let c = 3 ; c < 5 ; c++) { console .log (c) }for (var d = 4 ; d < 6 ; d++) { console .log (d) }(function global ( ) { e = 5 })(); console .log (a) console .log (b) console .log (c) console .log (d) console .log (e)
垃圾回收机制 Garbage Collection , 简称 GC, 是 JavaScript 中的自动管理内存的机制; 当内存不再使用时, 就需要将其释放, 否则会造成内存泄漏
生命周期 : 分配内存 → 使用内存 → 释放内存
分配内存 : 当声明变量、函数、对象时, 系统会自动分配内存
使用内存 : 读写变量、调用函数、访问对象属性等
释放内存 : 当变量、函数、对象不再使用时, 垃圾回收器会自动释放内存
全局变量的生命周期是永久的, 直到页面关闭
局部变量的生命周期是临时的, 使用完毕后, 局部变量就会被释放
内存泄漏 : 分配的内存未释放或无法释放, 可能导致程序占用的内存越来越多
垃圾回收算法
引用计数 : 当变量被引用时, 引用计数器加 1, 当变量不再被引用时, 引用计数器减 1, 当引用计数器为 0 时, 垃圾回收器会回收该变量; 但是这种算法无法解决循环引用的问题, 已经被淘汰
标记清除 : 定期扫描, 将无法从全局对象或其下级对象访问到的变量标记为 待回收, 并稍后回收它们; 现代浏览器都使用这种算法
栈内存 : 存储基本数据类型的值和引用类型的地址, 由系统自动分配和释放
堆内存 : 存储引用类型的值, 由程序员分配, 由程序员或垃圾回收器释放
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 let arr = [1 , 2 , 3 ]let num = arr[0 ]arr = null num = 0 (function func ( ) { let o1 = {} let o2 = {} o1.name = o2 o2.name = o1 })(); let arr = [1 , 2 , 3 ]let num = { number : arr[0 ] }arr = null num = null (function func ( ) { let o1 = {} let o2 = {} o1.name = o2 o2.name = o1 })();
闭包 Closure , 是指有权访问另一个函数作用域 即其外层函数 中的变量的函数, 这个函数和与其相关的变量构成闭包
作用 : 封闭数据, 实现数据为某个函数私有, 同时又可以让外部访问, 且不会被自动释放
原理 : 函数执行完毕后, 由于其内部的函数被外部函数表达式引用, 从而不会被垃圾回收器回收
问题 : 闭包可能会导致内存泄漏, 应适时手动释放闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function func ( ) { let num = 1 function inner ( ) { num++ return num } return inner } const numInFunc = func () console .log (numInFunc ()) console .log (numInFunc ()) console .log (num)
提升 变量提升 Hoisting , 是指在代码执行前, 会先将 var 变量的声明提升到作用域的最前面, 但不会提升变量的赋值
1 2 3 console .log (a) var a = 1 console .log (a)
以上代码等价于
1 2 3 4 var aconsole .log (a) a = 1 console .log (a)
let 和 const 声明的变量不会发生变量提升, 会直接报错
函数提升 在代码执行前, 会先将函数的声明提升到作用域的最前面, 但不会提升函数的调用
1 2 3 4 5 6 7 8 func () function func ( ) { console .log (1 ) } exp () var exp = function ( ) { console .log (2 ) }
以上代码等价于
1 2 3 4 5 6 7 8 9 function func ( ) { console .log (1 ) } var expfunc () exp () exp = function ( ) { console .log (2 ) }
函数表达式不会发生函数提升 , 包括 var 声明的函数表达式, 如上述示例
this 指向 this 是一个关键字, 用于指向当前执行上下文的对象; 不同的场合, this 可能有意想不到的指向
默认指向
场合
this 指向
在全局作用域声明的普通函数
window, 严格模式下为 undefined
对象中用普通函数声明的方法
对象本身
window 下的各种方法, 如定时器
window
普通函数声明的事件处理函数
触发事件的元素
对象方法内的箭头函数
同对象方法内的 this, 即对象本身
箭头函数 声明的事件处理函数
window
箭头函数 声明的 prototype 方法
window
普通函数的 this 可以理解为谁调用就指向谁
箭头函数的 this 指向定义时的上下文, 不会改变, 不受调用者影响
事实上箭头函数中并不存在 this, 其访问的 this 是其所在作用域的 this 变量
箭头函数不能用作对象的方法和构造函数
内层函数(一般是箭头函数)不存在 this 时, 会向上级作用域查找, 直到找到为止
指定指向
方法
作用
func.call(thisTarget, arg1, arg2, ...)
调用函数 , 指定 this 指向 thisTarget, 并传入参数
func.apply(thisTarget, arr)
调用函数 , 指定 this 指向 thisTarget, 并传入数组 [arg1, arg2, ...]
func.bind(thisTarget)
创建新函数 , 指定新函数的 this 指向 thisTarget
注意: 上述方法仅适用于普通函数, 箭头函数的 this 无法改变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const arrowSayHi = ( ) => `Hello, I'm ${this .myName} . I'm ${this .age} years old.` const normalSayHi = function ( ) { return `Hello, I'm ${this .myName} . I'm ${this .age} years old.` }const me = { myName : 'xiaoyezi' , age : 18 }const cat = { myName : '小猫' , age : 2 }const dog = { myName : '小狗' , age : 3 }window .myName = 'leaf' window .age = 12 console .log (arrowSayHi ()) console .log (arrowSayHi.call (me)) console .log (arrowSayHi.apply (cat)) console .log (arrowSayHi.bind (dog)()) console .log (normalSayHi ()) console .log (normalSayHi.call (me)) console .log (normalSayHi.apply (cat)) console .log (normalSayHi.bind (dog)())
事件监听中的定时器使用 bind()示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const btn = document .querySelector ('#btn' )btn.addEventListener ('click' , function ( ) { this .disabled = true setTimeout (function ( ) { this .disabled = false }.bind (this ), 5000 ) setTimeout (() => this .disabled = false , 6000 ) })
拷贝
深拷贝和浅拷贝是针对引用类型的数据, 因为简单类型的数据赋值时, 直接将数据的值赋给了新的变量
直接赋值 : 只将对象的地址赋值给了新的变量, 两者指向同一块内存地址, 修改其中一个会影响另一个
浅拷贝 : 拷贝了对象的第一层属性, 但如果属性值是引用类型, 拷贝的是地址, 修改其中一个会影响另一个
深拷贝 : 拷贝了对象的所有属性, 包括引用类型, 两者完全互不影响
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 const arr = [1 , 2 , [3 , 4 , [5 , 6 ]]]const obj = {name : 'xiaoyezi' , age : 18 , habits : {eat : 'strawberry' , sleep : 'bed' }}const newArr = arr.slice ()const newArr = arr.concat ()const newArr = [...arr]const newObj = Object .assign ({}, obj)const newObj = {...obj}const newArr = JSON .parse (JSON .stringify (arr))const newArr = _.cloneDeep (arr)const newObj = _.cloneDeep (obj)function deepClone (obj ) { if (typeof obj !== 'object' || obj === null ) return obj let result = Array .isArray (obj) ? [] : {} for (let key in obj) { result[key] = deepClone (obj[key]) } return result } const newArr = deepClone (arr)const newObj = deepClone (obj)
利用递归和 setTimeout 模拟 setInterval
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function mySetInterval (fn, time ) { let timer = null function interval ( ) { fn () timer = setTimeout (interval, time) } interval () return { clear : function ( ) { clearTimeout (timer) } } }
递归函数 : 函数内部调用自身的函数, 称为递归函数, 递归函数必须有一个结束条件, 否则会陷入死循环
宏任务和微任务 ES6 中新增了 Promise 对象, 使得 JavaScript 也具有了发起和管理异步操作的能力; 从而引入了宏任务 和微任务 的概念
宏任务 : 由浏览器环境执行的异步任务 , 如 setTimeout、setInterval、I/O、UI 渲染等
微任务 : 由 JavaScript 引擎执行的异步任务 , 如 Promise、process.nextTick、MutationObserver 等
宏任务队列 : 存放宏任务的队列
微任务队列 : 存放微任务的队列, 优先级高于宏任务队列
任务
类别
setTimeout / setInterval
宏任务
<script> 脚本执行事件
宏任务
AJAX 请求
宏任务
用户交互事件
宏任务
promise.then() / promise.catch()
微任务
Promise 对象本身为同步任务
async 和 await 本质上是 Promise 的语法糖, 它们的执行顺序和 Promise 是一样的
解构赋值
一种快速为变量赋值的简洁语法, 本质上仍然是为变量赋值; 分为数组解构 和对象解构
数组解构 1 2 3 4 5 6 7 8 9 10 const arr = [1 , 2 , 3 ]const [a, b, c, d] = arrconsole .log (a, b, c, d) const a = arr[0 ] const b = arr[1 ] const c = arr[2 ] const d = arr[3 ]
变量的数量大于单元值数量时, 多余的变量将被赋值为 undefined
变量的数量小于单元值数量时, 可以通过 ... 获取剩余单元值, 但只能置于最末位
允许初始化变量的默认值, 且只有单元值为 undefined 时默认值才会生效
不需要的单元值可以用 , , 省略
1 2 3 4 5 6 7 8 9 10 11 12 const arr = [1 , 2 , 3 , 4 ]const [a, b, ...c] = arrconsole .log (a, b, c) const [a, b, c = 6 , d = 7 , e = 8 ] = arrconsole .log (a, b, c, d, e) const [a, , , d] = arr;[a, b] = [b, a]
直接使用 [a, b, ...] 数组值进行赋值或调用数组方法时, 应在前面加分号, 避免错误解析
对象解构 1 2 3 4 5 6 7 8 9 10 11 12 const obj = { name : 'xiaoyezi' , age : 18 } const {name : myName, age, gender} = objconsole .log (myName, age, gender) const myName = obj.name const age = obj.age const gender = obj.gender
对象中找不到与变量名一致的属性时, 变量值为 undefined
允许初始化变量的默认值, 属性不存在或单元值为 undefined 时默认值才会生效
1 2 3 4 5 6 7 8 const obj = { name : 'xiaoyezi' , age : 18 } const {name = 'leaf' , age = 12 , gender = 'male' } = objconsole .log (name, age, gender)
将对象解构作为形参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const msg = { code : 200 , data : { name : 'xiaoyezi' , age : 18 } } function func ({data: content} ) { console .log (content) }
多维解构 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 const arr = [1 , 2 , [3 , 4 ]]const [a, b, [c, d]] = arrconst [a, b, e] = arrconst obj = { name : 'xiaoyezi' , age : 18 , habits : { eat : 'strawberry' , sleep : 'bed' } } const {name, age, habits : {eat, sleep}} = objconst {name, age, habits} = objconst mix = [ 1 , 2 , 3 , { name : 'xiaoyezi' , age : 18 , habits : { eat : 'strawberry' , sleep : 'bed' } } ] const [a, b, c, {name, age, habits : {eat, sleep}}] = mix
类基础
本节内容了解即可, 实际开发中会用 class 关键字定义类, 后面会详细讲解
构造函数 构造函数是一种特殊的函数, 用于创建对象和功能封装 , 构造函数的名称一般以大写字母开头 , 以便区分, 如 Object、Array、Date; 如果一个函数使用 new 关键字调用 , 那么这个函数就是构造函数
使用 new 关键字调用函数的行为被称为实例化
new 关键字会创建一个新对象, 并将构造函数的 this 指向该对象, 最后返回该对象
实例对象 : 通过构造函数创建的对象, 即 new 关键字创建的对象
实例成员 : 实例对象的属性和方法, 即声明构造函数时, 函数体中用 this 添加的属性和方法
不同的实例对象互不影响
不使用 new 调用时, 构造函数的行为和普通函数一样
实例化构造函数时没有参数时可以省略 ()
构造函数内部的 return 无效, 返回值为新创建的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function Person (name, age ) { this .myName = name this .age = age this .author = 'xiaoyezi' this .sayHello = function ( ) { console .log (`Hello, I'm ${this .myName} ` ) } this .setNewName = function (newName ) { this .myName = newName } return 1 } let xiaoyezi = new Person ('小叶子' , 18 )let cat = new Person ('小猫' , 2 )let dog = new Person ('小狗' , 3 )let res = Person ('小叶子' , 18 )console .log (res)
静态成员 在 JavaScript 中, 底层函数本质上也是对象类型, 因此允许直接为函数动态添加属性或方法; 构造函数本身的属性和方法被称为静态成员
静态成员方法中的 this 指向构造函数本身
实例对象无法访问静态成员
构造函数对象无法访问实例成员
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 function Person (name, age ) { this .myName = name this .age = age this .author = 'xiaoyezi' this .sayHello = function ( ) { console .log (`Hello, I'm ${this .myName} ` ) } this .setNewName = function (newName ) { this .myName = newName } } Person .leg = 2 Person .walk = function ( ) { console .log (this .legs ) console .log (this .author ) } let xiaoyezi = new Person ('小叶子' , 18 )console .log (xiaoyezi.author ) console .log (xiaoyezi.legs )
各种数据类型的构造函数 用字面量声明各种数据类型时, 实际上是调用了构造函数, 如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let obj = new Object ({ name : 'xiaoyezi' , age : 18 }) let arr = new Array ('xiaoyezi' , 18 )let reg = new RegExp ('\\d+' )let str = new String ('xiaoyezi' )let num = new Number (18 )let bool = new Boolean (true )
原型对象 Prototype , 是 JavaScript 中的一个重要概念, 每个(构造)函数都有一个 prototype 属性, 指向一个对象, 这个对象就是原型对象
实例对象不能访问构造函数的静态成员, 但可以直接访问构造函数的原型对象的属性和方法
实例成员用 ins.xx 访问原型对象时, 原型对象中的 this 都指向实例对象
实例对象的 __proto__ 属性, 指向它的构造函数的原型对象, 即 ins.__proto__ === Func.prototype
实例成员用 ins.__proto__.xx 访问原型对象时, 原型对象中的 this 都指向原型对象本身
实例成员和原型对象成员重名时, 实例成员优先级高
对象实例化不会多次创建原型上函数, 从而能节约内存
构造函数对象访问原型对象时, 要使用 prototype 属性, 且原型对象的 this 指向原型对象本身
基于以上性质, 将实例方法写在原型对象上, 即不改变使用方式, 又能节约内存, 还能让构造函数对象访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function Person (name, age ) { this .myName = name this .age = age } Person .prototype .sayHello = function ( ) {console .log (`Hello, ${this .myName} ` )}Person .prototype .myName = 'xiaoyezi' let xiaoyezi = new Person ('小叶子' , 18 )xiaoyezi.sayHello () xiaoyezi.sayHello = function ( ) {console .log (`Hi, ${this .myName} ` )} xiaoyezi.sayHello () Person .myName = 'leaf' xiaoyezi.myName = 'leaf' Person .prototype .sayHello () xiaoyezi.__proto__ .sayHello ()
概念区分
类型
添加方法
访问方法
this 指向
构造函数 → 静态成员
Func.xx = xx
Func.xx
构造函数本身
实例对象 → 实例成员
构造函数中 this.xx = xx
ins.xx
实例对象
构造函数 → 原型对象
Func.prototype.xx = xx
ins.xxFunc.prototype.xxins.__proto__.xx
实例对象 原型对象本身 原型对象本身
实例对象和原型对象都无法访问静态成员
上述的 this 可以简单概括为 this 指向调用者
再次注意, 不要用箭头函数作为构造函数及其方法
constructor 属性 每个原型对象都有一个 constructor 属性, 指向它的构造函数, 即 Func.prototype.constructor === Func
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function Person (name, age ) { this .myName = name this .age = age } Person .myName = 'leaf' let xiaoyezi = new Person ('小叶子' , 18 )console .log (xiaoyezi.constructor === Person ) console .log (xiaoyezi.constructor .myName ) console .log (xiaoyezi.myName ) Person .prototype = { constructor : Person , cat : function ( ) {console .log ('I am a cat' )} }
原型继承 Prototype Inheritance, 是指一个对象继承另一个对象的属性和方法, JavaScript 中的继承是通过原型链实现的
原型链 每个对象都有一个 __proto__ 属性, 称为对象原型 , 指向它的构造函数的原型对象, 构造函数的原型对象也有一个 __proto__ 属性, 指向它的构造函数的原型对象, 以此类推, 形成了一个原型链
[[Prototype]] 与 __proto__ 相同, 但前者无法直接用 . 访问, 后者不是标准属性
访问属性和方法时, 会先在实例对象中查找, 再逐级向上查找, 直到找到为止
之前提到的大部分数组、字符串、数字等的方法, 都是通过原型链实现的, 如 Array.prototype.map()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function Person (name, age ) { this .myName = name this .age = age } let xiaoyezi = new Person ('小叶子' , 18 )console .log (xiaoyezi.__proto__ === Person .prototype ) console .log (xiaoyezi.__proto__ .__proto__ === Object .prototype ) console .log (xiaoyezi.__proto__ .__proto__ .__proto__ === null )
给所有数组添加新方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Array .prototype .sum = function ( ) { return this .reduce ((pre, cur ) => pre + cur, 0 ) } let arr = [1 , 2 , 3 , 4 , 5 ]console .log (arr.sum ()) Object .prototype .print = function ( ) { console .log (this ) } arr.print ()
用原型对象赋予公共方法
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 function Animal (src ) { this .miao = function ( ) { console .log ('miao~' ) } this .woof = function ( ) { console .log ('woof~' ) } this .constructor = src } function Cat (name, age ) { this .myName = name this .age = age } function Dog (name, age ) { this .myName = name this .age = age } Cat .prototype = new Animal (Cat )Dog .prototype = new Animal (Dog )Cat .miao () Dog .miao ()
即 child.prototype = new Parent(), child 继承了 Parent 的属性和方法; 记得重新赋值 constructor 属性
不同内容的弹出对话框
1 2 3 <button id ="btn1" > 问好</button > <button id ="btn2" > 提示</button > <button id ="btn3" > 睡觉</button >
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 41 42 43 44 45 46 function Box (title, content ) { this .title = title || '标题' this .content = content || '内容' } Box .prototype .open = function ( ) { const current = document .querySelector ('[data-id="notice"]' ) current && current.remove () let notice = document .createElement ('div' ) notice.dataset .id = 'notice' notice.innerHTML = ` <h2>${this .title} </h2> <p>${this .content} </p> <button>关闭</button> ` notice.style = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 200px; height: 120px; padding: 20px; border: 1px solid #000; background-color: #fff; ` document .body .appendChild (notice) notice.querySelector ('button' ).onclick = () => document .body .removeChild (notice) } document .querySelector ('#btn1' ).onclick = () => new Box ('问好' , '你好' ).open ()document .querySelector ('#btn2' ).onclick = function ( ) { new Box ('提示' , '这是一个提示' ).open () }const box = new Box ('睡觉' , '晚安' )document .querySelector ('#btn3' ).onclick = box.open .bind (box)
Function 实例的 bind(thisTarget, arg1, arg2, ...) 方法创建一个新函数, 当调用该新函数时, 它会调用原始函数并将其 this 关键字设置为给定的值, 其第二个参数开始为新函数的实参
instanceof 运算符 instanceof 运算符用于检测”实例对象的原型链上”是否含有”某个构造函数的原型对象”; 语法为 ins instanceof Func, 返回布尔值
1 2 3 4 5 6 7 8 9 10 11 12 13 function Person (name, age ) { this .myName = name this .age = age } let xiaoyezi = new Person ('小叶子' , 18 )console .log (xiaoyezi instanceof Person ) console .log (xiaoyezi instanceof Object ) console .log (Person .prototype instanceof Object )
getter / setter getter 和 setter 是对象的一种属性 (用属性的方式获取, 用函数的方式定义), 用于获取 和设置 对象的属性值
getter 用于获取对象的属性值, 可以在获取属性值时执行一些逻辑, 使用 get 关键字定义
setter 用于设置对象的属性值, 可以在设置属性值时执行一些逻辑, 使用 set 关键字定义
getter 和 setter 不能与属性名相同, 但它们两者可以同名
在定义时, getter 没有形参, setter 有一个形参, 即用户赋值操作赋的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const obj = { logs : ['log1' , 'log2' , 'log3' ], get latestLog () { return this .logs [this .logs .length - 1 ] }, set latestLog (log ) { this .logs .push (log) } } console .log (obj.latestLog ) obj.latestLog = 'log4' console .log (obj.latestLog )
删除和添加 getter 和 setter 可以通过 delete 关键字删除、通过 Object.defineProperty 方法添加
1 2 3 4 5 6 7 8 9 10 11 12 13 delete obj.latestLog console .log (obj.latestLog ) Object .defineProperty (obj, 'latestLog' , { get ( ) { return this .logs [this .logs .length - 1 ] }, set (log ) { this .logs .push (log) } }) console .log (obj.latestLog )
异常处理
throw
throw 语句用于抛出一个用户自定义的异常, 后面可以跟任何值
配合 Error 构造函数使用, 可以创建一个新的错误对象, 展示更详细的错误信息
throw 语句会立即终止函数的执行, 后面的代码不会执行
1 2 3 4 5 6 7 8 9 10 11 12 function divide (a, b ) { if (b === 0 ) { throw '除数不能为 0' } else if (typeof a !== 'number' || typeof b !== 'number' ) { throw new Error ('参数类型错误' ) } return a / b } divide (1 , 0 )
try catch finally
try、catch、finally 语句用于处理异常
try 语句用于测试代码块, 如果有异常则跳转到 catch 语句
catch 语句用于处理异常, 可以获取异常信息
finally 语句用于无论是否有异常都会执行的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function func (x ) { try { } catch (e) { console .log (e.message ) return '出现异常' } finally { console .log ('测试完成' ) } return xxx }
debugger
debugger 语句用于在代码中设置断点, 调试代码
在调试器打开的情况下, 代码执行到 debugger 语句时会自动暂停, 可以查看变量的值、执行栈等信息
在调试器未打开的情况下, debugger 语句不会产生任何效果
1 2 3 4 5 6 7 8 9 10 11 function func ( ) { for (let i = 0 ; i < x; i++) { if (i === 3 ) { debugger } } } func ()
Error 对象 Error 对象是 JavaScript 中的一个内置对象, 表示某些特定的错误信息; 创建一个 Error 对象时, 可以省略 new 关键字
构造函数
描述
Error([message[, options]])
创建一个新的 Error 对象
RangeError([message[, options]])
表示数值或参数超出其有效范围
ReferenceError([message[, options]])
表示非法或不能识别的引用值
SyntaxError([message[, options]])
表示解析代码时发生的语法错误
TypeError([message[, options]])
表示变量或参数不是有效类型
URIError([message[, options]])
表示 encodeURI() 或 decodeURI() 函数的参数无效
实例属性
描述
error.message
错误消息, 即参数中的 message
error.name
错误名称, 即构造函数的名称
error.cause
错误原因, 通常是另一个错误 对于用户创建的错误, 该属性为 options.cause
options.cause 一般用于将捕获到的错误重新包装, 并附上一些信息
如果不传入 options 参数, 则 Error 对象没有 cause 属性
如果第二个参数不是对象, 也不会被添加到 cause 属性中 (用于对一些非标准的写法进行容错处理)
防抖和节流 防抖 / Debounce 单位时间内, 如果频繁触发同一事件, 只执行最后一次; 即触发后等待一段时间再执行, 如果在这段时间内再次触发, 则重新计时
搜索框输入时, 只有停止打字后才会触发搜索
用户输入密码时, 只有停止输入后才会验证密码
窗口大小改变时, 只有停止改变后才会触发重新计算布局
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 const box = document .querySelector ('#box' )function addOne ( ) { let i = 0 box.innerHTML = i++ } box.addEventListener ('mousemove' , _.debounce (addOne, 500 )) function debounce (fn, delay ) { let timer return function ( ) { timer && clearTimeout (timer) timer = setTimeout (fn, delay) } } box.addEventListener ('mousemove' , debounce (addOne, 500 ))
节流 / Throttle 单位时间内, 只触发一次事件; 即触发后立即执行, 然后在规定时间内不再触发
多次点赞时, 每隔一段时间才真正发送请求
上述防抖的案例, 改为节流则是每隔一段时间才会触发
针对高频事件, 如 mousemove、scroll、resize 等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 box.addEventListener ('mousemove' , _.throttle (addOne, 500 )) function throttle (fn, delay ) { let timer return function ( ) { if (!timer) { fn () timer = setTimeout (() => timer = 0 , delay) } } }
通过节流实现视频从上次播放位置开始
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Video</title > <style > video { width : 100% ; height : 100% ; } </style > <script src ="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js" > </script > </head > <body > <video src ="https://www.runoob.com/try/demo_source/movie.mp4" controls > </video > <script > const video = document .querySelector ('video' ) function saveTime ( ) { localStorage .setItem ('videoTime' , video.currentTime ) } video.addEventListener ('timeupdate' , _.throttle (saveTime, 1000 )) video.currentTime = localStorage .getItem ('videoTime' ) || 0 </script > </body > </html >
模块化 通过 export 和 import 关键字, 可以将代码分割成多个模块, 使代码更加清晰、易于维护
export
export ... 关键字用于在一个独立的 .js 文件中, 导出变量、函数、类等供外部使用
export default ... 关键字用于声明导出模块的默认成员, 一个模块只能有一个默认成员
as 关键字用于重命名导出的成员
1 2 3 4 5 6 7 8 9 10 11 12 13 export default const name = 'xiaoyezi' export const age = 18 export function sayHi ( ) { console .log (`Hello, I'm ${name} . I'm ${age} years old.` ) } export default name export { age as myAge, sayHi }
import
必须在 <script> 标签中声明 type="module" 才能使用 import
需要引入的模块无需在 HTML 文件中引入, 只需在 JavaScript 文件中使用 import 关键字即可
import ... from ... 关键字用于导入模块的变量、函数、类等
import 关键字后面的花括号 {} 用于导入模块的指定成员, 不加时导入模块的默认成员
as 关键字用于重命名导入的成员
* as xxx 用于导入模块的所有成员, xxx 是一个对象, 包含了模块的所有成员
1 <script type ="module" src ="module.js" > </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import xxx from './module.js' import xxx, { age, sayHi } from './module.js' import { name as myName, age as myAge, sayHi} from './module.js' import * as module from './module.js'
注意事项 1 2 3 4 export let myLetVar : number = 1 export const myConstVar : number = 2 export const myObj : object = {name : 'xiaoyezi' }
1 2 3 4 5 6 7 import { myLetVar, myConstVar, myObj } from './lib' myLetVar = 3 myConstVar = 4 console .log (myObj.name ) myObj.name = 'leaf' console .log (myObj.name )
动态导入 import() 函数用于动态导入模块, 返回一个 Promise 对象, 可以在 async 函数中使用 await 关键字
1 2 3 import * as mod from 'xxx' const mod = await import ('xxx' )
⭐网络 JavaScript 中可以通过 Fetch 和 XMLHttpRequest 发送网络请求的, Fetch 是 XMLHttpRequest 的替代品, 使用更加简单
HTTP HTTP 是 HyperText Transfer Protocol 的缩写, 即超文本传输协议 , 是当前互联网上应用最为广泛的一种网络传输协议, 最新版本为 HTTP/3
版本
年份
特点
HTTP/0.9
1991
只有一个命令 GET, 并且只能请求 HTML 格式的文档
HTTP/1.0
1996
增加了很多命令, 如 POST、HEAD、PUT、DELETE 等
HTTP/1.1
1999
增加持久连接、管道机制、增加缓存处理、增加 Host 等
HTTP/2
2015
所有数据以二进制传输, 多路复用, 头部压缩, 服务器推送等
HTTP/3
2018
基于 QUIC 协议, 解决 TCP 的队头阻塞问题
由于 HTTP/3 的 QUIC 协议是基于 UDP 的, 所以在国内不太受待见(虽然速度快, 但成本高且较难审查), 经常被限速甚至封锁(点名批评北师大校园网)
URL URL 是 Uniform Resource Locator 的缩写, 指的是统一资源定位符 , 用于定位互联网上的资源, 包括文件、目录、程序等; 即我们通常所说的网址
以 http://www.example.com:8080/path/to/file?query=string#hash 为例, URL 由以下几部分组成
内容
说明
示例
协议
资源的访问协议
http://、https://、ftp://
主机名
资源所在的域名 或IP
www.example.com、127.0.0.1
端口
资源所在的端口号, 不写时使用默认端口 默认 http 端口为 80, https 端口为 443
8080、443
路径
资源所在的路径
/path/to/file、/
查询字符串
资源的查询参数 , 用 ? 开头, 多个参数用 & 连接
query=string&name=leaf
哈希值
资源的锚点 , 用 # 开头
#hash
接口 API 是 Application Programming Interface 的缩写, 指的是应用程序接口 , 用于不同软件系统之间的通信; 在网页开发中, 通常指的是接口文档 , 用于说明如何与服务器进行通信; 可以在这里 获取一些练习用的接口文档及示例代码
encodeURI() 用于将中文等特殊字符转换为 URL 编码, 如 小叶子 转换后为 %E5%B0%8F%E5%8F%B6%E5%AD%90
decodeURI() 用于将 URL 编码转换为中文等特殊字符, 如 %E5%B0%8F%E5%8F%B6%E5%AD%90 转换后为 小叶子
URLSearchParams 对象会自动处理 URL 查询参数, 因此通过 URLSearchParams 或 URL 对象访问查询参数时, 不需要手动处理编码和解码
但在使用 fetch 发送 请求时, 需要手动处理编码
Token Token 是一种身份验证的方式, 常用于登录和权限验证; Token 通常是一个长字符串 , 由服务器生成并返回给客户端, 包含了用户的一些信息, 如用户名、权限等; Token 认证失败时, 服务器会返回 401 状态码
IP 地址 IP 是 Internet Protocol 的缩写, 指的是互联网协议 , 用于唯一标识互联网上的设备; IP 地址分为 IPv4 和 IPv6 两种, 前者是 32 位的二进制数, 后者是 128 位的二进制数
IPv4 地址由 4 个 8 位的二进制数组成, 每个数组用 0-255 的十进制数表示, 如 192.168.0.1
IPv6 地址由 8 个 16 位的二进制数组成, 每个数组用 0-ffff 的十六进制数表示, 如 2001:0db8:85a3:0000:0000:8a2e:0370:7334
IPv4 已经用尽, 现在一般是一个 IPv4 地址对应对应多个实际使用的用户(NAT 技术)
NAT 技术就是让多个私有地址共用一个公网地址, 通过端口号区分不同的用户
192.168.x.x、172.16.0.0-172.31.255.255、10.0.0.0-10.255.255.255 是私有地址, 只能在局域网内使用(家庭宽带一般是第一个、校园网一般是第二个)
127.0.0.1-127.255.255.254 是本地回环地址, 用于访问本机; 而 localhost 就是指向 127.0.0.1 的域名
每一个域名都对应一个 IP 地址, 而 DNS 服务器就是用来记录这个对应关系的
端口 一个主机往往不只有一个服务, 因此为了区分不同的服务, 为它们分配了不同的端口号, 范围是 0-65535
0-1023 是系统保留端口 , 一般用于系统服务
1024-49151 是注册端口 , 一般用于用户进程
49152-65535 是动态和/或私有端口 , 一般用于客户端程序
80 是 HTTP 协议的默认端口
443 是 HTTPS 协议的默认端口
21 是 FTP 协议的默认端口
22 是 SSH 协议的默认端口
请求 请求方法 访问 URL 时, 我们的请求方法 决定了服务器对资源的操作; 直接输入网址访问某个网站时, 浏览器默认使用的是 GET 方法
请求方法
说明
GET
请求指定的页面信息, 并返回实体主体, 数据一般附在 URL 后面
POST
向指定资源提交数据进行处理请求, 数据一般包含在请求体中
PUT
从客户端向服务器传送数据, 用其取代指定文档的内容, 数据包含在请求体中
DELETE
请求服务器删除指定的页面
HEAD
类似于 GET 请求, 只不过返回的响应中没有具体的内容, 用于获取报头
PATCH
对资源进行部分修改
实际开发中, GET 和 POST 常常也被用来删除和修改资源
GET 请求大小有限制, 一般不超过 2KB, POST 请求大小没有限制
请求报文 发送请求时, 我们需要按照 HTTP 或 HTTPS 协议的规范, 向服务器发送一个请求报文, 包括请求行、请求头和请求体
1 2 3 4 5 6 7 8 9 10 11 12 13 GET /path/to/file?query=string#hash HTTP/1.1 ----- 请求行 Host : www.example.com ----- 请求头Connection : keep-aliveAccept : text/htmlUser-Agent : Mozilla/5.0Content-Type : application/json ----- 这句表明请求体的数据类型Content-Length : 13Origin : http://127.0.0.1:5500Referer : http://127.0.0.1:5500/Accept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9 ----- 请求头结束 ----- 空行 {"name": "xiaoyezi","age": 18} ----- 请求体
调试时, 可以在浏览器的开发者工具中的 Network 选项卡中查看请求报文和响应报文
响应 响应报文 服务器接收到请求报文后, 会返回一个响应报文, 包括状态行、响应头和响应体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 HTTP/1.1 400 Bad Request ----- 状态行Server : nginx ----- 响应头Date : Fri, 01 Jan 2021 00:00:00 GMTContent-Type : application/json ----- 这句表明响应体的数据类型Content-Length : 13Connection : keep-aliveVary : OriginAccess-Control-Allow-Origin : *Access-Control-Allow-Credentials : truex-frame-options : SAMEORIGINx-xss-protection : 1; mode=blockx-content-type-options : nosniffx-download-options : noopenx-readtime : 16 ----- 响应头结束 ----- 空行 {"error": "Bad Request"} ----- 响应体
HTTP 状态码 HTTP 协议的响应报文中, 状态行的第二个字段是状态码 , 用于表示服务器对请求的处理结果
状态码
说明
状态码
说明
1xx
信息性状态码
2xx
成功状态码
3xx
重定向状态码
4xx
客户端错误状态码
5xx
服务器错误状态码
100
继续
200
成功
301
永久重定向
302
临时重定向
304
未修改
400
错误请求
401
未授权
403
禁止
404
未找到
500
服务器错误
503
服务不可用
XMLHttpRequest XMLHttpRequest 对象, 简称 XHR, 用于在后台与服务器交换数据, 可以在不重新加载页面的情况下更新页面
由于 Fetch 没有上传时的进度事件, 所以在上传载时, 如果需要跟踪进度, 可以使用 XMLHttpRequest 对象
1 2 3 4 5 6 7 8 9 10 11 const xhr = new XMLHttpRequest ()xhr.open ('PUT' , signedUrl) xhr.upload .onprogress = event => { flushSync (() => setProgress (+(10 + 89 * event.loaded / event.total ))) } xhr.send (blob) await new Promise ((resolve, reject ) => { xhr.onload = () => resolve (null ) xhr.onerror = error => reject (error) })
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://xxx.xxx/xxx' ) xhr.addEventListener ('loadend' , () => { if (xhr.status === 200 ) { console .log (xhr.response ) } else { console .log ('请求失败' ) } }) xhr.send ()
属性或方法
说明
readyState
请求的状态, 0 未初始化, 1 已打开, 2 已发送, 3 已接收, 4 完成
status
响应的状态码, 如 200 成功, 404 未找到, 500 服务器错误
response
响应的数据
open(method, url, async, user, password)
初始化请求, 后三个参数可省略
send(body)
发送请求, body 为请求体, 仅在 POST 和 PUT 中使用
setRequestHeader(name, value)
设置请求头
getResponseHeader(name)
获取响应头
getAllResponseHeaders()
获取所有响应头
abort()
取消请求
addEventListener(event, callback)
添加事件监听
XHR 事件
事件
说明
loadstart
请求开始
progress
请求过程中
abort
请求被取消
error
请求失败
load
请求成功
loadend
请求完成
timeout
请求超时
readystatechange
请求状态改变
发送 GET 请求获取省份列表
1 2 3 4 5 6 7 8 9 10 11 const xhr = new XMLHttpRequest ()xhr.open ('GET' , 'http://hmajax.itheima.net/api/province' ) xhr.addEventListener ('load' , () => console .log (JSON .parse (xhr.response ))) xhr.send ()
发送 POST 请求登陆账号
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Login</title > <style > input { display : block; margin : 10px 0 ; } </style > </head > <body > <input type ="text" id ="username" placeholder ="用户名" > <input type ="password" id ="password" placeholder ="密码" > <button id ="login" > 登陆</button > <script > const username = document .querySelector ('#username' ) const password = document .querySelector ('#password' ) const login = document .querySelector ('#login' ) login.onclick = function ( ) { const xhr = new XMLHttpRequest () xhr.open ('POST' , 'http://hmajax.itheima.net/api/login' ) xhr.setRequestHeader ('Content-Type' , 'application/json' ) xhr.addEventListener ('loadend' , () => { if (xhr.status === 200 ) { alert (JSON .parse (xhr.response ).message ) username.disabled = true password.disabled = true login.disabled = true } else { alert (JSON .parse (xhr.response ).message ) username.value = '' password.value = '' } }) xhr.send (JSON .stringify ({ username : username.value , password : password.value })) } </script > </body > </html >
AJAX 和 axios AJAX 是 Asynchronous JavaScript and XML 的缩写, 指的是通过 JavaScript 发送异步请求, 获取数据并更新页面, 而不用刷新整个页面; 本质上还是通过 XMLHttpRequest 对象发送请求
axios 是一个基于 Promise 的 HTTP 客户端, 可以实现 AJAX 请求, 可用于浏览器和 Node.js, 支持 Promise API, 可以拦截请求和响应, 转换请求和响应数据, 取消请求, 自动转换 JSON 数据, 客户端支持防止 CSRF 等
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 axios ({ method : 'get' , url : 'https://xxx.xxx/xxx' , params : { name : 'xiaoyezi' , age : 18 }, data : { name : 'xiaoyezi' , age : 18 } }).then (res => { console .log (res) console .log (res.data ) }).catch (err => { console .log (err) console .log (err.response .data ) })
发送 GET 请求获取省份列表
1 2 3 4 5 6 7 8 9 axios ({ method : 'get' , url : 'http://hmajax.itheima.net/api/province' }).then (res => { res.data .list .forEach (item => console .log (item.name )) }).catch (err => { console .log (err) })
发送 GET 请求获取城市列表
1 2 3 4 5 6 7 8 9 10 11 12 axios ({ method : 'get' , url : 'http://hmajax.itheima.net/api/city' , params : { pname : '广东省' } }).then (res => { res.data .list .forEach (item => console .log (item.name )) }).catch (err => { console .log (err) })
发送 POST 请求登陆账号
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Login</title > <style > input { display : block; margin : 10px 0 ; } </style > <script src ="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js" > </script > </head > <body > <input type ="text" id ="username" placeholder ="用户名" > <input type ="password" id ="password" placeholder ="密码" > <button id ="login" > 登陆</button > <script > const username = document .querySelector ('#username' ) const password = document .querySelector ('#password' ) const login = document .querySelector ('#login' ) login.onclick = function ( ) { if (!username.value || !password.value ) { alert ('用户名和密码不能为空' ) return } else if (username.value .length < 6 ) { alert ('用户名长度不能小于 6 位' ) return } else if (password.value .length < 6 ) { alert ('密码长度不能小于 6 位' ) return } else { axios ({ method : 'post' , url : 'http://hmajax.itheima.net/api/login' , data : { username : username.value , password : password.value } }).then (res => { alert (res.data .message ) username.disabled = true password.disabled = true login.disabled = true }).catch (err => { if (err.response .status === 0 ) { alert ('网络错误' ) } else { alert (err.response .data .message ) } username.value = '' password.value = '' }) } } </script > </body > </html >
发送 PUT 请求修改图书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const form = document .querySelector ('form' )const data = serialize (form, {hash : true , empty : true })axios ({ method : 'put' , url : `http://hmajax.itheima.net/api/book/${data.id} ` , data : { name : data.name , author : data.author , publisher : data.publisher } }).then (res => { render () }).catch (err => { alert (err.response .data .message ) })
发送 DELETE 请求删除图书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 document .querySelector ('.list' ).onclick = e => { if (e.target .tagName === 'BUTTON' && e.target .classList .contains ('delete' )) { const id = e.target .dataset .id axios ({ method : 'delete' , url : `http://hmajax.itheima.net/api/book/${id} ` }).then (res => { render () }).catch (err => { alert (err.response .data .message ) }) } }
FormData 对象用于将表单数据发送到服务器, 可以用于上传文件 , 也可以用于发送 XMLHttpRequest 对象; 使用时, 请求头中的 Content-Type 应为 multipart/form-data(axios 会自动设置)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const input = document .querySelector ('input[type="file"]' )input.addEventListener ('change' , e => { const formData = new FormData () formData.append ('img' , e.target .files [0 ]) axios ({ method : 'post' , url : 'http://hmajax.itheima.net/api/uploadimg' , data : formData }).then (res => { window .open (res.data .url ) }).catch (err => { alert (err.response .data .message ) }) })
URL 对象 URL 对象用于处理 URL 地址; 实例化时可以传入一个 URL 地址, 也可以传入一个基地址和一个相对地址, 如 new URL(https://${req.headers.origin}${req.url}) (Node.js 中)
属性或方法
说明
new URL()
实例化 URL 对象
url.hash
URL 的哈希值, 如 #hash
url.host
URL 的主机名和端口号 (如果有), 如 www.example.com:8080
url.hostname
URL 的主机名, 如 www.example.com
url.hrefurl.toString()url.toJSON()
URL 的完整地址
url.origin
只读, URL 的协议、主机名和端口号, 如 http://www.example.com:8080
url.pathname
URL 的路径, 如 /path/to/file
url.port
URL 的端口号, 如 8080 (字符串)
url.protocol
URL 的协议, 如 https:
url.search
URL 的查询参数, 如 ?query=string
url.searchParams
只读, URLSearchParams 对象
URL.createObjectURL(blob)
创建一个 Blob 对象的 URL (用于图片、音频等文件)
URL.revokeObjectURL(url)
释放一个 Blob 对象的 URL
URL(xxx).searchParams 和 URLSearchParams(xxx) 等效, 但传入的参数不同
URLSearchParams 对象 URLSearchParams 对象用于处理 URL 查询参数, 可以用于设置查询参数; 实例化时, 可以传入一个对象, 也可以传入一个查询字符串 (不是传入完整 URL )
属性或方法
说明
get(‘xxx’)
获取指定名称的查询参数
set(‘xxx’, ‘xxx’)
设置指定名称的查询参数
append(‘xxx’, ‘xxx’)
添加指定名称的查询参数
delete(‘xxx’)
删除指定名称的查询参数
toString()
返回查询参数的字符串形式, 如 name=xiaoyezi&age=18
forEach()
遍历查询参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const getParams = new URLSearchParams (location.search )console .log (getParams.get ('name' )) getParams.delete ('name' ) const setParams = new URLSearchParams ({ name : 'xiaoyezi' , age : 18 }) setParams.append ('gender' , 'male' ) const xhr = new XMLHttpRequest ()xhr.open ('GET' , `https://xxx.xxx/xxx?${setParams.toString()} ` ) xhr.send ()
Promise Promise 对象用于表示一个异步操作的最终完成或失败, 以及其结果值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const promise = new Promise ((resolve, reject ) => { if () { resolve (xxx) } else { reject (xxx) } }).then (res => { console .log (res) }).catch (err => { console .log (err) })
一个 Promise 必然出于以下三种状态之一
待定 Pending: 既不是成功也不是失败状态, 表示异步操作尚未完成, 是 Promise 对象的初始状态
已兑现 Fulfilled: 表示异步操作成功完成, 通过 resolve() 方法将状态从 Pending 改为 Fulfilled
已拒绝 Rejected: 表示异步操作失败, 通过 reject() 方法将状态从 Pending 改为 Rejected
兑现或拒绝后, 状态就不会再改变, 即已敲定 Settled
then() 方法接收两个参数, 第一个是成功时的回调, 第二个是失败时的回调 (通常不写, 而是使用 catch() 方法)
then() 方法返回一个新的 Promise 对象, 可以进行链式调用
catch() 方法用于捕获错误, 与 then() 方法的第二个参数等效
finally() 方法用于无论成功或失败都会执行的回调, 通常用于清理工作, 放在链式调用的最后
通过 Promise 对象获取省份列表
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 const promise = new Promise ((resolve, reject ) => { const xhr = new XMLHttpRequest () xhr.open ('GET' , 'http://hmajax.itheima.net/api/province' ) xhr.addEventListener ('load' , () => { if (xhr.status === 200 ) { resolve (JSON .parse (xhr.response )) } else { reject (new Error (xhr.response )) } }) xhr.send () }) promise.then (res => { res.list .forEach (item => console .log (item)) }).catch (err => { alert (err.message ) })
封装一个简易的 axios 函数
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 41 42 43 function axios (config = { method: 'get' , url: '' , params: {}, data: {} } ) { return new Promise ((resolve, reject ) => { const xhr = new XMLHttpRequest () config.method .toLowerCase () === 'get' && /?/ .test (config.url ) ? config.url += '?' + new URLSearchParams (config.params ) : null xhr.open (config.method , config.url ) xhr.setRequestHeader ('Content-Type' , 'application/json' ) xhr.addEventListener ('loadend' , () => { if (xhr.status >= 200 && xhr.status < 300 ) { resolve (JSON .parse (xhr.response )) } else { reject (new Error (xhr.response )) } }) config.method .toLowerCase () === 'get' ? xhr.send () : xhr.send (JSON .stringify (config.data )) }) } axios ({ method : 'get' , url : 'http://hmajax.itheima.net/api/province' }).then (res => { res.list .forEach (item => console .log (item)) }).catch (err => { alert (err.message ) })
调试时, 可以在浏览器的开发者工具中的 Network → Online 选项卡中模拟低网速和断网, 观察代码的执行情况
链式调用 回调函数地狱
回调函数地狱
回调地狱 指的是多个回调函数嵌套调用, 导致代码难以阅读和维护
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 axios ({ url : 'http://hmajax.itheima.net/api/province' }).then (res => { document .querySelector ('.province' ).innerHTML = res.list [0 ].name axios ({ url : `http://hmajax.itheima.net/api/city?pname=${res.list[0 ].name} ` }).then (res => { document .querySelector ('.city' ).innerHTML = res.list [0 ].name axios ({ url : `http://hmajax.itheima.net/api/area?cname=${res.list[0 ].name} ` }).then (res => { document .querySelector ('.area' ).innerHTML = res.list [0 ].name }).catch (err => { alert (err.message ) }) }).catch (err => { alert (err.message ) }) }).catch (err => { alert (err.message ) })
通过 Promise 对象的链式调用, 可以解决回调地狱的问题, 使代码更加清晰和易读
1 2 3 4 5 6 7 8 9 10 11 12 axios ({ url : 'http://hmajax.itheima.net/api/province' }).then (res => { document .querySelector ('.province' ).innerHTML = res.list [0 ].name return axios ({ url : `http://hmajax.itheima.net/api/city?pname=${res.list[0 ].name} ` }) }).then (res => { document .querySelector ('.city' ).innerHTML = res.list [0 ].name return axios ({ url : `http://hmajax.itheima.net/api/area?cname=${res.list[0 ].name} ` }) }).then (res => { document .querySelector ('.area' ).innerHTML = res.list [0 ].name }).catch (err => { alert (err.message ) })
如需共享某个数据, 可以在 axios 外声明一个变量, 或利用闭包
静态方法
Promise.all() 方法接收一个 Promise 对象数组, 返回一个新的 Promise 对象, 只有当所有 Promise 对象都成功时, 才会成功, 返回结果为一个数组, 顺序与传入的数组一致
Promise.allSettled() 类似于 Promise.all(), 当所有 Promise 对象都敲定时, 返回结果为一个数组, 包含每个 Promise 对象的结果
Promise.any() 方法接收一个 Promise 对象数组, 返回一个新的 Promise 对象, 只要有一个 Promise 对象成功, 就会成功, 并返回第一个成功的结果; 如果所有 Promise 对象都失败, 则返回错误数组
Promise.race() 类似于 Promise.all(), 但只要有一个 Promise 对象敲定 就会返回结果, 无论成功或失败
Promise.withResolvers() 见下面的示例 (Node.js 暂不支持, Deno 1.38 后支持)
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 async function getPrice ( ) { const choice = await promptForDishChoice () const prices = await fetchPrices () return prices[choice] } async function getPrice ( ) { const [choice, prices] = await Promise .all ([ promptForDishChoice (), fetchPrices (), ]) return prices[choice] } let res, rejconst promise = new Promise ((resolve, reject ) => { res = resolve rej = reject }) const { promise, resolve, reject } = Promise .withResolvers ()
async await async 和 await 是 ES2017 中新增的关键字, 用于简化 Promise 对象的使用和链式调用
关键字
说明
async
声明一个函数为异步函数 , 返回一个 Promise 对象
await
等待 Promise 对象的状态改变; 若成功, 返回 Promise 对象的结果, 函数继续执行 若失败, 抛出错误, 函数终止执行; 只能在 async 函数中使用
ECMAScript 2022 中引入了 顶层 await, 用于在模块的顶层使用 await 关键字; 但不支持 CommonJS 规范的 Node.js ; 如果要使用顶层 await, 需要在 package.json(<script>) 中设置 type 字段为 module, 并用 import 模块化规范
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 async function getData ( ) { try { const res = await axios ({ url : 'http://hmajax.itheima.net/api/province' }) document .querySelector ('.province' ).innerHTML = res.list [0 ].name document .querySelector ('.city' ).innerHTML = (await axios ({ url : `http://hmajax.itheima.net/api/city?pname=${res1.list[0 ].name} ` })).list [0 ].name document .querySelector ('.area' ).innerHTML = await axios ({ url : `http://hmajax.itheima.net/api/area?cname=${res2.list[0 ].name} ` }).then (res => res.list [0 ].name ).catch (err => err.message ) } catch (err) { alert (err.message ) } } getData () .then (() => console .log ('执行完成' )) .catch (() => console .log ('执行失败' )) for await (const item of asyncIterable) { console .log (item) }
记得把 await 和异步操作用 () 包裹起来, 否则会报错
错误捕获 一个系统或用户用 throw 语句抛出的错误, 只会被最近的 catch 语句捕获; 如果没有 catch 语句, 错误会一直向上冒泡, 直到被浏览器捕获
1 2 3 4 5 6 7 8 9 10 11 (async () => { try { await fetch ('url' ) .catch (() => fetch ('url' )) .catch (() => fetch ('url' )) .then (res => console .log (res)) } catch (err) { console .log ('三次请求全部失败' ) } })()
fetch fetch 是 JavaScript 中的一个全局函数, 用于发送网络请求, 替代了 XMLHttpRequest 对象和 AJAX 技术, 返回一个 Promise 对象
1 fetch ('url' [, req]).then (res => {}).catch (err => {})
相关对象
说明
Response
fetch 成功后返回的响应对象, 用于获取响应结果
Request
用于设置请求的相关信息
Headers
用于设置请求头和获取响应头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 fetch ('url' , { method : 'get' , headers : { 'Content-Type' : 'application/json' }, body : JSON .stringify ({ name : 'xiaoyezi' , age : 18 }) }).then (res => { return res.json () }).then (data => { console .log (data) }).catch (err => { console .log (err) })
发送 GET 请求获取省份列表
1 2 3 4 5 6 7 8 9 10 11 fetch ('http://hmajax.itheima.net/api/province' ).then (res => { return res.json () }).then (data => { data.list .forEach (item => console .log (item)) }).catch (err => { alert (err.message ) })
发送 POST 请求登陆账号
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 41 42 43 <input type ="text" id ="username" placeholder ="用户名" > <input type ="password" id ="password" placeholder ="密码" > <button id ="login" > 登陆</button > <script > const username = document .querySelector ('#username' ) const password = document .querySelector ('#password' ) const login = document .querySelector ('#login' ) login.onclick = function ( ) { fetch ('http://hmajax.itheima.net/api/login' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON .stringify ({ username : username.value , password : password.value }) }).then (res => { return res.json () }).then (data => { alert (data.message ) username.disabled = true password.disabled = true login.disabled = true }).catch (err => { if (err.status === 0 ) { alert ('网络错误' ) } else { alert (err.message ) } username.value = '' password.value = '' }) } </script >
用 async 和 await 实现上述功能
获取省份列表
1 2 3 4 5 6 7 8 9 10 11 12 13 async function getProvince ( ) { try { const res = await fetch ('http://hmajax.itheima.net/api/province' ) const data = await res.json () data.list .forEach (item => console .log (item)) } catch (err) { alert (err.message ) } } getProvince ()
登陆账号
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 41 42 43 44 <input type ="text" id ="username" placeholder ="用户名" > <input type ="password" id ="password" placeholder ="密码" > <button id ="login" > 登陆</button > <script > const username = document .querySelector ('#username' ) const password = document .querySelector ('#password' ) const login = document .querySelector ('#login' ) login.onclick = async function ( ) { try { const res = await fetch ('http://hmajax.itheima.net/api/login' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON .stringify ({ username : username.value , password : password.value }) }) const data = await res.json () alert (data.message ) username.disabled = true password.disabled = true login.disabled = true } catch (err) { if (err.status === 0 ) { alert ('网络错误' ) } else { alert (err.message ) } username.value = '' password.value = '' } } </script >
Response 对象
属性或方法
说明
new Response([body, options])
创建一个新的 Response 对象body 为 Blob、FormData、string、ReadableStream 等options 含 status、statusText、headers 对象等属性
res.headers
响应的头部信息, Headers 对象
res.ok
响应是否成功, 布尔值
res.status
响应的状态码, 如 200、404
res.statusText
响应的状态文本, 如 OK、Not Found
res.type
响应的类型, 如 basic、cors
res.url
响应的 URL
res.body
响应体 getter(可读流对象)
res.clone()
克隆响应对象(用于多次处理响应体)
res.arrayBuffer()
promise, 返回响应体的 ArrayBuffer 对象
res.blob()
promise, 返回响应体的 Blob 对象
res.formData()
promise, 返回响应体的 FormData 对象
res.json()
promise, 返回响应体的 JSON 对象
res.text()
promise, 返回响应体的文本
属性都是只读的, 且响应体只能被读取一次, 再次读取会报 TypeError: body stream is locked 错误
Request 对象
属性或方法
说明
new Request('url')
创建一个新的 Request 对象, 一般不直接使用
req.body
请求体(可读流对象)
req.headers
请求头部信息, Headers 对象
req.method
请求方法, 如 GET、POST
req.mode
请求模式, 如 cors、no-cors、same-origin
req.referrer
请求 referrer (没有时, 为 '')
req.url
请求的 URL (node 中可能不是完整 url, 详见相关内容)
req.arrayBuffer()
promise, 返回请求体的 ArrayBuffer 对象
req.blob()
promise, 返回请求体的 Blob 对象
req.formData()
promise, 返回请求体的 FormData 对象
req.json()
promise, 返回请求体的 JSON 对象
req.text()
promise, 返回请求体的文本
req.clone()
克隆请求对象(用于多次处理请求体)
属性都是只读的, 请求体只能被读取一次, 再次读取会报 TypeError: body stream is locked 错误
属性或方法
说明
headers.append(key, value)
添加一个头部信息(如果已存在, 则添加到末尾)
headers.delete(key)
删除一个头部信息
headers.entries()
返回一个迭代器, 包含所有头部信息的键值对
headers.get(key)
获取一个头部信息
headers.has(key)
判断是否存在某个头部信息
headers.keys()
返回一个迭代器, 包含所有头部信息的键
headers.set(key, value)
设置(更新或添加)一个头部信息
headers.values()
返回一个迭代器, 包含所有头部信息的值
Stream API fetch 函数返回的 Response 对象的 body 属性是一个可读流对象, 用于读取响应体的数据, 而无需等待整个响应体下载完毕
可读/可写流对象不能被直接读取, 需要通过 reader 对象读取, 通过 writer 对象写入
可读流对象
对象
说明
new ReadableStream([underlyingSource, [strategy]])
创建一个可读流对象
rs.locked
只读, 表示流是否被锁定 (即正被使用)
rs.cancel(reason)
取消流, 返回一个 promise 对象
rs.tee()
返回一个包含两个流的数组, 两个流都是原流的副本
rs.getReader()
返回一个 ReadableStreamDefaultReader 对象, 并锁定流
ReadableStreamDefaultReader
对象
说明
reader.closed
只读, Promise 对象, 流关闭时兑现, 错误时拒绝
reader.cancel([reason])
取消流, 返回 Promise 对象, 流取消时兑现
reader.read()
返回一个 Promise 对象, 读取流中的数据, 兑现值为 {done, value}
可写流对象
对象
说明
new WritableStream([underlyingSink, [strategy]])
创建一个可写流对象
ws.locked
只读, 表示流是否被锁定 (即正被使用)
ws.abort(reason)
中止流, 返回一个 promise 对象
ws.close()
关闭流, 返回一个 promise 对象
ws.getWriter()
返回一个 WritableStreamDefaultWriter 对象, 并锁定流
WritableStreamDefaultWriter
对象
说明
writer.closed
只读, Promise 对象, 流关闭时兑现, 错误时拒绝
writer.abort([reason])
中止流, 返回 Promise 对象, 流中止时兑现
writer.close()
关闭流, 返回 Promise 对象, 流关闭时兑现
writer.write(chunk)
写入数据, 返回 Promise 对象, 写入成功时兑现
⭐高级 类 ES6 中引入了 class 关键字, 用于定义类, 可以便捷地替代原本的构造函数 、原型对象 、原型继承 等
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 class Person { constructor (name, age ) { this .name = name this .age = age } say ( ) { console .log (this .name , this .age ) } static makePerson (name, age ) { return new Person (name, age) } message = 'hello' static message = 'world' } const personA = new Person ('xiaoyezi' , 18 )const personB = Person .makePerson ('leaf' , 18 )personA.say () personB.say ()
如果用老方法实现上面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Person (name, age ) { this .name = name this .age = age } Person .prototype .say = function ( ) { console .log (this .name , this .age ) } Person .makePerson = function (name, age ) { return new Person (name, age) } const personA = new Person ('xiaoyezi' , 18 )const personB = Person .makePerson ('leaf' , 18 )personA.say () personB.say ()
继承 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 class Student extends Person { constructor (name, age, grade ) { super (name, age) this .grade = grade } say ( ) { super .say () console .log (`And the grade is ${this .grade} ` ) } static makeStudent (name, age, grade ) { const person = super .makePerson (name, age) return new Student (person.name , person.age , grade) } } const studentA = new Student ('xiaoyezi' , 18 , 3 )const studentB = Student .makeStudent ('leaf' , 18 , 3 )studentA.say () studentB.say ()
如果用老方法实现上面的代码
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 function Student (name, age, grade ) { Person .call (this , name, age) this .grade = grade } Student .prototype = Object .create (Person .prototype )Student .prototype .say = function ( ) { Person .prototype .say .call (this ) console .log (`And the grade is ${this .grade} ` ) } Student .makeStudent = function (name, age, grade ) { const person = Person .makePerson (name, age) return new Student (person.name , person.age , grade) } const studentA = new Student ('xiaoyezi' , 18 , 3 )const studentB = Student .makeStudent ('leaf' , 18 , 3 )studentA.say () studentB.say ()
私有字段 ECMAScript 2022 中引入了 # 关键字, 用于定义类的私有属性和方法, 只能在类的内部访问
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 class Person { #name constructor (name ) { this .#name = name } getName ( ) { return this .#name } setName (name ) { this .#name = name } } const person = new Person ('xiaoyezi' )console .log (person.getName ()) console .log (person.name ) console .log (person.#name) person.name = 'leaf' console .log (person.getName ()) person.setName ('leaf' ) console .log (person.getName ())
静态属性和静态方法也可以设置为私有字段
in 操作符 in 操作符用于检测对象是否具有某个属性 (包括私有字段), 返回布尔值
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 Person { constructor (name ) { this .#name = name } static check (obj ) { return #name in obj } } class Person { constructor (name ) { this .#name = name } static check (obj ) { try { obj.#name return true } catch (err) { if (err instanceof TypeError ) { return false } throw err } } }
二进制数据
Blob : Binary Large Object, 二进制大对象, 表示一个不可变、原始数据的类文件对象
File : 继承自 Blob, 表示一个文件对象(<input type="file">)
FileReader : 用于读取 Blob 或 File 对象的内容
ArrayBuffer : 表示一个通用的、固定长度的二进制数据缓冲区, 不能直接操作
TypedArray : ArrayBuffer 的视图, 用于操作 ArrayBuffer 对象; 最常用的是 Uint8Array
DataView : ArrayBuffer 的视图, 用于操作 ArrayBuffer 对象; 相比于 TypedArray, DataView 可以自定义字节序
在 Node.js 中, 二进制数据的处理更多的是通过 Buffer 对象 (继承自 Uint8Array). 它提供了如 .from(), .toString('base64') 等便捷方法
相互转换
From →To ↓
ArrayBuffer
TypedArray
Buffer
Blob
ArrayBuffer
-
x.buffer
x.buffer
await x.arrayBuffer()
TypedArray
new TypedArray(x)
-
new TypedArray(x)
new TypedArray(await x.arrayBuffer())
Buffer
Buffer.from(x)
Buffer.from(x)
-
Buffer.from(await x.arrayBuffer())
Blob
new Blob([x], { type: 'xxx' })
new Blob([x.buffer], { type: 'xxx' })
new Blob([x], { type: 'xxx' })
-
String
new TextEncoder().encode(x)
new TextEncoder().encode(x)
x.toString('utf-8')
await x.text()
Array
Array.from(...)
Array.from(...)
Array.from(...)
Array.from(...)
详见此处
Blob Blob 是 Binary Large Object 的缩写, 表示一个不可变、原始数据的类文件对象, 用于存储二进制数据
属性或方法
作用
new Blob(array[, options])
创建一个 Blob 对象array: 可迭代对象, 如数组、字符串、TypedArray 等option.type: MIME 类型
blob.size
返回 Blob 对象的字节长度, 只读
blob.type
返回 Blob 对象的 MIME 类型, 只读
blob.arrayBuffer()
promise, 返回 Blob 对象的 ArrayBuffer 对象
blob.slice([start[, end[, type]]])
返回一个新的 Blob 对象, 包含原 Blob 对象的部分数据 不传参相当于复制、不包含 end、type 为 MIME 类型
blob.stream()
返回一个 ReadableStream 对象
blob.text()
promise, 返回 Blob 对象的文本
URL.createObjectURL(blob)
创建一个 URL, 用于访问 Blob 对象
URL.revokeObjectURL(url)
释放一个 URL
ObjectURL 的生命周期是在 document 被卸载时结束, 但强烈建议在不需要时手动释放
使用示例 1 2 3 4 5 6 7 8 9 10 11 12 const encodedText = encodeURI (text)const res = await fetch (`${server} /?prompt=${encodedText} ` )const blob = await res.blob ()const imgUrl = URL .createObjectURL (blob)const img = document .createElement ('img' )img.src = imgUrl
FileReader FileReader 对象用于读取 Blob 或 File 对象的内容
方法
作用
new FileReader()
创建一个 FileReader 对象
reader.readAsArrayBuffer(blob/file)
promise, 返回 ArrayBuffer 对象
reader.readAsBinaryString(blob/file)
promise, 返回二进制字符串
reader.readAsDataURL(blob/file)
promise, 返回 data: 格式的 URL (base64 编码)
使用前确认所在环境是否支持 FileReader 对象
Uint8Array Uint8Array 是 TypedArray 中的一种, 用于操作 ArrayBuffer 对象, 表示一个 8 位无符号整数的数组
属性或方法
作用
new Uint8Array(length)
创建一个 Uint8Array 数组, 长度为 length, 用 0 填充
new Uint8Array(typedArray/object/buffer)
创建一个 Uint8Array 数组
Uint8Array.from(arrayLike)
从类数组对象或可迭代对象中创建一个 Uint8Array 数组 包括字符串、数组、TypedArray 等
Uint8Array.of(...items)
从参数中创建一个 Uint8Array 数组
u8a.buffer
返回 Uint8Array 对象的 ArrayBuffer 对象, 只读
u8a.byteLength
返回 Uint8Array 对象的字节长度, 只读
u8a.length
返回 Uint8Array 对象的长度, 只读
u8a.toString()
返回 Uint8Array 对象的字符串表示
由于 Uint8Array 是 TypedArray 的一种, 所有也可以用 TypedArray 的方法
🚧 TypedArray Set Set 是 ES6 中新增的一种数据结构, 用于存储唯一 的值, 类似于数组, 但值是唯一的
方法
作用
new Set([arr])
创建一个 Set 集合, 重复值会被去除
set.add(value)
向 Set 集合中添加一个值, 重复值会被忽略
set.delete(value)
删除 Set 集合中的一个值
set.has(value)
判断 Set 集合中是否有某个值
set.clear()
清空 Set 集合
set.size
返回 Set 集合的大小
set.keys()set.values()
返回一个迭代器, 包含 Set 集合的值
set.entries()
返回一个迭代器, 包含 [value, value]
set.forEach(callback)
遍历 Set 集合, 回调函数接受三个参数: value、key (同 value)、set (当前 Set 集合)
1 2 3 4 5 6 7 8 9 10 11 const set = new Set ([1 , 2 , 3 , 4 , 5 , 1 , 2 , 3 ])console .log (set) set.add (6 ) set.delete (6 ) console .log (set.has (5 )) set.clear ()
可以使用 Array.from 将迭代器转换为数组, 如 Array.from(new Set(arr)) 可以去除数组中的重复值, 并返回一个新数组
Map Map 是 ES6 中新增的一种数据结构, 用于存储键值对 , 类似于对象, 键是唯一的; 但 Map 的键可以是任意类型 , 而对象的键只能是字符串或 Symbol 类型
方法
作用
new Map()
创建一个 Map 集合
map.set(key, value)
向 Map 集合中添加一个键值对
map.get(key)
获取 Map 集合中的一个值
map.delete(key)
删除 Map 集合中的一个键值对
map.has(key)
判断 Map 集合中是否有某个键
map.clear()
清空 Map 集合
map.size
返回 Map 集合的大小
map.keys()
返回一个迭代器, 包含 Map 集合的键
map.values()
返回一个迭代器, 包含 Map 集合的值
map.entries()
返回一个迭代器, 包含 [key, value]
map.forEach(callback)
遍历 Map 集合, 回调函数接受三个参数: value、key、map (当前 Map 集合)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const map = new Map ([ ['name' , 'xiaoyezi' ], ['age' , 18 ], [ { key : 'key' }, { value : 'value' } ] ]) console .log (map) map.set ('height' , 180 ) console .log (map.get ('name' )) map.delete ('height' ) console .log (map.has ('age' )) map.clear ()
WeakSet & WeakMap Set 和 Map 一般用于存储强引用 的值, 即使值不再被引用, 也不会被垃圾回收; 而 WeakSet 和 WeakMap 用于存储弱引用 的值, 当值不再被引用时, 会被垃圾回收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const ws = new WeakSet ()const obj = {}ws.add (obj) console .log (ws.has (obj)) ws.delete (obj) console .log (ws.has (obj)) const wm = new WeakMap ()const key = {}const value = {}wm.set (key, value) console .log (wm.get (key)) wm.delete (key) console .log (wm.get (key))
Symbol Symbol 是 ES6 中新增的一种基本数据类型 , 用于表示独一无二的值, 可以用于对象的属性名
Symbol 函数接受一个字符串 作为参数, 用于描述 Symbol 的名称, 但是 Symbol 的名称只是一个描述, 不会影响 Symbol 的唯一性
Symbol 的值是唯一的, 即使描述相同, 值也不同
Symbol 的值可以作为对象的属性名, 用于解决对象属性名冲突的问题
Symbol 的值不会被 for...in、Object.keys、JSON.stringify 等遍历方法遍历到, 但可以使用 Object.getOwnPropertySymbols 方法获取
Symbol 的值可以作为对象的私有属性, 用于隐藏属性 (见下方示例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const symbolA = Symbol ('description' )const symbolB = Symbol ('description' )console .log (symbolA) console .log (symbolB) console .log (symbolA === symbolB) const obj = { [symbolA]: 'valueOfA' , [symbolB]: 'valueOfB' } console .log (obj[symbolA]) console .log (obj[symbolB]) for (const key in obj) console .log (key) console .log (Object .keys (obj)) console .log (Object .values (obj)) console .log (JSON .stringify (obj)) console .log (Object .getOwnPropertySymbols (obj))
内置 Symbol 值 ES6 中提供了一些内置的 Symbol 值, 用于表示对象的内部方法, 如 .toString
如果直接以字符串定义这些属性, 可能会与其他属性冲突, 也可能会被覆盖
Symbol.iterator: 用于表示对象的迭代器方法
Symbol.hasInstance: 用于表示对象的 instanceof 方法
Symbol.toPrimitive: 用于表示对象的转换方法
Symbol.toStringTag: 用于表示对象的 toString 方法
Symbol.isConcatSpreadable: 用于表示对象的 concat 方法
Symbol.species: 用于表示对象的构造函数
1 2 3 4 const obj = {}console .log (obj.toString ()) obj[Symbol .toStringTag ] = 'xxx' console .log (obj.toString ())
BigInt BigInt 是一种内置对象, 它提供了一种方法来表示大于 2^53 - 1 的整数, 这原本是 Javascript 中可以用 Number 表示的最大数字; BigInt 可以表示任意大的整数
BigInt 不能用于 Math 对象的方法
BigInt 不能和 Number 直接运算, 需要先转换为同一类型(但要小心 BigInt 转换为 Number 时可能会丢失精度)
BigInt 不支持单目 + 运算符(即正号)
运算结果的小数部分会被舍弃
BigInt('1') == 1 为 true, BigInt('1') === 1 为 false
BigInt('2') > 1 为 true
Boolean(BigInt('0')) 为 false(和 Number 一样)
1 2 3 4 5 6 7 8 const bigIntA = BigInt ('1234567890123456789012345678901234567890' )const bigIntB = 1234567890123456789012345678901234567890n const bigIntC = BigInt ('0x1fffffffffffffffffffffffffffff' )console .log (typeof bigIntA)
存入 JSON BigInt 类型的值不能直接存入 JSON(会出现 TypeError 错误), 需要先转换为字符串
1 2 3 4 5 6 7 8 9 const obj = { bigInt : 1234567890123456789012345678901234567890n }const json = JSON .stringify (obj) const json = JSON .stringify ({ bigInt : obj.bigInt .toString () })BigInt .prototype .toJSON = function ( ) return this .toString ()
遍历
方法
适用对象
示例
for
数组
for (let i = 0; i < arr.length; i++)
for...of
数组、Set、Map、字符串等可迭代对象
for (const value of arr)可以使用 break 和 continue
for...in
对象
for (const key in obj)
forEach
数组、Set、Map
arr.forEach(value => null)
上面的遍历方法无有效的返回值, 而其他遍历数组的方法(如 map、filter、reduce 等)都有有效的返回值, 详见数组方法
Set 和 Map 集合都可以使用 for...of 循环遍历, 也可以使用 forEach 方法遍历
通过解构赋值 可以在遍历 Map 集合时获取键和值
1 2 3 4 5 6 7 8 9 10 for (const value of set) { console .log (value) } set.forEach (value => console .log (value)) for (const [key, value] of map) { console .log (key, value) } map.forEach ((value, key ) => console .log (key, value))
可迭代对象 for...of 适用于实现了迭代器 [Symbol.iterator]()(这个常量见下文)方法的对象, 即可迭代对象 , 包括数组、Set、Map、字符串等
调用 [Symbol.iterator]().next() 方法会返回一个对象, 形如 { value: xxx, done: false }, value 为当前值, done 为是否遍历结束; 再次调用 next() 方法会返回下一个值, 直到 done 为 true; 这就是 for...of 的原理
只要实现了 [Symbol.iterator]() 方法, 任何对象都可以使用 for...of 遍历
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 const person = { name : 'xiaoyezi' , age : 18 , hobby : ['coding' , 'painting' , 'psychology' ], [Symbol .iterator ]() { const keys = Object .keys (this ) let index = 0 return { next : () => { if (index < keys.length ) { return { value : this [keys[index++]], done : false } } else { return { done : true } } } } } } for (const value of person) { console .log (value) }
用生成器函数可以更方便地实现迭代器, 见下文
生成器 ES6 中引入了 function* 关键字, 用于定义生成器函数, 可以用于生成迭代器
生成器函数内部可以使用 yield 关键字, 用于返回一个迭代器 , 并暂停函数的执行
返回的迭代器可以调用 next 方法, 用于继续函数的执行 , 并返回一个迭代器
生成器函数内部可以使用 return 关键字, 用于结束函数的执行
生成器函数内部可以使用 throw 关键字, 用于抛出一个错误
1 2 3 4 5 6 7 8 9 10 11 12 13 function * person (name, age ) { yield name yield age } const generator = person ('xiaoyezi' , 18 )console .log (generator.next ()) console .log (generator.next ()) console .log (generator.next ())
用于发号器
1 2 3 4 5 6 7 8 9 10 11 12 13 function * idMaker ( ) { let index = 0 while (true ) { yield index++ } } const generator = idMaker ()console .log (generator.next ().value ) console .log (generator.next ().value ) console .log (generator.next ().value )
用于实现[Symbol.iterator]()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 const obj = { name : 'xiaoyezi' , age : 18 , hobby : ['coding' , 'painting' , 'psychology' ], [Symbol .iterator ]: function * () { for (const key in this ) yield this [key] } } for (const value of obj) console .log (value)
用于异步 生成器函数可以用于异步编程, 可以用于实现 async、await 的功能; 调用 next 方法时可以传入参数, 用于传递异步操作的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function * asyncFunction ( ) { try { const result = yield fetch ('https://api.xxx.com' ) console .log (result) } catch (error) { console .log (error) } } const generator = asyncFunction ()generator.next ().value .then ( result => generator.next (result) )
Worker JavaScript 是单线程语言, 即一次只能执行一个任务, 但 HTML5 提供了 Web Worker 接口, 可以创建多个线程, 用于执行一些耗时的任务, 如大量计算、文件读取等, 以提高性能
worker 被标准化后, 也被 Node.js、Deno 等环境支持
Worker 不能访问 DOM, 也不能访问 window 对象, 它的全局对象是 DedicatedWorkerGlobalScope, 可以通过 self 访问
专用 Worker 只能被生成它的脚本所使用, 而共享 Worker 可以被多个脚本使用
为了更好的错误处理控制, 推荐在主线程中将 Worker 的相关代码放在 if(window.Worker) {} 中
Worker 可以生成 subWorker, 但这些 subWorker 都是托管在主线程中的, 其 URL 相对路径是相对于主线程的
Worker 不能访问 HTML 中引入的外部脚本, 需要使用 importScripts('url') 方法引入
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 const worker = new Worker ('worker.js' )inputElement.onchange = () => { worker.postMessage (inputElement.value ) } worker.onmessage = function (event ) { console .log (event.data ) } setTimeout (() => worker.terminate (), 30000 )onmessage = event => { console .log (event.data ) postMessage ('你好' ) } onerror = event => { event.preventDefault () console .log (event.message ) console .log (event.filename ) console .log (event.lineno ) }
注意事项
线程安全 : Worker 与主线程之间的通信是通过消息传递的, 且不共享全局对象, 因此不会出现数据竞争、死锁等问题, 相对不容易搞出问题
内容安全策略 : Worker 并不受限于主线程的内容安全策略, 有着自己的独立上下文
消息传递 : 主线程与 Worker 之间的数据传递是通过拷贝的方式, 而不是共享内存
SharedWorker SharedWorker 可以被多个脚本使用, 但这些脚本必须来自同一个域名
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 const worker = new SharedWorker ('worker.js' )inputElement.onchange = () => { worker.port .postMessage (inputElement.value ) } worker.port .onmessage = event => { console .log (event.data ) } onconnect = event => { const port = event.ports [0 ] port.onmessage = event => { console .log (event.data ) port.postMessage ('你好' ) } }
IdexedDB IndexedDB 是 HTML5 中的一种本地数据库, 用于存储大量的结构化数据, 包括 blob, ArrayBuffer 等; 相比 localStorage 等, IndexedDB 更适合存储大量数据
由于 IndexedDB 的 API 比较复杂, 对于简单的应用, 可以使用 localForage, IDB-Keyval 等库简化操作
localForage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import localforage from 'localforage' const value = await localforage.getItem ('key' )await localforage.setItem ('key' , value) await localforage.removeItem ('key' )await localforage.clear ()const length : number = await localforage.length ()const keys : string [] = await localforage.keys ()
IDB-Keyval
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { set, get, ... } from 'idb-keyval' await set ('key' , value)await setMany ([ ['key1' , value], ['key2' , value], ]) const value : any = await get ('key' )const [value1, value2] = await getMany (['key1' , 'key2' ])const allKV = await entries () const allKeys = await keys ()const allValues = await values ()await update ('key' , value => value.push ('new' ))await del ('key' )await delMany (['key1' , 'key2' ])await clear ()
WebSocket WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议, 用于实现客户端和服务器之间的实时通信
类型
内容
说明
构造函数
WebSocket(url)
创建一个 WebSocket 对象
属性
ws.binaryType
WebSocket 连接所传输的二进制数据类型'blob' 或 'arraybuffer'
属性
ws.readyState
只读, WebSocket 连接的当前状态0: 连接尚未建立1: 连接已建立2: 连接正在关闭3: 连接已关闭
属性
ws.url
只读, WebSocket 连接的 URL
事件
ws.onclose
连接关闭时触发
事件
ws.onerror
连接出错时触发
事件
ws.onmessage
接收到消息时触发 回调函数的参数是一个 MessageEvent 其 data 属性是接收到的消息
事件
ws.onopen
连接成功时触发
方法
ws.close()
关闭连接
方法
ws.send(data)
发送消息 (将消息加入发送队列)data 可以是字符串、ArrayBuffer、Blob
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const ws = new WebSocket ('ws://localhost:8080' )ws.onopen = () => { console .log ('连接成功' ) ws.send ('你好' ) } ws.onmessage = event => { console .log (event.data ) } ws.onclose = () => { console .log ('连接关闭' ) } ws.close ()
Proxy Proxy 对象用于拦截和自定义基本操作的行为(如属性查找、赋值、函数调用等)
类型
内容
说明
术语
handler
处理器事件, 其属性是某个事件的处理函数
构造函数
new Proxy(target, handler)
创建一个 Proxy 对象
构造函数
Proxy.revocable(target, handler)
创建一个可撤销的 Proxy 对象return.proxy: Proxy 对象return.revoke: 撤销 Proxy 的方法
handler 方法
getPrototypeOf(target)
拦截 Object.getPrototypeOf() 必须返回一个对象或 null
handler 方法
setPrototypeOf(target, prototype)
拦截 Object.setPrototypeOf() 如果成功修改 prototype, 返回 true
handler 方法
isExtensible(target)
拦截 Object.isExtensible() 必须返回一个布尔值
handler 方法
defineProperty(target, prop, descriptor)
拦截 Object.defineProperty() 如果成功定义属性, 返回 true
handler 方法
has(target, prop)
拦截 prop in target 必须返回一个布尔值
handler 方法
get(target, prop, receiver)
拦截属性读取receiver 是原始操作行为所在的对象, 一般是 Proxy 对象本身或继承的对象 返回值可以是任意类型
handler 方法
set(target, prop, value, receiver)
拦截属性设置 如果成功设置属性, 返回 true 如果设置失败, 返回 false 或抛出错误
handler 方法
deleteProperty(target, prop)
拦截属性删除 (delete 操作符) 如果成功删除属性, 返回 true 如果删除失败, 返回 false 或抛出错误
handler 方法
apply(target, thisArg, argumentsList)
拦截函数调用thisArg 是函数调用时的 this 值argumentsList 是函数调用时的参数列表
handler 方法
construct(target, argumentsList, newTarget)
拦截 new 操作符argumentsList 是构造函数的参数列表newTarget 是被 new 的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 const proxy = new Proxy ({}, { get (target, prop ) { return prop in target ? target[prop] : 'default' } }) proxy.name = 'xiaoyezi' proxy.hobby = undefined console .log (proxy.name ) console .log (proxy.age ) console .log (proxy.hobby )
动态执行 可以在运行时动态执行代码, 有以下几种方法
方法
作用域
同步或异步
示例
eval
当前
同步
eval('console.log(1)')
setTimeout
全局
异步
setTimeout('console.log(1)', 0)
setInterval
全局
异步
setInterval('console.log(1)', 1000)
创建 script 元素
全局
同步
const script = document.createElement('script')script.innerHTML = 'console.log(1)'document.body.appendChild(script)
new Function
全局
同步
const func = new Function('console.log(1)')func()
随意执行用户输入的代码非常危险, 请特别注意!
JSDoc
JSDoc 是一种用于描述 JavaScript 代码的注释规范, 可用于生成文档和类型检查
1 2 3 4 5 6 7 pnpm add -g jsdoc jsdoc ./index.js jsdoc ./src -r -d ./docs
类型注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const num = 1 const arr = [1 , 2 , 3 ]const arr = [1 , 2 , 3 ]const promise = new Promise (resolve => resolve (1 ))const strOrNum = 'str' const obj = { name : 'xiaoyezi' , age : 18 }const func = (name, age ) => console .log (name, age)
导入类型 1 2 3 4 5 6 7 8 9 10 const path = 'path' const path = 'path' const func = path => console .log (path)
1 2 export type PathLike = string
函数注解 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 function func (name, age ) { console .log (name, age) } async function func (name, age ) { return { name, age } } function func (person = { hobby: [] } ) { console .log (person.name , person.age ) }
类注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Person extends Animal { constructor (name, age ) { this .name = name this .age = age } print ( ) { console .log (this .name , this .age ) } }
类型定义 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 const person = { name : 'xiaoyezi' , age : 18 , } function func (person ) { console .log (person.name , person.age ) } function func (person ) { console .log (person.name , person.age ) }
Object 可以写成 object, 但为了区分基本类型和引用类型, 一般写成 Object
泛型 1 2 3 4 5 6 7 8 function identity (value ) { return value }
装饰器
注意: 装饰器目前还未被 JavaScript 官方标准化 (处于 Stage 3 阶段), 需要使用 Babel、TypeScript 等工具进行转译; 详见 https://github.com/tc39/proposal-decorators
装饰器是一种特殊的声明, 可以附加到类、方法、访问器、属性或参数上, 用于修改类的行为; 装饰器函数是一类特殊的函数, 接受原类/方法/属性等作为参数, 并返回一个新的类/方法/属性等
1 2 3 4 5 6 7 8 9 10 11 12 type Decorator = (value: Input, context: { kind: string name: string | symbol access: { get?(): unknown set?(value: unknown ): void } private ?: boolean static ?: boolean addInitializer(initializer: () => void ): void } ) => Output | void
kind
说明
对应装饰器类型
class
类装饰器
ClassDecorator
method
方法装饰器
ClassMethodDecorator
field
属性装饰器
ClassFieldDecorator
getter
getter 装饰器
ClassSetterDecorator
setter
setter 装饰器
ClassGetterDecorator
accessor
accessor 装饰器
ClassAutoAccessorDecorator
accessor accessor 即类自动访问器, 可以自动地定义私有字段及其 getter 和 setter
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 class Person { accessor x = 1 static accessor y = 2 accessor #z = 3 } class Person { #x = 1 static #y = 2 #z = 3 get x () { return this .#x } set x (value ) { this .#x = value } static get y () { return Person .#y } static set y (value ) { Person .#y = value } get z () { return this .#z } set z (value ) { this .#z = value } }