JavaScript 概述
JavaScript 编程语言允许你在 Web 页面上实现
复杂的功能
;如果你看到一个网页不仅仅显示静态的信息,而是显示依时间更新的内容,或者交互式地图,或者 2D/3D 动画图像,或者滚动的视频播放器,等等——你基本可以确定,这需要 JavaScript 的参与
JavaScript 编程语言是一种
具有函数优先的轻量级,解释型或即时编译型的 编程语言
,虽然它是作为开发 Web 页面的 脚本语言 而出名,但是它也被用到了很多非 浏览器 环境中,JavaScript 基于原型编程/多范式的动态脚本语言,并且支持面向对象、命令式和声明式(如 函数 式编程)风格
基础特性
1. 注释
// 单行注释
/* 多行注释 */
TODO 注释
注意:
注释应该描述代码是什么,而不是做什么
2. 变量、常量
变量是存储数据的容器
基础数据类型值存于栈内存
中,占用大小固定,变量标识符赋值时直接将 栈内存 中值可以直接进行拷贝
引用数据类型值存于堆内存
中,变量标识符中存储 堆内存 的地址,称之为引用
标识符
所有 JavaScript 变量
必须
以唯一的名称的标识
- 名称可包含字母、数字、下划线和美元符号
- 名称
必须
以字母开头 - 名称也可以 $ 和 _ 开头
- 名称对
大小写
敏感(y 和 Y 是不同的变量) 保留字
(比如 JavaScript 的关键词)无法用作变量名称- 推荐使用小驼峰命名法命名(myName)
声明语法
1. var
全局作用域(
全局变量
),挂载到全局对象 Window 上
存在变量提升(变量声明提升到其作用域顶部)
函数内声明具有函数作用域(局部变量)
2. ES6-let
块级作用域(
局部变量
),就算在最外层函数定义的 let 变量也不会被挂载到 window 上(不会与 window 相映射)
3. ES6-const
块级作用域(
局部变量
),就算在最外层函数定义的 const 常量也不会被挂载到 window 上(不会与 window 相映射)
基础类型数据值不可修改/引用类型数据引用地址不可修改
建议书写全大写,使用 _ 下滑线分隔
注意:
变量不声明直接赋值,将会被挂载到全局对象 Window
上
3. 表达式 and 运算符
JavaScript 从左向右计算表达式和运算符;不同的次序会产生不同的结果
表达式分组
语法 | 详解 | 示例 |
---|---|---|
( ) | (3 + 4) |
成员运算符
语法 | 详解 | 示例 |
---|---|---|
. | person.name | |
[] | person[‘name’] |
函数调用
语法 | 详解 | 示例 |
---|---|---|
() | myFunction() |
对象创建
语法 | 详解 | 示例 |
---|---|---|
new | new Date() |
自增/自减
语法 | 详解 | 示例 |
---|---|---|
++ | 后缀递增/先执行 后自增 | i++ |
– | 后缀递减/先执行 后自减 | i– |
++ | 前缀递增/先自增 后执行 | ++i |
– | 前缀递减/先自减 后执行 | –i |
类型运算符
语法 | 详解 | 示例 |
---|---|---|
typeof | 返回变量的类型(object/number/string/boolean/undefined/null,symbol,bigint) | typeof x |
instanceof(ES6) | 如果对象是对象类型的实例,返回 true(判断是否为指定对象类型的实例,无法判断基础数据类型) | instanceof Array |
算术运算符
语法 | 详解 | 示例 |
---|---|---|
** (ES7) | 求幂 | 10 ** 2 |
* | 乘(类型转换,非数值类型转换为数值类型进行计算) | 10 * 5 |
/ | 除(类型转换,非数值类型转换为数值类型进行计算) | 10 * 5 |
% | 模数除法 | 10 % 5 |
+(级联运算) | 用于对字符串进行相加(concatenate,级联);在用于字符串时,+ 运算符被称为级联运算符 | ‘my’+‘name’ |
+ | 加(类型转换,非数值类型转换为数值类型进行计算) | 10 + 5 |
- | 减(类型转换,非数值类型转换为数值类型进行计算) | 10 - 5 |
位运算符
(位运算符处理 32 位数)该运算中的任何数值运算数都会被转换为 32 位的数;结果会被转换回 JavaScript 数
语法 | 详解 | 示例 |
---|---|---|
<< | 左位移 | x << 2 |
>> | 右位移 | x >> 2 |
>>> | 右位移(无符号) | x >>> 2 |
& | 按位于 | x & y |
^ | 按位 XOR(异或) | x ^ y |
| | 按位或 | x | y |
比较运算符
语法 | 详解 | 示例 |
---|---|---|
< | 小于 | x < y |
<= | 小于或等于 | x <= y |
> | 大于 | x > y |
>= | 大于或等于 | x >= y |
== | 相等,要求值相同(类型转换) | x == y |
=== | 严格相等要求值、类型相同(全等) | x === y |
!= | 不相等要求值不相同 | x != y |
!== | 严格不相等要求值、类型不相同(全不等) | x !== y |
对象属性
语法 | 详解 | 示例 |
---|---|---|
in | 属性是否在该对象中存在 | PI in Math |
逻辑运算符
语法 | 详解 | 示例 |
---|---|---|
&& | 逻辑与,非布尔类型会转为布尔类型 | x && y |
|| | 逻辑否,非布尔类型会转为布尔类型 | x || y |
! | 逻辑非,非布尔类型会转为布尔类型 | ! x |
?: | 三元运算符 | ? “Yes” : “No” |
赋值运算符
语法 | 详解 | 示例 |
---|---|---|
= | x = y | |
+= | x += y | |
-= | x -= y | |
*= | x *= y | |
%= | x %= y | |
<<= | x <<= y | |
>>= | x >>= y | |
>>>= | x >>>= y | |
&= | x &= y | |
^= | x ^= y | |
|= | x |= y |
暂停函数(ES6)
语法 | 详解 | 示例 |
---|---|---|
yield | yield x |
逗号
语法 | 详解 | 示例 |
---|---|---|
, | 7 , 8 |
4. 流程控制
条件判断
if…else if…else
当指定条件为真,if 语句会执行一段语句;如果条件为假,则执行另一段语句;
if (condition) {
// 判断条件为真执行
} else if (condition) {
// 判断条件为真执行
} else {
// 否则执行此语句
}
switch…case…default
switch 语句评估一个表达式,将表达式的值与case子句匹配,并执行与该情况相关联的语句;
switch (key) {
case value:
// key === value时执行
break; // 执行完毕跳出
default:
// 否则执行此语句
break; // 执行完毕跳出
}
循环
while
while 语句可以在某个条件表达式为真的前提下,循环执行指定的一段代码,直到那个表达式不为真时结束循环;
while (condition) {
// statement
}
do…while
do…while 语句创建一个执行指定语句的循环,直到condition值为 false;在执行statement 后检测condition,所以指定的statement至少执行一次;
do {
// statement
} while (condition);
for
for 语句用于创建一个循环,它包含了三个可选的表达式,这三个表达式被包围在圆括号之中,使用分号分隔,后跟一个用于在循环中执行的语句(通常是一个块语句)
for (let index = 0; index < array.length; index++) {
// statement
}
for…in
for…in 语句以任意顺序迭代一个对象的除 Symbol 以外的可枚举属性,包括继承的可枚举属性(原型对象属性)
for (variable in object) {
// statement
}
ES6-for…of
for…of 语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
for…of 无法遍历普通的对象,需要和 Object.keys 配合使用
for (variable of iterable) {
//statements
}
控制语句
continue
终止本次循环(迭代),执行下次循环(迭代)
break
退出循环(迭代)
5. 严格模式
"use strict"
指令开启,“use strict” 是 JavaScript 1.8.5 中的新指令(ECMAScript version 5);它不算一条语句,而是一段文字表达式
,更早版本的 JavaScript 会忽略它;
"use strict"
的作用是指示 JavaScript 代码应该以“严格模式”执行;
当使用 ES6 模块化时,JavaScript 自动加入严格
严格模式的作用
- 消除了Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为
- 消除代码运行的一些不安全之处,保证代码运行的安全
- 提高编译器效率,增加运行速度
严格模式规范
- 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量.严格模式禁止这种用法,变量都必须先用 var/let/const 命令声明,然后再使用
- 严禁删除已经声明变量;例如,delete x 语法是错误的
- 函数不能有重名的参数
- 函数必须声明在最前面
- 构造函数必须使用 new 关键字调用(默认 this 指向 window,不调用会指向 undefined 报错)
6. 作用域
1. 全局作用域
最外层定义中的变量拥有全局作用域,对于任何内部函数来说,都是可以访问的
2. 局部作用域
局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部;在ES6 之前,只有函数可以划分变量的作用域,所以在函数的外面无法访问函数内的变量
3. 块级作用域
以代码块用以区分的作用域
4. 函数作用域
在函数体内部声明的变量存在函数作用域,在函数内部或上级函数的变量才可以访问
7. 词法作用域
词法作用域,又叫静态作用域,变量被创建时就确定好了,而非执行阶段确定的。也就是说我们写好代码时它的作用域就确定了,Javascript遵循的就是词法作用域
ES6 之前 JS 采用的是函数作用域+词法作用域, ES6 之后 JS 采用的是块级作用域+词法作用域
8. 作用域链
在当前的作用域中没有定义num变量,而当前作用域没有定义的变量,就成为了自由变量,自由变量的值如何得到 —— 向父级作用域(
创建这个函数的作用域
)寻找,这样查找的过程就被称为作用域链
备注: 可以使用闭包延长作用域链
9. 预解析
JavaScript代码的执行是由浏览器中的JavaScript解析器来执行的;JavaScript解析器执行JavaScript代码的时候,分为两个过程: 预解析过程和代码执行过程
预解析过程:
- 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值
- 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用
- 先提升 function ,在提升 var(let,const提升但存在暂时性死区)
变量提升
定义变量的时候,变量的声明会被提开到作用域的最上面,变量的赋值不会提升
暂时性死区(ES6)
暂时性死区是针对
const
,let
这两个关键字而产生的概念
备注: 变量提升这个 javascript 的基本概念无法撼动, const
,let
作为块级作用域也不能避免
;和 var
不同,这两个关键字将作用域限制在了 块
中,且规定了在该块中,由这两个关键字定义的变量已经被分配内存;即其实已经 存在
了,但程序未执行到声明处时,访问该变量都会报引用错误;这个时候,对于该变量来说就是 暂时性死区
,通俗来说就是该变量存在,但并未完全存在
数据类型
注意:
不要使用 new 关键字构建基础数据类型和数组,new 关键字使代码复杂化;也可能产生一些意想不到的结果;
基础数据类型
Number
数值始终是 64 位的浮点数,数值始终以双精度浮点数来存储,根据国际 IEEE 754 标准,此格式用 64 位存储数值,其中 0 到 51 存储数字(片段),52 到 62 存储指数,63 位存储符号
精度最多只能到53个二进制位,这意味着,绝对值小于2的53次方的整数,即-253到253,都可以精确表示,所以简单的法则就是,JavaScript 对15位的十进制数都可以精确处理
Number 属性
Number.MAX_VALUE // Number.MAX_VALUE 属性表示在 JavaScript 里所能表示的最大数值
Number.MIN_VALUE // Number.MIN_VALUE 属性表示在 JavaScript 中所能表示的最小的正值
Number.NEGATIVE_INFINITY // Number.NEGATIVE_INFINITY 属性表示负无穷大(溢出返回)
Number.POSITIVE_INFINITY // Number.POSITIVE_INFINITY 属性表示正无穷大(溢出返回)
Number.NaN // NaN 属于 JavaScript 保留词,指示某个数不是合法数,尝试用一个非数字字符串进行除法会得到 NaN(Not a Number):NaN 是数,typeof NaN 返回 number
Infinity // Infinity (或 -Infinity)是 JavaScript 在计算数时超出最大可能数范围时返回的值;除以 0(零)也会生成 Infinity;Infinity 是数:typeOf Infinity 返回 number;
注意:
数字属性不可用于变量,数字属性属于名为 number 的 JavaScript 数字对象包装器,这些属性只能作为 Number对象包装器 访问;使用变量、表达式或值访问都将返回 undefined
Number 方法
let newNumber = Number.parseInt(string[, radix]) // 函数解析一个参数,返回一个整数(如果字符串的第一个字符不能被转换为数字,那么 parseInt() 会返回 NaN;只有字符串中的第一个数字会被返回)
// [radix]: 表示要解析的数字的基数,该值介于 2 ~ 36 之间; string: 需要解析的字符串; 给定值被解析成整数,如果无法被解析成整数,则返回NaN
let newNumber = Number.parseFloat(string) // 函数解析一个参数(必要时先转换为字符串),返回一个浮点数
// string: 需要解析的字符串; 给定值被解析成浮点数,如果无法被解析成浮点数,则返回NaN
let newNumber = Number.toExponential(fractionDigits) // 以指数表示法格式化数值字符串,返回一个用幂的形式 (科学记数法) 来表示Number 对象的字符串
// [fractionDigits]: 可指定一个整数,用来指定小数点后有几位数字; 默认情况下用尽可能多的位数来显示数字
let newNumber = Number.toFixed(digits) // 以定点表示法格式化数值,使用定点表示法表示给定数字的字符串;
// [digits]:小数点后数字的个数,介于 0 到 20 (包括)之间,实现环境可能支持更大范围;如果忽略该参数,则默认为 0
let newNumber = Number.toPrecision(precision) // 以指定的精度格式化,返回以定点表示法或指数表示法表示的一个数值对象的字符串表示
// [precision]:一个用来指定有效数个数的整数
Number.toString([radix]) // 返回指定 Number 对象的字符串表示形式,radix 指定要用于数字到字符串的转换的基数 (从 2 到 36);如果未指定 radix 参数,则默认值为 10
Number.valueOf() // 返回表示指定 Number 对象的原始值的数字
Number.isNaN() // 判断是否为非数值(为全局 isNaN 的稳妥版本)
String
用于存储和操作文本,是引号中的零个或多个字符
注意:
所有字符串方法都会返回新字符串;它们不会修改原始字符串,正式地说:字符串是不可变的:字符串不能更改,只能替换
String 属性
String.length // 返回字符串的长度
空值 // 空的字符串变量既有值也有类型('', string)
String 方法
// 增
// 删
// 改
let newStr = String.concat(str2, [, ...strN]) // 拼接字符串,返回一个新字符串
let newStr = String.trim() // (ES6)删除两端空白字符,返回一个新字符串
let newStr = String.toUpperCase() // 将字符串转为大写形式并返回
let newStr = String.toLowerCase() // 将字符串转为小写形式并返回
let newStr = String.toLocaleUpperCase() // 将字符串转为大写形式(根据主机语言环境)并返回
let newStr = String.toLocaleLowerCase() // 将字符串转为小写形式(根据主机语言环境)并返回
let newStr = String.repeat(count) // 将字符串重复 count 次并返回
let newStr = String.split([separator[, limit]]) // 使用指定的分隔符字符串将一个 String 对象分割成子字符串数组
let newStr = String.slice(beginIndex[, endIndex]) // 提取字符串的一部分,并返回一个新字符串,如果不指定 [endIndex] 则截取全部,如果 [endIndex] 是负数,则从后舍弃对应的字符
let newStr = String.toString() // 返回指定对象的字符串表示
let newStr = String.substring(indexStart[, indexEnd]) // 返回开始索引到结束索引之间的一个子集字符串
// 查
let strChar = String.charAt(index) // 返回指定索引(默认为0)处的字符
let strUnicode = String.charCodeAt(index) // 返回指定索引(默认为0)处的 Unicode 值
let strIndex = String.indexOf(searchValue [, fromIndex]) // 返回第一次出现的指定值的索引值,[fromIndex]: 指定从何处索引开始查找(默认为 0),空字符串也返回0
let strLastIndex = String.lastIndexOf(searchValue[, fromIndex]) // 返回最后一次出现指定值的索引值,[fromIndex]: 指定从何处索引开始查找(默认为 +Infinity)
let isCharArise = String.localeCompare(compareString[, locales[, options]]) // 返回一个数字来指示一个参考字符串是否在排序顺序前面或之后或与给定字符串相同
- 在前返回 -1(负数),在后返回 1(正数),两个字符串相对返回 0
String.startsWith(searchString[, position]) // 判断当前字符串是否以另外一个给定的子字符串开头,返回布尔值,[position]: 判断开始的位置,默认为0
// 模板匹配
let newStr = String.search(regexp) // 执行正则表达式和 String 对象之间的一个搜索匹配,匹配成功返回匹配成功,第一个匹配字符处索引(正数),失败返回(-1)
let newStr = String.match(regexp) // 检索返回一个字符串匹配正则表达式的结果
let newStr = String.replace(regexp|substr, newSubStr|function) // 返回一个由替换值(replacement)替换部分或所有的模式(pattern)匹配项后的新字符串。模式可以是一个字符串或者一个正则表达式,替换值可以是一个字符串或者一个每次匹配都要调用的回调函数。如果pattern是字符串,则仅替换第一个匹配项
// ES6
String.startsWith(searchString[, position]) // 判断当前字符串是否以另外一个给定的子字符串开头,返回布尔值
String.endsWith(searchString[, length]) // 判断当前字符串是否是以另外一个给定的子字符串“结尾”的,返回布尔值
转义字符
\b 退格键
\f 换页
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
Boolean
Boolean 属性
Boolean.true let x = true;
Boolean.false let y = false;
Boolean 方法
Boolean.toString() // 返回布尔值的字符串表示
Boolean.valueOf() // 返回布尔值
Undefined
没有值的变量,典型: 1. 变量声明未赋值; 2. 调用函数时,应该提供的参数没有提供; 3. 对象没有赋值的属性; 4. 函数没有返回值时
Null
值 null 是一个字面量,不像 undefined,它不是全局对象的一个属性;null 是表示缺少的标识,指示变量未指向任何对象;把 null 作为尚未创建的对象,也许更好理解;在 API 中,null 常在返回类型应是一个对象,但没有关联的值的地方使用;
备注: 值 null 特指对象的值未设置;它是 JavaScript 基本类型 之一,在布尔运算中被认为是falsy;对象原型链的终点!
Symbol
symbol 是一种基本数据类型 (primitive data type);Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法;它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:“new Symbol()”;
作用: Symbol(‘key’)可以解决属性名冲突的问题
BigInt
BigInt
是一种内置对象,它提供了一种方法来表示大于2^53 - 1
的整数;这原本是 Javascript 中可以用Number
表示的最大数字;BigInt
可以表示任意大的整数
可以用在一个整数字面量后面加n
的方式定义一个BigInt
,如:10n
,或者调用函数BigInt()
(但不包含new
运算符)并传递一个整数值或字符串值
不能用于Math
对象中的方法;不能和任何Number
实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为BigInt
变量在转换成Number
变量时可能会丢失精度
注意:
当使用 BigInt
时,带小数的运算会被取整
引用数据类型
Object
Object 是 JavaScript 的一种 数据类型 ;它用于存储各种键值集合和更复杂的实体;Objects 可以通过 Object() 构造函数或者使用 对象字面量 的方式创建
在JavaScript中,几乎所有的对象都是Object类型的实例,它们都会从Object.prototype继承属性和方法,虽然大部分属性都会被覆盖(shadowed)或者说被重写了(overridden);
创建对象
字面量创建
let obj = {}
构造函数
function Star(age) {
this.age = age
}
let obj = new Star(18)
Object()
let obj = new Object({})
Object 属性
事物的特征,在对象中用属性来表示(常用名词)
Object.this // 指向其拥有者
Object 方法
事物的行为,在对象中用方法来表示(常用动词)
// 增
Object.defineProperty(obj, prop, descriptor) // 直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象(实现双相绑定的基础)
// configurable 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除;默认为 false;
// enumerable 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中;默认为 false;
// writable 当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符 (en-US)改变;默认为 false;
// value 该属性对应的值;可以是任何有效的 JavaScript 值(数值,对象,函数等);默认为 undefined;
// get 属性的 getter 函数,如果没有 getter,则为 undefined;当访问该属性时,会调用此函数;执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象);该函数的返回值会被用作属性的值;默认为 undefined;
// set 属性的 setter 函数,如果没有 setter,则为 undefined;当属性值被修改时,会调用此函数;该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象;默认为 undefined;
let newObject = Object.create(proto) // 用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)
// 删
delete Object.name // 删除指定对象上的某个属性
// 改
let newObject = Object.assign(target, ...sources) // ES6-将所有可枚举(Object.propertyIsEnumerable() 返回 true)和自有(Object.hasOwnProperty() 返回 true)属性从一个或多个源对象复制到目标对象,返回修改后的对象
// 查
Object.name // 访问对象中的 name 属性(可以进行赋值操作)
Object['name'] // 访问对象中的 name 属性(可以进行赋值操作)
Object.values(obj) // ES6-返回一个由一个给定对象的自身可枚举数值值组成的数组,数组中属性值的排列顺序和正常循环遍历该对象时返回的顺序一致
Object.getOwnPropertyNames() // 返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组
Object.keys(obj) // ES6-返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致
// ES6
Object.getOwnPropertyDescriptors() // 返回指定对象所有自身属性(非继承属性)的描述对象
Object.setPrototypeOf() // 方法用来设置一个对象的原型对象
Object.getPrototypeOf() // 用于读取一个对象的原型对象
Object.entries() // 返回一个对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对的数组
Object.fromEntries() // 用于将一个键值对数组转为对象
Object.prototype.constructor // 返回 Object 的构造函数(用于创建实例对象);注意,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串
Object.prototype.__proto__ // 已废弃,访问器属性(一个 getter 函数和一个 setter 函数), 暴露了通过它访问的对象的内部[[Prototype]] (一个对象或 null)
Object.hasOwnProperty(key) // 返回一个布尔值,指示对象自身属性(非继承)中是否具有指定的属性(也就是,是否有指定的键)
Object.prototype.isPrototypeOf() // 用于测试一个对象是否存在于另一个对象的原型链上
Object.prototype.propertyIsEnumerable() // 返回一个布尔值,表示指定的属性是否可枚举
Object.prototype.toLocaleString() // 返回一个该对象的字符串表示;此方法被用于派生对象为了特定语言环境的目的(locale-specific purposes)而重载使用
Object.prototype.toString() // 返回指定对象的字符串表示
Object.prototype.valueOf() // 返回指定对象的原始值
遍历次序规则
- 首先遍历所有数值键,按照数值升序排列
- 其次遍历所有字符串键,按照加入时间升序排列
- 最后遍历所有Symbol键,按照加入时间升序排
Array
用于在单一变量中存储多个值
创建数组
- 字面量创建
let arr = []
注意:
对未定义的索引赋值可在数组中创建未定义的“洞”(索引越界)(中间的元素使用 empty items 占位),ES6 规定数组空位使用 undefined 占位,尽量不要造成空位
Array 属性
Array.length // 数组长度(计数,从1开始)
Array.index // 数组索引(从0开始)
Array 方法
// 构造数组
let newArray = Array.from(arr) // 将伪数组转换为数组,返回这个数组
let newArray = Array.of(...arr) // 将一组数据转换为数组,返回这个数组
// 增
Array.push(element1, ..., elementN) // 添加到数组的尾部,返回新的 length 长度
Array.unshift(element1, ..., elementN) // 添加到数组的头部,返回新的 length 长度
// 删
Array.shift() // 删除数组中第一个元素,数组为空返回undefined,删除成功返回删除的那个值
Array.pop() // 删除数组中最后一个元素,数组为空返回undefined,删除成功返回删除的那个值
Array.splice(start[, deleteCount[, item1[, item2[, ...]]]]) // 删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容;此方法会改变原数组
delete arr[0] // 数组也是对象,删除指定对象上的某个属性(不推荐,删除后数组使用 empty items 占位)
// 改
let newArrarr= Array.slice([begin[, end]]) // 返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end);原始数组不会被改变
let newArray = Array.concat(value1[, value2[, ...[, valueN]]]) // 用于合并两个或多个数组;此方法不会更改现有数组,而是返回一个新数组(简写:arr = [].concat())
Array.reverse() // 在原数组上翻转数组
Array.sort([compareFunction]) // 在原数组上排序(a-b:升序, b-a:降序,),可以根据对象身上的属性进行排序(obj1.age - obj2.age);
- // 如果没有指明 compareFunction ,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序
let str = Array.join('') // 将一个数组(或一个类数组对象)的所有元素(以指定字符,默认为,号)连接成一个字符串并返回这个字符串
let str = Array.toString(arr) // 将指定的数组及其元素转换为字符串表示
let str = Array.toLocaleString(arr) // 返回一个字符串表示数组中的元素;数组中的元素将使用各自的 toLocaleString 方法转成字符串,这些字符串将使用一个特定语言环境的字符串(例如一个逗号 ",")隔开
let newArray = Array.flat([depth]) // 方法会按照一个可指定的深度(默认为1)递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回(数组扁平化)
// 查
Array.isArray(fruits) // 判断参数是否为数组,返回布尔值
// 数组迭代
Array.forEach(callback(currentValue [, index [, array]])[, thisArg]) // ES6-为数组中的每个元素执行一次给定的函数,返回 undefined
var newArray = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }
[, thisArg]) // 遍历数组中每个元素执行一次给定函数后返回一个新数组
var newArray = arr.filter(callback(element[, index[, array]])[, thisArg]) // ES6-返回一个通过给定函数判断的新数组(过滤)
let value = Array.reduce((previousValue, currentValue, currentIndex, array) => { /* ... */ }, initialValue) // ES6-数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值
let value = Array.reduceRight(callback(accumulator, currentValue[, index[, array]])[, initialValue]) // ES6-接受一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值并返回
Array.every(callback(element[, index[, array]])[, thisArg]) // ES6-测试一个数组内的所有元素是否都能通过某个指定函数的测试,返回布尔值(一假则假)
Array.some(callback(element[, index[, array]])[, thisArg]) // ES6-测试数组中是不是至少有1个元素通过了被提供的函数测试,返回布尔值(一真则真)
Array.indexOf(searchElement[, fromIndex]) // 返回在数组中可以找到一个给定元素的第一个索引
Array.lastIndexOf(searchElement[, fromIndex]) // 返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个值的索引
Array.includes(valueToFind[, fromIndex]) // ES6-判断一个数组是否包含一个指定的值,返回布尔值
Array.find(callback[, thisArg]) // ES6-返回数组中满足提供的测试函数的第一个元素的值
Array.findIndex(callback[, thisArg]) // ES6-返回数组中满足提供的测试函数的第一个元素的索引
Funciton
JavaScript 函数实际上都是一个 Function 对象;运行 (function(){}).constructor === Function // true 便可以得到这个结论
函数是被设计为执行特定任务的代码块(函数声明
),函数会在某代码调用它时被执行(函数调用
)
Function 语法定义
- 函数通过 function 关键词进行定义,其后是函数名和括号 ()
- 函数名可包含字母、数字、下划线和美元符号(规则与变量名相同)
- 圆括号可包括由逗号分隔的参数(参数 1, 参数 2, …)
- 由函数执行的代码被放置在花括号中:{}
- 函数参数(Function parameters)是在函数定义中所列的名称(
形参
) - 函数参数(Function arguments)是当调用函数时由函数接收的真实的值(
实参
) - 函数遇到 return 语句退出函数体
不再执行后续代码
- 在函数中,
参数是局部变量
- 被作为
实参传入另一函数
,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数
Function 函数声明
- 一个被函数声明创建的函数是一个 Function 对象,具有 Function 对象的所有属性、方法和行为
- 函数也可以被表达式创建( function expression )
let fn = function() {} // 函数表达式
- 函数使用对象式创建
let fn = new Function("num1", "num2", "return num1 + num2")
- 默认情况下,函数是返回
undefined
的;想要返回一个其他的值,函数必须通过一个 return 语句指定返回值;
Function 函数提升
函数提升只会提升函数声明式写法,函数表达式的写法不存在函数提开
函数提升的优先级大于变量提升的优先级,即函数提升在变量提升之上
Function 函数调用
fn 引用的是函数对象, fn()引用的是函数结果
- 在函数定义中,this 引用该函数的
拥有者
切记:
this 默认情况下都是指向其调用者,谁调用this指向谁
Function 高阶函数(回调函数)
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出(回调函数)
所谓”回调函数”(Callback),就是那些会被主线程挂起来的代码;异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数,或者理解为以函数以参数的形式传递给另一个函数,在特定时间再调用的函数被称为回调函数
Function 闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数 ----- JavaScript 高级程序设计;简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量
作用: 延伸变量的作用范围
示例: 立即执行函数/递归/返回函数
(function() { console.log(demo) })(demo) // 立即执行函数,括号内函数访问到了调用函数的形参
function fn() { fn() } // 递归函数,函数自身调用自身
function fn() { return fn2age => { console.log(age) } } // 函数返回一个函数
优缺点:
闭包的主要作用: 延伸了变量的作用范围,因为闭包函数中的局部变量不会等着闭包函数执行完就销毁,因为还有别的函数要调用它,只有等着所有的函数都调用完了他才会销毁 闭包会造成内存泄漏
如何解决: 用完之后手动释放详解:
闭包不仅仅可以实现函数内部的作用域访问这个函数中的局部变量,还可以实现全局作用域或者是别的地方的作用域也可以访问到函数内部的局部变量实现方法就是return了一个函数,所以return函数也是我们实现闭包的一个主要原理,因为返回的这个函数本身就是我们 fn 函数内部的一个子函数,所以子函数是可以访问父函数里面的局部变量的,所以返回完毕之后,外面的函数一调用,就会回头调用返回的这个函数,所以就可以拿到这个子函数对应的父函数里面的局部变量注意:
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值
Function 递归
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数;简单理解:函数内部自己调用自己, 这个函数就是递归函数;递归函数的作用和循环效果一样
由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return;
备注:
所有能够使用递归实现的方法都能够使用迭代实现(且效率更高)
function fn(n) {
if (n === 1) {
return 1
}
return n * fn(n - 1)
}
// 尾递归
function fn(n, count) {
if (n === 1) {
return count
}
return fun(n - 1, n * count)
}
Function 函数构造器
Function.prototype.call(thisArg, arg1, arg2, ...) // 使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数,返回调用有指定this值和参数的函数的结果
Function.prototype.apply(thisArg, [argsArray]) // 一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数,返回调用有指定this值和参数的函数的结果
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]]) // 创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用,返回一个原函数的拷贝,并拥有指定的 this 值和初始参数;
Function.prototype.toString() // 返回一个表示当前函数源代码的字符串
原生对象
Arguments (类数组)
Arguments
对象是一个对应于传递给函数的参数的类数组对象,typeof 返回 Object
arguments
对象是所有(非箭头)函数中都可用的局部变量
;你可以使用arguments
对象在函数中引用函数的参数;此对象包含传递给函数的每个参数,第一个参数在索引 0 处,未声明形参时接收
备注: 如果要编写兼容 ES6 的代码,那么优先推荐使用剩余参数,“类数组” 意味着 arguments
有 length
属性并且属性的索引是从零开始的,但是它没有 Array
的内置方法
Arguments 转换为数组
var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);
// ES2015
const args = Array.from(arguments);
const args = [...arguments];
Arguments 属性
arguments.callee // 指向参数所属的当前执行的函数
arguments.length // 传递给函数的参数数量
arguments[@@iterator] // 返回一个新的 Array 迭代器 对象,该对象包含参数中每个索引的值
Date (时间)
Date
对象提供了对时间操作的一系列属性和方法
Date 方法
let timer = new Date() // 创建一个 JavaScript Date 实例,该实例呈现时间中的某个时刻
let timer = new Date(tiem) // 调用 Date 构造函数返回一个指定数据的 Date 实例对象
Date.now() // 方法返回自 1970 年 1 月 1 日 00:00:00 (UTC) 到当前时间的毫秒数(时间戳)
Date.prototype.getFullYear() // 返回4位年份
Date.prototype.getMonth() // 返回月份(0 - 11)
Date.prototype.getDay() // 返回星期几(0 - 6)
Date.prototype.getDate() // 返回月中日期(0 - 31)
Date.prototype.getHours() // 返回小时(0 - 24)
Date.prototype.getMinutes() // 返回分钟(0 - 59)
Date.prototype.getSeconds() // 返回秒数(0 - 59)
Date.prototype.getYear() // 返回2位年份
Date.prototype.getTime() // 返回一个时间的格林威治时间数值(时间戳)
Date.prototype.getMilliseconds() // 根据本地时间,返回一个指定的日期对象的毫秒数
Date.prototype.toLocaleDateString() // 返回该日期对象日期部分的字符串,该字符串格式因不同语言而不同
Date.prototype.toLocaleTimeString() // 返回该日期对象时间部分的字符串,该字符串格式因不同语言而不同
Date.prototype.toLocaleString() // 返回该日期对象的字符串,该字符串格式因不同语言而不同
Date.prototype.toUTCString() // 把一个日期转换为一个字符串,使用 UTC 时区
Error (异常)
Error 属性
Error.prototype.message // 有关错误信息,人类易读的(human-readable)描述
Error.prototype.name // 表示 error 类型的名称;初始值为"Error"
Errot 方法
Error.prototype.toString() // 返回一个指定的错误对象(Error object)的字符串表示
Error 对象
EvalError // 代表了一个关于 eval 函数的错误;此异常不再会被 JavaScript 抛出,但是 EvalError 对象仍然保持兼容性
InternalError // 非标准,表示出现在 JavaScript 引擎内部的错误; 例如: "InternalError: too much recursion"(内部错误:递归过深)
RangeError // 标明一个错误,当一个值不在其所允许的范围或者集合中
ReferenceError // (引用错误)对象代表当一个不存在(或尚未初始化)的变量被引用时发生的错误
SyntaxError // 代表尝试解析语法上不合法的代码的错误
TypeError // 用来表示值的类型非预期类型时发生的错误(类型错误!)
URIError // 用来表示以一种错误的方式使用全局 URI 处理函数而产生的错误
JSON (JS 对象表示法)
JSON
这个对象除了对象转换 JSON 格式和利用 JSON 格式创建对象外,本身并没有其他作用,也不能被调用或者作为构造函数调用
JSON 方法
JSON.stringify() // 将一个 JavaScript 对象或值转换为 JSON 字符串,如果指定了一个 replacer 函数,则可以选择性地替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性
JSON.parse() // 用来解析 JSON 字符串,构造由字符串描述的 JavaScript 值或对象;提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换 (操作),解析 null 会返回 null
RegExp (正则表达式)
RegExp
对象用于将文本与一个模式匹配
创建 RegExp 对象
构造函数创建
new RegExp('ab+c', 'i'); // 首个参数为字符串模式的构造函数
new RegExp(/ab+c/, 'i'); // 首个参数为常规字面量的构造函数
- 在脚本运行过程中,用构造函数创建的正则表达式会被编译。如果正则表达式将会改变,或者它将会从用户输入等来源中动态地产生,就需要使用构造函数来创建正则表达式
字面量创建
/ab+c/i; //字面量形式
RegExp 方法
RegExp.prototype.exec() // 指定字符串中执行一个搜索匹配,返回一个结果数组或 null
ES6-Set (元组)
Set
对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用
Set
对象是值的集合,你可以按照插入的顺序迭代它的元素;Set 中的元素只会出现一次,即 Set 中的元素是唯一的
Set 属性
Set.prototype.size // 返回 Set 对象中的值的个数
Set 方法
Set.prototype.add(value) // 在Set对象尾部添加一个元素。返回该 Set 对象
Set.prototype.clear() // 移除Set对象内的所有元素
Set.prototype.delete(value) // 移除值为 value 的元素,并返回一个布尔值来表示是否移除成功。Set.prototype.has(value) 会在此之后返回 false
Set.prototype.entries() // 返回一个新的迭代器对象,该对象包含 Set 对象中的按插入顺序排列的所有元素的值的 [value, value] 数组。为了使这个方法和 Map 对象保持相似, 每个值的键和值相等
Set.prototype.forEach(callbackFn[, thisArg]) // 按照插入顺序,为 Set 对象中的每一个值调用一次 callBackFn。如果提供了thisArg参数,回调中的 this 会是这个参数
Set.prototype.has(value) // 返回一个布尔值,表示该值在 Set 中存在与否
Set.prototype.keys() // 与 values() 方法相同,返回一个新的迭代器对象,该对象包含 Set 对象中的按插入顺序排列的所有元素的值
Set.prototype.values() // 返回一个新的迭代器对象,该对象包含 Set 对象中的按插入顺序排列的所有元素的值
Set.prototype[@@iterator]() // 返回一个新的迭代器对象,该对象包含 Set 对象中的按插入顺序排列的所有元素的值
Set 使用示例(数组去重)
[...new Set(numbers)] // 数组去重
Map (对象)
Map
对象保存键值对,并且能够记住键的原始插入顺序;任何值(对象或者基本类型)都可以作为一个键或一个值;
Map 特点
Map
默认情况不包含任何键;只包含显式插入的键- 一个
Map
的键可以是任意值,包括函数、对象或任意基本类型 Map
中的键是有序的;因此,当迭代的时候,一个Map
对象以插入的顺序返回键值;Map
的键值对个数可以轻易地通过size
属性获取Map
是 可迭代的 的,所以可以直接被迭代- 在频繁增删键值对的场景下表现更好
- 没有元素的序列化和解析的支持
Map 方法
Map.prototype.clear() // 移除 Map 对象中所有的键值对
Map.prototype.delete(key) // 移除 Map 对象中指定的键值对,如果键值对存在并成功被移除,返回 true,否则返回 false
Map.prototype.get(key) // 返回与 key 关联的值,若不存在关联的值,则返回 undefined
Map.prototype.has(key) // 返回一个布尔值,用来表明 Map 对象中是否存在与 key 关联的值
Map.prototype.set(key, value) // 在 Map 对象中设置与指定的键 key 关联的值 value,并返回 Map 对象
Promise
Promise
对象用于表示一个异步操作的最终完成(或失败)及其结果值;
Promise
是异步编程的一种解决方案,将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,要是为了解决异步处理回调地狱(也就是循环嵌套的问题)而产生的
一个Promise
对象代表一个在这个 promise 被创建出来时不一定已知值的代理;它让你能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来;这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者
Promise 状态
- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝;
- 已兑现(fulfilled): 意味着操作成功完成;
- 已拒绝(rejected): 意味着操作失败;
Promise 特点
- 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
- 一旦状态改变(从 pending变为fulfilled和从pending变为rejected),就不会再变,任何
时候都可以得到这个结果
Promise 流程
Promise 方法
new Promise(resolve, reject) // 创建一个新的 Promise 对象;该构造函数主要用于包装还没有添加 promise 支持的函数
- resolve: 解析回调
- reject: 拒接回调(抛出异常)
Promise.prototype.catch(error) // 为 promise 实例化对象添加一个被拒绝状态的回调函数(解决异常),并返回一个新的 promise,若回调函数被调用,则兑现其返回值,否则兑现原来的 promise 兑现的值
Promise.prototype.then(response, error) // 为 promise 实例化对象添加被兑现和被拒绝状态的回调函数(如果不接受错误回调则传给catch),其以回调函数的返回值兑现 promise;若不处理已兑现或者已拒绝状态(例如,onFulfilled 或 onRejected 不是一个函数),则返回 promise 被敲定时的值;
Promise.prototype.finally() // 为 promise 添加一个回调函数,并返回一个新的 promise;这个新的 promise 将在原 promise 被兑现时兑现;而传入的回调函数将在原 promise 被敲定(无论被兑现还是被拒绝)时被调用;
Promise.resolve() // 方法返回一个以给定值解析后的 Promise 对象
Promise.reject() // 方法返回一个带有拒绝原因的 Promise 对象
Promise.all() // 接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个Promise实例, 那个输入的所有 promise 的 resolve 回调的结果是一个数组。这个Promise的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。它的 reject 回调执行是,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且 reject 的是第一个抛出的错误信息
Promise.race() // 方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。
内置对象
Math (数学)
Math
是一个内置对象,它拥有一些数学常数属性和数学函数方法;Math
不是一个函数对象
Math
用于Number
类型;它不支持BigInt
与其他全局对象不同的是,
Math
不是一个构造器;Math
的所有属性与方法都是静态的;引用圆周率的写法是Math.PI
,调用正余弦函数的写法是Math.sin(x)
,x
是要传入的参数;Math
的常量是使用 JavaScript 中的全精度浮点数来定义的
Math.ceil(x) // 返回大于一个数的最小整数,即一个数向上取整后的值
Math.floor(x) // 返回小于一个数的最大整数,即一个数向下取整后的值
Math.max([x[, y[, …]]]) // 返回零到多个数值中最大值
Math.min([x[, y[, …]]]) // 返回零到多个数值中最小值
Math.random() // 返回一个 0 到 1 之间的伪随机数,伪随机数在范围从0到小于1
function getRandomIntInclusive(min, max) { // 返回一个 min ~ max 之间的随机数
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
}
ES6+ 特性
ES5 和 ES6 的区别
ECMAScript5,即 ES5,是 ECMAScript 的第五次修订,于2009年完成标准化
ECMAScript6,即 ES6,是 ECMAScript 的第六次修订,于2015年完成,也称 ES2015 ES6 是继 ES5 之后的一次改进,相对于 ES5 更加简洁,提高了开发效率
变量常量
let // 块级作用域/值可变/暂时性死区
const // 块级作用域/值不可变/暂时性死区
模板字符串
`使用反引号包裹起来的就是模板字符串,在其中拼接变量十分容易${name}`
- 字符串格式化。将表达式嵌入字符串中进行拼接;用${}来界定
// 编码实现类似模板字符串
String = String.replace(/\$\{([^}]*)\}/g, function() {
return eval(arguments [1])
}
函数拓展
函数 length
函数的 length 属性将返回没有指定默认值的参数个数,rest参数也不会计入length属性,如果设置了默认值的参数不是尾参数,那么1ength属性也不再计入后面的参数了
函数 name
函数的 name 名字
- 如果将一个具名函数赋值给一个变量,则name属性都返回这个具名函数原本的名字
- Function构造函数返回的函数实例, name属性的值为anonymous
- bind返回的函数, name属性值会加上bound前缀
参数默认值
ES6为参数提供了默认值。在定义函数时便初始化了这个参数,以便在参数没有被传递进去时使用
function(num = 0, str = '') { ... }
箭头函数
() => {} // 箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this
- 在只有一个参数时,小括号可以省略;在函数体中只有一条语句且为返回值时可以省略大括号
- 箭头函数没有 arguments 对象
- 不能用作构造函数,这就是说不能够使用new命令,否则会抛出一个错误
- 不可以使用yield命令,因此箭头函数不能用作Generator函数
剩余参数
function(...arr) { ... } // 使用 arr 接收所有的参数,数组的形式
对象拓展
属性简写
let obj = { name } // 等同于 let obj = { name: name }
- ES6 提供了属性与属性值名称相同,即可直接简写
方法简写
let obj = { fn(){} } // 等同于 let obj = { fn: function() {} }
- ES6 提供了方法省略冒号和 function
- 简写方法不能作为构造函数使用
属性名表达式
let obj = { // 允许使用表达式创建对象属性,如果属性名表示式是一个对象变量则会自动转换对象字符串 [object Object]
[lastWord]: 'world' // let lassWored = 'last word'
['h'+'ello']() { ... }
}
注意:
属性名表达式与简洁表示法不能同时使用( const obj = { [foo] } ) // 报错
解构赋值
数组解构
[x, y] = [y, z]
let [x = 1, y = 2] = [0] // 设定初始值(注意:给予初始值操作在赋值操作之后,在此之前使用变量会直接报错)
对象解构
let { x, y } = { x: 10, y: 20} // 基础对象解构
let { x = 0, y = 0 } = { x: 10, y: 20} // 给解构对象赋予初始值
let { x: age, y: num } = { x: 10, y: 20} // 给解构对象重命名
let { x: age = 0, y: num = 0 } = { x: 10, y: 20} // 给解构对象暨重命名又赋予初始值
// 等号左侧的大括号中,写设置的形参以及它的初始值
// 等号右侧的大括号,表示传入的实参(右侧的大括号中什么都不用填,它是根据实际调用时传入的实参来实时变化的)
// 等号右侧的大括号,以及等号,可以进行忽略 ={}
function demo ({size = 'big', cords = {x: 0, y: 0}, radius = 0} ={}) {
console.log(size, cords, radius)
}
展开运算符
let [...arr] = [1,2,3,4,5] // 剩余参数
let { data:res } = {...obj} // 展开对象
async and await
可以用一种更简洁的方式写出基于
Promise
的异步行为,而无需刻意地链式调用promise
class
类是用于创建对象的模板;他们用代码封装数据以处理该数据
JS 中的类建立在原型上,但也具有某些语法和语义未与 ES5 类相似语义共享
类声明无法提升,本质上是 构造函数 的语法糖,使继承更加容易实现
extends // 继承
super // 调用父类构造函数
proxy 代理
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
术语:
handler
包含捕捉器(trap)的占位符对象,可译为处理器对象traps
这类似于操作系统中捕获器的概念target
被 Proxy 代理虚拟化的对象。它常被作为代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)
const proxyObject = new Proxy(target, handler) // 语法
- target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
- handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 proxyObject 的行为
const proxyObject = new Proxy(test, {
// * 代理对象的读取操作
get(obj, prop) {
return obj[prop]
},
// * 代理对象的修改操作
set(obj, prop, value) {
obj[prop] = value
},
// * 代理对象的删除操作
deleteProperty(obj, prop) {
delete obj[prop]
}
})
Reflect 反射
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers (en-US)的方法相同。
Reflect
不是一个函数对象,因此它是不可构造的
与大多数全局对象不同Reflect
并非一个构造函数,所以不能通过new 运算符对其进行调用,或者将Reflect
对象作为一个函数来调用。Reflect
的所有属性和方法都是静态的(就像Math
对象)
Reflect.set(target, 'name', 10) // * 设置对象属性值,返回布尔值表示是否成功
Reflect.get(target, 'name') // * 获取对象属性值,返回获取到的值
Reflect.deleteProperty(target, 'name') // * 删除对象属性,返回布尔值表示是否成功
开发中有什么使用 ES6 优化编码的方法
- 常用箭头函数来取代var self = this;的做法。
- 常用let取代var命令。
- 常用数组/对象的结构赋值来命名变量,结构更清晰,语义更明确,可读性更好。
- 在长字符串多变量组合场合,用模板字符串来取代字符串累加,能取得更好地效果和阅读体验。
- 用class类取代传统的构造函数,来生成实例化对象。
- 在大型应用开发中,要保持module模块化开发思维,分清模块之间的关系,常用import、export方法。
JS 特性要点
JS 执行步骤/生命周期
1. 创建阶段
- 确定 this 的值,称为
this binding
- (LexicalEnvironment)词法环境组件被创建
- 全局环境: 是一个没有外部环境的词法环境,其外部环境引用为nu11,有一个全局对象,this的值指向这个全局对象
- 函数环境: 用户在函数中定义的变量被存储在环境记录中,包含了arguments对象,外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境
- (VariableEnvironment)变量环境组件被创建
- 变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性在ES6中,词法环境和变量环境的区别在于前者用于存储函数声明和变量(let和const)绑定,而后者仅用于存储变量(var )绑定
- 语法分析
- 作用域规则确定
备注:
创建阶段,会在代码中扫描变量和函数声明,然后将函数声明存储在环境中但变量会被初始化为undefined (var声明的情况下)和保持uninitialized(未初始化状态)(使用let)和const声明的情况下)这就是变量提升的实际原因
2. 执行阶段
- 创建执行上下文: 简单的来说,执行上下文是一种对Javascript代码执行环境的抽象概念,也就是说只要有Javascript 代码运行,那么它就一定是运行在执行上下文中
- 全局执行上下文: 只有一个,浏览器的全局对象是 window ,this 指向这个全局对象
- 函数执行上下文: 存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的函数执行上下文,并创建一个私有作用域,函数内部声明的任何变量都不能再函数作用域外部使用(闭包除外)
- Eval 函数上下文: 指的是运行在eval函数中的代码,很少用而且不建议使用
- 执行函数代码
- 执行变量赋值,代码执行,如果Javascript 引擎在源代码中声明的实际位置找不到变量的值,那么将为其分配undefined值
- 执行变量赋值,代码执行,如果Javascript 引擎在源代码中声明的实际位置找不到变量的值,那么将为其分配undefined值
3. 回收阶段
- 垃圾回收,执行上下文出栈等待虚拟机回收执行上下文
JS 任务队列
“任务队列”是一个事件的队列(也可以理解成消息的队列),IO 设备完成一项任务,就在”任务队列”中添加一个事件,表示相关的异步任务可以进入”执行栈”了;JS 引擎线程读取”任务队列”,就是读取里面有哪些事件
所有任务可以分成两种,一种是同步任务(Synchronous tasks),另一种是异步任务(Asynchronous tasks);同步任务指的是,在 JS 引擎线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入JS 引擎线程、而进入事件触发线程管理的”任务队列“(Task Queue),所有同步任务执行完毕(此时 JS 引擎线程空闲),就会读取任务队列,将可运行的异步任务添加到 JS 引擎线程的执行栈中,开始执行
“任务队列
”中的事件,除了 IO 设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等);只要指定过回调函数,这些事件发生时就会进入”任务队列”,等待 JS 引擎线程读取
“任务队列
”是一个先进先出的数据结构,排在前面的事件,优先被主线程读取;主线程的读取过程基本上是自动的,只要执行栈一清空,”任务队列”上第一位的事件就自动进入 JS 引擎线程
具体来说,异步执行的运行机制如下;(同步执行也是如此,因为它可以被视为没有异步任务的异步执行
)
- 所有同步任务都在 JS 引擎线程上执行,形成一个执行栈(Execution Context Stack)
- 事件触发线程一个”任务队列”(Task Queue);只要异步任务有了运行结果,就在”任务队列”之中放置一个事件
- 一旦”执行栈”中的所有同步任务执行完毕,JS 引擎线程就会读取”任务队列”,看看里面有哪些事件;那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
- JS 引擎线程不断重复上面的第三步
备注:
只要 JS 引擎线程空了,就会去读取”任务队列”,这就是 JavaScript 的运行机制;这个过程会不断重复
Event Loop
JS 引擎线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为 Event Loop(事件循环)
JS 引擎线程运行的时候,产生堆(Heap)和栈(Stack),栈中的代码调用各种外部 API,它们在”任务队列”中加入各种事件(click,load,done);只要栈中的代码执行完毕,JS 引擎线程就会去读取”任务队列”,依次执行那些事件所对应的回调函数
- JavaScript是单线程,非阻塞的
- JavaScript在处理异步操作时,利用的是事件循环机制(event loop)
宏任务:
JavaScript 与宿主环境产生的回调微任务:
JavaScript 自身发起的回调
宏任务包括:
script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering;微任务包括:
Promises(Promise在实例化的过程中所执行的代码都是同步进行的), Object.observe, MutationObserver
宏任务、微任务的执行顺序:
- JS引擎在执行时优先将同步代码添加到执行线程中
- 异步任务(宏任务,微任务)放入事件触发线程处理
- 当对应事件符合触发条件时,事件触发线程将代码压入任务队列(区分宏任务队列/微任务队列)中,JS引擎线程从任务队列中读取任务并执行
说明: JS代码执行,先将同步代码放到执行进程中执行,遇到异步任务直接放到异步进程(事件触发线程)中,当事件满足触发条件后,异步进程将异步任务放到任务队列尾部(先微后宏),JS线程执行完同步代码就依次从任务队列中取任务放到执行栈中执行,在执行中遇到微任务、宏任务就又将任务交予异步进程,直到异步进程(事件触发线程)清空,代码执行完毕
async/await 举例
ES2016 中加入的 async/await 关键字,引入了对异步/等待的支持;允许开发者以同步的方式编写 Javascript 代码,但以异步的方式执行
在运行中,await 关键字将暂停 async 函数的执行,然后释放 JS 引擎线程,让 JS 引擎线程跳出当前 async 函数继续执行后面栈中的代码;而事件触发线程管理 await 的异步运行,当异步完成后加入到任务队列中;当本轮事件循环完成后,JS 引擎线程检查任务队列中是否有 await 的运行结果;如果已在任务队列中,就读取结果后跳回 async 函数继续执行
以《JS 高级程序设计第四版》中 11 章的代码举例,分析实际的运行流程,代码如下
async function foo() {
console.log(2);
console.log(await Promise.resolve(8));
console.log(9);
}
async function bar() {
console.log(4);
console.log(await 6);
console.log(7);
}
console.log(1);
foo();
console.log(3);
bar();
console.log(5);
// 执行结果如下所示:
1
2
3
4
5
8
9
6
7
结合线程的概念,运行流程分析如下:
- 执行 console.log(1), 输出
1
- 执行 foo() 函数,内部同步代码 输出
2
- await Promise.resolve(8) 语句在执行后交予事件处理线程处理并跳出此函数
- 执行 console.log(3) 输出
3
- 执行 bar() 函数,内部同步代码 输出
4
- await 6 语句在执行后交予事件处理线程处理并跳出此函数
- 执行 console.log(5) 输出
5
- 事件处理线程将处理完毕的任务放置到任务队列中
- 程序跳回 Promise.resolve(8) 处获取结果
8
- 执行后续代码输出
9
- 程序跳回 await 6 处获取结果
6
- 执行后续代码输出
7
eval()
函数会将传入的字符串当做 JavaScript 代码进行执行
eval()
的参数是一个字符串。如果字符串表示的是表达式,eval()
会对表达式进行求值。如果参数表示一个或多个 JavaScript 语句,那么eval()
就会执行这些语句。不需要用 eval()
来执行一个算术表达式:因为 JavaScript 可以自动为算术表达式求值
new 关键字执行步骤
- 在内存中创建一个新的空对象
- 让this指向这个新的对象
- 执行构造函数里面的代码,给这个新对象添加属性和方法
- 返回这个新对象(构造函数里不需要return)
this 绑定
this 绑定规则
- 默认绑定: 全局环境定义 fn 函数,内部使用 this 关键字,默认绑定 window (严格模式下 undefined)
- 隐式绑定: 函数作为某个对象的方法进行调用时,this 指向调用的那个上级对象
- new 绑定: 使用构造函数 new 实例化对象时, this 指向这个实例化的对象(特殊情况: 构造函数如果 return 了一个对象,那 this 就指向这个返回的对象)
- 显式绑定: 使用 Function 函数构造器显式的修改 this 的执行
fu() // 普通函数调用, this => window
obj.fn() // 对象方法函数调用, this => 对象
new Star() // 构造函数函数调用, this => 构造函数 new 出来的实例对象
btn.onclick = function() {} // 绑定事件函数调用, this => 函数的调用者
setTimeout(function() {}, timeout) // 定时器函数调用, this => window
(function(){})() // 立即执行函数调用, this => window
- 函数构造器this为指定的对象
// 严格模式下
fu() // 普通函数调用, this => undefined
obj.fn() // 对象方法函数调用, this => 对象
new Star() // 构造函数函数调用, this => 构造函数 new 出来的实例对象
btn.onclick = function() {} // 绑定事件函数调用, this => 函数的调用者
setTimeout(function() {}, timeout) // 定时器函数调用, this => window
(function(){})() // 立即执行函数调用, this => window
this 绑定优先级
显式绑定
==== 隐式绑定new 绑定
==== 隐式绑定new 绑定
==== 显式绑定- new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
JS 成员,属性区分
实例成员与静态成员
实例成员:
在构造函数内创建的属性与方法,只能由该构造函数实例化的对象来访问静态成员:
直接在构造函数本身身上直接添加的属性与方法,只能通过构造函数本身来访问(不需要实例化对象就可以访问)ES6:
通过关键词static
创建静态方法
公有属性与私有属性
公有属性/方法:
实例化对象能够访问的到的就是公有属性/方法私有属性/方法:
只能在 构造函数/类 内部访问的属性/方法
构造函数与原型链
JS 中所有的引用类型都具有对象特性
- 所有的构造函数,都有一个
prototype
属性(显示原型),属性值为一个对象(指向其自身的原型对象) - 所有的对象,都有一个
__proto__
属性(隐式原型),属性值为一个对象(指向其构造函数的原型对象) - 如果当前对象中没有某个属性或方法,那便会寻着原型对象的
__proto__
属性向上寻找(原型链)
class 与 ES5 构造函数的区别
- 类的内部定义的所有方法,都是不可枚举的
- ES6的class类必须用new命令操作,而ES5的构造函数不用new也可以执行
- ES6 的class类不存在变量提升,必须先定义class之后才能实例化,不像ES5中可以将 构造函数写在实例化之后
- ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面
- ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this
内存泄漏
内存泄漏概念
内存泄漏也称作"存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏)
JS 内存管理
JavaScript 是一种垃圾回收语言。垃圾回收语言通过周期性地检查先前分配的内存是否可达,帮助开发者管理内存。换言之,垃圾回收语言减轻了“内存仍可用”及“内存仍可达”的问题。两者的区别是微妙而重要的:仅有开发者了解哪些内存在将来仍会使用,而不可达内存通过算法确定和标记,适时被操作系统回收
Mark-and-sweep
大部分垃圾回收语言用的算法称之为 Mark-and-sweep 。算法由以下几步组成:
- 垃圾回收器创建了一个“roots”列表。Roots 通常是代码中全局变量的引用。JavaScript 中,“window” 对象是一个全局变量,被当作 root 。window 对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是垃圾);
- 所有的 roots 被检查和标记为激活(即不是垃圾)。所有的子对象也被递归地检查。从 root 开始的所有对象如果是可达的,它就不被当作垃圾。
- 所有未被标记的内存会被当做垃圾,收集器现在可以释放内存,归还给操作系统了。
备注:
现代的垃圾回收器改良了算法,但是本质是相同的:可达内存被标记,其余的被当作垃圾回收。
内存泄漏操作及解决方案
- 不经意在函数内部为一个未声明的变量赋值,使其成为了全局变量
解决方案:
在 JavaScript 文件头部加上'use strict'
,可以避免此类错误发生。启用严格模式解析 JavaScript ,避免意外的全局变量注意事项:
如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。与全局变量相关的增加内存消耗的一个主因是缓存。缓存数据是为了重用,缓存必须有一个大小上限才有用。高内存消耗导致缓存突破上限,因为缓存内容无法被回收 - 被遗忘的计时器或回调函数(观察者函数)
解决方案:
老的 IE 6 是无法处理循环引用的。如今,即使没有明确移除它们,一旦观察者对象变成不可达,大部分浏览器是可以回收观察者处理函数的注意事项:
老版本的 IE 是无法检测 DOM 节点与 JavaScript 代码之间的循环引用,会导致内存泄露。如今,现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法,已经可以正确检测和处理循环引用了。换言之,回收节点内存时,不必显式调用removeEventListener
了 - 脱离 DOM 的引用,使用字典(JSON 键值对)的方式保留了 DOM 元素的引用,此时 OG 无法检测到
解决方案:
在使用完毕后,显式将 字典 赋值 为 Null注意事项:
DOM 树内部或子节点的引用问题,子节点的引用只要存在,在垃圾回收时就不会回收包含此子节点引用的元素对象,所以在保存 DOM 元素引用的时候,要小心谨慎
- 闭包,可以访问其他函数内部变量的函数
解决方案:
在使用完毕后,显式将 变量 赋值 为 Null注意事项:
函数会分享闭包作用域,且迫使函数保留在内存中(防止被回收),垃圾回收器(GC)无法降低内存占用 - 循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)
解决方案:
避免此类操作
JS 垃圾回收机制
1. 标记清除(mark and sweep)
这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”
垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
2. 引用计数(reference counting)
在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。
引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间
在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的,也就是说只要涉及BOM及DOM就会出现循环引用问题
原生/内置/宿主对象
原生对象
独立于宿主环境 ECMAScript 实现提供的对象
包含:
Object, Function, Array, String, Boolean, Number, Date, RegExp, Error、EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError
内置对象
开发者不需要明确实例化的对象,已在内部进行了实例化
同样是独立于宿主环境
,ECMA-262只定义了两个内置对象,即 Global 和 Math
宿主对象
BOM 和 DOM 都是宿主对象,因为其对于不同的“宿主”环境所展示的内容不同;其实说白了就是,ECMAScript 官方未定义的对象都属于宿主对象,因为其未定义的对象大多数是自己通过 ECMAScript 程序创建的对象
== and === 的区别
===
三个等号称为等同符,当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时直接返回false;也就是说全等既要判断值也要判断类型是否相等
==
两个等号称为等值符,当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较
- undefined与null进行比较为true
- 基础类型与基础类型进行比较,会先转换为数值后再进行比较
- 数值与布尔值进行比较: 布尔值转换为数值后进行比较(false == 0 ; true == 1)
- 数值与字符串进行比较: 字符串转换为数值后进行比较
- 基础类型与引用类型进行比较: 引用类型调用 valueOf 获取原始值进行比较,与字符串调动 toString() 方法,与数值则再调用 Number() 方法
- 引用类型之间进行比较: 比较地址
- 任一操作数为 NaN: 直接返回 false
src and href 的区别
- **src (source)**指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求src资源时会将其指向的资源下载并应用到文档中,如JavaScript脚本,img图片和iframe等元素,当浏览器解析到该元素时,会
暂停其他资源的下载和处理
,直到将该资源加载、编译、执行完毕,类似于将所指向资源嵌入当前标签内 - **href (hypertext reference/超文本引用)**指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,如果我们在文档中添加那么浏览器会识别该文档为CSS文件,就会
并行下载资源
并且不会停止对当前文档的处理
类型转换
强制类型转换
// 数据类型构造函数
Number() // 数值(原始值)
String() // 字符串(原始值)
Boolean() // 布尔值(原始值)
// 转换方法
toString() // 转换为字符串
parseInt() // 转换为数值
parseFloat() // 转换为数值
隐式类型转换
+/-/*// -- 算数运算符,将运算结果转换为数值
+ -- 与字符串相加时变为级联运算符,将所有运算结果转换为String
比较,逻辑运算符 -- 运算时将值转换为Boolean进行计算
在转换时 undefined/null/0/-0/+0/''/NaN/false 视作为假
判断数据类型
1. typeof
可以区分原始数据类型(除null),和Function , 其他引用数据类型返回 Object,未定义变量返回 undefined
typeof 1 // number
typeof 'str' // string
typeof undefined // undefined
typeof null // object
typeof NaN // number
typeof function() {} // function
typeof Number(1) // number: 因为 Number / String 作为普通函数调用时,是将参数转换为原始数据类型(类似于类型转换),而不是默认作为一个构造函数
typeof new Number(1) // object
2. instanceof
用于区分引用数据类型(根据其构造函数),不能区分原始数据类型
obj instanceof Object // true
arr instanceof Array // true
fn instanceof Function // true
3. Object.prototype.toString.call()
最全面,可以判断所有数据类型的类型
Object.prototype.toString.call(1) // Number
Object.prototype.toString.call(obj) // Object
4. 判断是否数据
Array.isArray(arr) // true
Array.isArray(fn) // false
5. null 如何区分
value === null
JS 手写实现
JS 继承实现方案
父类构造函数
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal'
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!')
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food)
};
1. 原型链继承
核心:
将父类的实例作为子类的原型
function Cat() { ... }
Cat.prototype = new Animal()
Cat.prototype.name = 'cat
// Test Code
let cat = new Cat()
console.log(cat instanceof Animal) // true
console.log(cat instanceof Cat) // true
特点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
缺点:
- 要想为子类新增原型属性和方法,必须要在
new Animal()
这样的语句之后执行 - 无法实现多继承
- 来自原型对象的所有属性被所有实例共享(来自原型对象的引用属性是所有实例共享的)
- 创建子类实例时,无法向父类构造函数传参
2. 构造继承
核心:
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat()
console.log(cat instanceof Animal) // false
console.log(cat instanceof Cat) // true
特点:
- 解决了 1 中,子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call多个父类对象)
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
3. 实例继承
核心:
为父类实例添加新特性,作为子类实例返回
function Cat(name){
let instance = new Animal();
instance.name = name || 'Tom';
return instance;
}
// Test Code
var cat = new Cat();
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false
特点:
- 不限制调用方式,不管是
new 子类()
还是子类()
,返回的对象具有相同的效果
缺点:
- 实例是父类的实例,不是子类的实例
- 不支持多继承
4. 拷贝继承
function Cat(name){
var animal = new Animal()
for(var p in animal){
Cat.prototype[p] = animal[p]
}
this.name = name || 'Tom'
}
// Test Code
var cat = new Cat();
console.log(cat instanceof Animal) // false
console.log(cat instanceof Cat) // true
特点:
- 支持多继承
缺点:
- 效率较低,内存占用高(因为要拷贝父类的属性)
- 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
5. 组合继承
核心:
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){
Animal.call(this);
this.name = name || 'Tom'
}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat
// Test Code
var cat = new Cat();
console.log(cat instanceof Animal) // true
console.log(cat instanceof Cat) // true
特点:
- 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
缺点:
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
6. 寄生组合继承
核心:
通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function Cat(name){
Animal.call(this)
this.name = name || 'Tom'
}
(function(){
// 创建一个没有实例方法的类
let Super = function(){}
Super.prototype = Animal.prototype
// 将实例作为子类的原型
Cat.prototype = new Super()
})()
Cat.prototype.constructor = Cat; // 需要修复下构造函数
// Test Code
var cat = new Cat()
console.log(cat instanceof Animal) // true
console.log(cat instanceof Cat) // true
特点:
- 堪称完美
缺点:
- 实现较为复杂
7. ES6 class实现
// 创建一个父类
class Parent {
constructor(name, age) {
this.name = name
this.age = age
}
sayName() {
console.log(this.name)
}
}
// 创建一个子类,使用 extends 实现继承,super指向父类的构造函数
class Child extends Parent {
constructor(name, age, gender) {
super(name, age)
this.gender = gender
}
sayGender() {
console.log(this.gender)
}
}
// 实例化对象
const ming = new Child('ming', 18, '男')
Array 功能方法实现
Array 数组扁平化(ES flat 已实现)
/**
* * 1、数组扁平化处理,利用递归循环遍历判断是否为数组并展开
*
* @param {Array} arr
* @return {Array}
*/
function flatten1(arr) {
// 创建空数组用于接收数据并返回
let result = []
// 遍历数组
arr.forEach((item) => {
if (Array.isArray(item)) {
result = result.concat(flatten1(item))
} else {
result = result.concat(item)
}
})
// 遍历中用于终止条件返回处理后的数据
return result
}
/**
* * 2、数组扁平化处理,利用 some and concat 方法,遍历数组判断是否还有子数组,如有则展开
*
* @param {Array} arr 需要扁平化处理的数组
* @return {Array} 处理过后的数组
*/
function flatten2(arr) {
// 创建数组用于返回数据
let result = [...arr]
// 判断数据是否有子数组,如有的话使用 concat 展开数组
while (result.some((item) => Array.isArray(item))) {
result = [].concat(...result) // 合并数组(...+数组)
}
// 返回数据
return result
}
// 3. 使用 Array.prototype.flat([depth]) 方法指定深度递归遍历数组,返回新数组
Array 数组随机排序
/**
* * 1. 随机排序数组内元素
*
* @param {Array} arr
* @return {Array}
*/
function randSort(arr) {
let result = []
while (arr.length > 0) {
let deleteIndex = parseInt(Math.random() * arr.length)
result.push(arr[deleteIndex])
arr.splice(deleteIndex, 1)
}
return result
}
/**
* * 2. 随机排序数组内元素
*
* @param {Array} arr
*/
function randSort(arr) {
arr.sort(function(){
return Math.random() - 0.5
})
}
Array 数组去重
/**
* ! 数组去重方法封装实现
* * 1、unique1(arr) 利用 forEach() and indexOf 双重循环去重(效率低)
* * 2、unique2(arr) 利用 forEach() and Obj容器 去重(一重for循环,效率高)
* * 3、unique3(arr) 利用 Set 内置对象去重(使用便捷)
*/
/**
* * 1、数组去重,利用 Array.forEach() 和 Array.indexOf() 方法来遍历数组去重
* ! 注意:该方法效率低,本质上就是双重 for 循环
*
* @param {Array} arr
* @return {Array}
*/
function unique1(arr) {
const result = []
// 去重处理
arr.forEach((item) => {
result.indexOf(item) === -1 ? result.push(item) : ''
})
return result
}
/**
* * 2、数组去重,利用 Array.forEach() 和 对象容器的方法来遍历数组去重,obj[item] 用来判断值是否存在于对象中,不存在则取反进行赋值操作,有则跳过来实现去重
* ! 注意:只需要一层 for 循环,提高了效率
*
* @param {Array} arr
* @return {Array}
*/
function unique2(arr) {
const result = []
const obj = {}
// 去重处理
arr.forEach((item) => {
if (!obj[item]) {
obj[item] = true
result.push(item)
}
})
return result
}
/**
* * 3、数组去重,利用 set 内置对象实现,使用简便
*
* @param {Array} arr
* @return {Array}
*/
function unique3(arr) {
return [...new Set(arr)]
}
Array 删除指定值
/**
* ! 数组删除特定值方法封装实现
* * 1、pull(arr, ...args) 删除数组特定值(接收参数列表)
* * 2、pullAll(arr, values) 删除数组特定值(接收数组、间接调用 pull 方法)
*/
/**
* * 1、删除数组特定值,将符合条件的值放入返回数组中,将原数组中特定值进行删除
*
* @param {Array} arr
* @param {...any} args
* @return {Array}
*/
function pull(arr, ...args) {
const result = []
for (let i = 0; i < arr.length; i++) {
if (args.includes(arr[i])) {
// 将需要删除的值压入返回数组中
result.push(arr[i])
// 删除符合条件的值,改变原数组
arr.splice(i, 1)
// i自减,splice 方法删除数组元素后,数组索引减 1,需自减
i--
}
}
return result
}
/**
* * 2、删除数组特定值,将数组展开运算符后传入 pull 中进行计算
*
* @param {Array} arr
* @param {Array} values
* @return {Array}
*/
function pullAll(arr, values) {
return pull(arr, ...values)
}
Array 删除截取指定值
/**
* ! 数组截取特定数组方法封装实现
* * 1、drop(arr, size) 截取指定索引条件数组(从左至右)
* * 2、dropRight(arr, size) 截取指定索引条件数组(从右至左)
*/
/**
* * 1、从左至右,截取 size 指定索引之后的数组
*
* @param {Array} arr
* @param {Number} size
* @return {Array}
*/
function drop(arr, size) {
return arr.filter((value, index) => index >= size)
}
/**
* * 2、从右至左,截取 size 指定索引之前的数组
*
* @param {Array} arr
* @param {Number} size
* @return {Array}
*/
function dropRight(arr, size) {
return arr.filter((value, index) => index < arr.length - size)
}
Array 差集
/**
* ! 数组差集方法封装实现
* * difference(arr1, arr2) 利用 filter and includes 方法对数组遍历并判断是否有相同项,如有则取反以获取差集
*
* @param {Array} arr1
* @param {Array} arr2
* @return {Array}
*/
function difference(arr1, arr2) {
if (arr1.length === 0) {
return []
}
if (arr2.length === 0) {
return arr1.slice()
}
// 遍历判断 arr2 中是否有 item 项,如有则取反,实现差集
const result = arr1.filter((item) => !arr2.includes(item))
return result
}
Array 数组分块
/**
* ! 数组分块方法封装实现
* * chunk(arr, size = 1) 以 size 分块数组,利用临时数组存储数据,以分块的形式压入返回数组中
*
* @param {Array} arr
* @param {number} [size=1] 分块区分
* @return {Array}
*/
function chunk(arr, size = 1) {
// 初始判断
if (arr.length === 0) {
return []
}
// 初始数组
const result = []
let tmp = []
// 遍历操作
arr.forEach((item) => {
// 压入数组
if (tmp.length === 0) {
result.push(tmp)
}
// 将数据项压入临时数组(引用数据类型)
tmp.push(item)
// 数组分块区分,清空临时数组(重新赋值),以便下次压入
if (tmp.length === size) {
tmp = [] // 重新指向一个新的数组对象
}
})
return result
}
Function 函数构造器封装实现
/**
* ! 对函数相关的一些方法进行封装实现
* * 1、call(Fn, obj, ...args) 改变 Fn this 指向并执行
* * 2、apply(Fn, obj, args) 改变 Fn this 指向并执行(参数为数组)
* * 3、bind(Fn, obj, ...args) 改变 Fn this 指向并返回函数
*/
/**
* * 1、Function.prototype 中 call() 函数的封装实现
* ? 此方法将传入函数拷贝一份给传入对象,使其在 this 执行上实现 this 间接指向 obj,调用 obj 零时函数后删除零时方法,返回函数执行后的值
*
* @param {Function} Fn 需要改变this执行被调用的函数
* @param {Object} obj this执行的对象
* @param {*} args 传递的参数列表
* @return {*} this执行改变后 Fn 函数调用后的值
*/
function call(Fn, obj, ...args) {
// 判断 obj 为 undefined 或 null 时将 obj 设置为全局对象
if (obj === undefined || obj === null) {
obj = globalThis
}
// 将 Fn 挂载到 obj 临时方法上
obj.temp = Fn
// 调用克隆出临时方法计算结果,因为临时方法是 obj 身上的一个属性,所以其 this 指向 obj 对象
let result = obj.temp(...args)
// 调用完成删除 temp 临时方法
delete obj.temp
// 返回计算结果
return result
}
/**
* * 2、Function.prototype 中 apply() 函数的封装实现
* ? 此方法将传入函数拷贝一份给传入对象,使其在 this 执行上实现 this 间接指向 obj,调用 obj 零时函数后删除零时方法,返回函数执行后的值(与 call 唯一不同就是 args 列表为一个数组)
*
* @param {Function} Fn 需要改变this执行被调用的函数
* @param {Object} obj this执行的对象
* @param {Array} args 传递的参数列表(数组形式)
* @return {*} this执行改变后 Fn 函数调用后的值
*/
function apply(Fn, obj, args) {
// 判断 obj 为 undefined 或 null 时将 obj 设置为全局对象
if (obj === undefined || obj === null) {
obj = globalThis
}
// 将 Fn 挂载到 obj 临时方法上
obj.temp = Fn
// 调用克隆出临时方法计算结果,因为临时方法是 obj 身上的一个属性,所以其 this 指向 obj 对象
let result = obj.temp(...args)
// 调用完成删除 temp 临时方法
delete obj.temp
// 返回计算结果
return result
}
/**
* * 3、Function.prototype 中 bind() 函数的封装实现
* ? 此方法将传入函数拷贝一份给传入对象,使其在 this 执行上实现 this 间接指向 obj,调用 obj 零时函数后删除零时方法,返回函数执行后的值
* ! 注意:此方法在调用时不会立即执行,而是返回一个函数对象 call,并在后续函数对象调用时还能传递参数,按照先后顺序,最后 call 函数返回值,实现功能
*
* @param {Function} Fn 需要改变this执行被调用的函数
* @param {Object} obj this执行的对象
* @param {*} args 传递的参数列表
* @return {*} 返回一个函数,函数中调用 call 方法实现功能
*/
function bind(Fn, obj, ...args) {
// 返回一个新的函数
return function (...args2) {
// 执行 call 函数
call(Fn, obj, ...args, ...args2)
}
}
柯里化函数
柯里化(Currying),把接受多个参数的函数转换成接受一个单一参数的函数
- 柯里化突出一种重要思想:降低适用范围,提高适用性
- 柯里化的三个作用和特点:参数复用、提前返回、延迟执行
- 柯里化是闭包的一个典型应用,利用闭包形成了一个保存在内存中的作用域,把接收到的部分参数保存在这个作用域中,等待后续使用。并且返回一个新函数接收剩余参数
/**
* ! 通用柯里化函数
*
* @param {*} fn 需要柯里化的函数
* @param {*} args 传递的参数
* @return {*} 参数一致直接执行,否则返回柯里化函数
*/
function currying(fn, ...args) {
// 当传入的参数和函数需求参数相同或多于时,直接执行函数
if (args.length >= fn.length) return fn(...args)
// 闭包,柯里化函数应用
return (...remaining) => {
// 继续返回此函数,待参数一致时再执行
currying(fn, ...args, ...remaining)
}
}
深、浅拷贝
JavsScript 变量包含两种不同数据类型的值:
基本数据类型
和引用数据类型
基本数据类型: 标识符-值 存储在栈内存中
引用数据类型: 标识符存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值
浅拷贝
拷贝一层,引用类型数据任然拷贝的是地址的引用
// 1、浅克隆引用对象(利用 es6 扩展运算符),基础类型直接返回
function clone1(target) {
if (typeof target === 'object' && target !== null) {
// 利用 es6 扩展运算符返回容器对象
if (Array.isArray(target)) {
return [...target]
} else {
return { ...target }
}
} else {
return target
}
}
// 2、浅克隆引用对象,返回容器对象,类型类型直接返回
function clone2(target) {
if (typeof target === 'object' && target !== null) {
const result = Array.isArray(target) ? [] : {}
for (const key in target) {
// 判断数据上是否有 key 这个属性
if (target.hasOwnProperty(key)) {
// 将属性压入到 result 容器中
result[key] = target[key]
}
}
return result
} else {
return target
}
}
深拷贝
递归拷贝多层,将引用数据类型解析到基础类型时再进行拷贝
/**
* * 深拷贝(乞丐版),利用 JSON 将数据变成 JSON 格式再根据 JSON 格式创建 JS 数据完成拷贝
* ! 缺点:1、无法拷贝方法;2、无法解决循环引用
*
* @param {*} target
* @return {*}
*/
function deepJsonClone(target) {
// 使用 JS 数据创建 JSON 数据
let str = JSON.stringify(target)
// 使用 JSON 格式数据创建 JS 数据
let data = JSON.parse(str)
return data
// 简写 return JSON.parse(JSON.stringify(target))
}
/**
* * 深拷贝(完整版),在原有基础上提升性能
* ? 数组:while | for | forEach() 优于 for-in | keys()&forEach()
* ? 对象:for-in 与 keys()&forEach() 差不多
*
* @param {*} target
* @return {*}
*/
function deepClone(target, map = new Map()) {
// 判断是否为引用数据类型
if (typeof target === 'object' && target !== null) {
// 判断值是否已被拷贝,如是,则直接返回此值
let cache = map.get(target)
if (cache) {
return cache
}
let isArray = Array.isArray(target)
const result = isArray ? [] : {}
// 创建 map 数据容器以存放用于判断的值
map.set(target, result)
if (isArray) {
target.forEach((item, index) => {
result[index] = deepClone(item, map)
})
} else {
Object.keys(target).forEach((key) => {
result[key] = deepClone(target[key], map)
})
}
return result
} else {
return target
}
}
节流、防抖
防抖和节流都是为了解决
短时间内大量触发
某函数而导致的性能问题
,比如触发频率过高导致的响应速度跟不上触发频率,出现延迟,假死或卡顿的现象
节流
在 wait 时间内,callback 只能执行一次(在指定时间内执行一次)
/**
* * 1、事件函数 节流(在 wait 时间内,callback 只能执行一次) 实现
* ? 此函数利用时间戳,判断事件触发的时间间隔,如果触发时间间隔少于设置的时间间隔,则不允许事件触发;以减少频繁触发事件的触发频率,提升系统效率
* ! 注意:返回的函数中通过 call 函数指定调用者, 如不指定,则由 window 全局变量调用此函数
*
* @param {Function} callback 回调函数
* @param {Number} wait 允许调用的时间间隔
* @return {Function} 函数对象,包括函数调用并将间隔判断值作更新
*/
function throttle(callback, wait) {
// 间隔判断值
let start = 0
// 返回值为一个函数
return function (e) {
// 函数执行时时间
let now = Date.now()
// 触发时间判断
if (now - start >= wait)
callback.call(this, e)
start = now
}
}
}
docement.qs('#app').scroll = throttle(function(e){
console.log(e)
},500)
防抖
在一次函数调用后,延迟 time 毫秒后调用 callback ,重置触发则重置延迟毫秒数(在满足规定时间要求后执行一次)
/**
* * 2、事件函数 防抖(在一次函数调用后,延迟 time 毫秒后调用 callback ,重置触发则重置延迟毫秒数) 实现
* ? 此函数利用定时器,判断事件触发的时间间隔,如果触发时间间隔在延迟毫秒事件内重置触发,则重置事件触发定时器;以减少规定时间内,触发事件的触发频率,提升系统效率
* ! 注意:返回的定时器函数中通过 call 函数指定调用者, 如不指定,则由 window 全局变量调用此函数
*
* @param {Function} callback 回调函数
* @param {Number} time 允许调用的时间间隔
* @return {Function} 函数对象,包括函数调用并将间隔判断值作更新
*/
function debounce(callback, time) {
// 设置触发判断值
let timeId = null
// 返回值为一个函数
return function (e) {
// timeId只要改变,在执行至此时关闭上一个定时器,重置定时器,实现防抖
if (timeId !== null) {
clearTimeout(timeId)
}
timeId = setTimeout(() => {
// 执行回调函数
callback.call(this, e)
// 执行成功重置触发判断值,如果执行成功后不重置触发判断值,则没触发情况下 if !== null 判断无用
timeId = null
}, time)
}
}
判断两个对象的值是否相等
/**
* ! 对象是否相等方法封装实现
* * isObjectValueEqual(obj1, obj2) 将给定的对象参数进行是否相等比较
*
* @param {Object} obj1
* @param {Object} obj2
* @return {Boolean}
*/
function isObjectValueEqual(obj1, obj2) {
// 1. 判断地址是否相等,是则 return true
if (obj1 === obj2) {
return true
}
// 获取对象本身所有属性键值
let obj1Proper = Object.getOwnPropertyNames(obj1)
let obj2Proper = Object.getOwnPropertyNames(obj2)
// 2. 判断键值数组长度是否相等
if (obj1Proper.length !== obj2Proper.length) {
return false
}
// 3. 遍历键值判断是否相等
Object.keys(obj1).forEach(key => {
// 3.1 判断 obj2 中是否存在此键,不存在直接 return false
if (obj2.hasOwnProperty(key)) {
// 3.2 判断 obj1[key] 是否为对象,是则递归判断,不是则判断值是否相等,不等于直接 return false
if (typeof obj1[key] === 'object') {
// 3.3 递归调用返回结果如果为 false 则取反 return false
if (!isEquality(obj1[key],obj2[key])) {
return false
}
} else if (obj1[key] !== obj2[key]){
return false
}
} else {
return false
}
})
// 判断不为 false 则返回 true
return true
}
手写 Promise
// 状态常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
let _this = this // 当前对象 this
this.status = PENDING // 状态
this.value = undefined // 成功结果
this.reason = undefined // 失败原因
this.onFulfilled = [] // 成功的回调
this.onRejected = [] // 失败的回调
// 修改为成功状态
function resolve(value) {
setTimeout(() => {
// 只有状态为待定状态才能更改状态
if (_this.status === PENDING) {
_this.status = FULFILLED // 改变状态
_this.value = value // 保存结果
_this.onFulfilled.forEach(fn => fn()) // 调用存入的成功回调
}
}, 0);
}
// 修改为失败状态
function reject(reason) {
setTimeout(() => {
// 只有状态为待定状态才能更改状态
if (_this.status === PENDING) {
_this.status = REJECTED // 改变状态
_this.reason = reason // 保存失败原因
_this.onRejected.forEach(fn => fn()) // 调用存入的失败回调
}
}, 0);
}
// 执行传入函数
try {
executor(resolve, reject) // 此方法调用可能失败所以需要捕获异常
} catch (error) {
reject(error) // 改变状态为 rejected
}
// ** Promise 方法(原型实现)
/**
* * then(onFulfilled, onRejected) 传入成功回调和失败回调,此回调会在 promise 状态变更方法执行时被调用
* * resolvePromise(promise2, x, resolve, reject) 设置 then 函数返回 promise 对象状态
* * catch(onRejected) 静态 reject 方法
* * resolve(value) 静态 resolve 方法
* * reject(reason) 静态 reject 方法
* * stop() 停止 promise 方法执行
* * all(promise) 传入一个 promise 数组,执行全部 promise,只有全部 resolve 的情况下返回结果数组,其余情况下返回 reject
* * race(promise) 传入一个 promise 数组,执行全部 promise,只要有一个 resolve 的情况下返回结果,其余情况下返回 reject
*/
// ? then(onFulfilled, onRejected) 传入成功回调和失败回调,此回调会在 promise 状态变更方法执行时被调用
Promise.prototype.then = function (onFulfilled, onRejected) {
let _this = this // 保存当前对象 this
// promise 规范规定 then 方法参数不为函数时值向下传递(值穿透)
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : resole => { throw resole }
// 创建返回的 promise 对象
let promise2 = new Promise((resole, reject) => {
if (_this.status === FULFILLED) {
setTimeout(() => {
try {
const x = onFulfilled(_this.value) // 获取函数调用返回值以更新当前状态
this.resolvePromise(Promise2, x, resole, reject) // 更改返回 promise 对象状态
} catch (error) {
reject(error)
}
}, 0)
}
if (_this.status === REJECTED) {
setTimeout(() => {
try {
const x = onRejected(_this.reason) // 获取函数调用返回值以更新当前状态
this.resolvePromise(promise2, x, resole, reject) // 更改返回 promise 对象状态
} catch (error) {
reject(error)
}
}, 0)
}
// 压入改变状态方法
if (_this.status === PENDING) {
_this.onFulfilled.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(_this.value)
this.resolvePromise(promise2, x, resole, reject)
} catch (error) {
reject(error)
}
}, 0)
})
_this.onRejected.push(() => {
setTimeout(() => {
try {
const x = onRejected(_this.reason)
this.resolvePromise(promise2, x, resole, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise2 // 最终返回一个 promise 对象
}
// ? resolvePromise(promise2, x, resolve, reject) 设置 then 函数返回 promise 对象状态
Promise.prototype.resolvePromise = function(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Chaining cycle'))
}
if ((x && typeof x === 'object') || typeof x === 'function') {
try {
let then = x.then
if (typeof then === 'function') {
then.call(
x,
y => {
this.resolvePromise(promise2, y, resolve, reject)
},
r => {
reject(r)
}
)
} else {
resolve(x)
}
} catch (e) {
reject(e)
}
} else {
resolve(x)
}
}
// ? catch(onRejected) 静态 reject 方法
Promise.prototype.catch = function(onRejected) {
return new Promise(null, onRejected)
}
// ? resolve(value) 静态 resolve 方法
Promise.prototype.resolve = function(value) {
return new Promise((resolve, reject) => {
resolve(value) // 更改为成功状态
})
}
// ? reject(reason) 静态 reject 方法
Promise.prototype.reject = function(reason) {
return new Promise((resolve, reject) => {
reject(reason) // 更改为错误状态
})
}
// ? stop() 停止 promise 方法执行
Promise.prototype.stop = function() {
return new Promise() // 返回一个新的 promise 对象,因为对象的状态是 pending 所有后续成功/失败回调就无法执行
}
// ? all(promise) 传入一个 promise 数组,执行全部 promise,只有全部 resolve 的情况下返回结果数组,其余情况下返回 reject
Promise.prototype.all = function(promise) {
return new Promise((resolve, reject) => {
if (promise.length === 0) {
return resolve([])// 传入 promise 数组长度为 0,直接返回
}
const result = [] // 返回容器
// 遍历 promise 数组
promise.forEach((item, index) => {
item.then((value) => {
result.push(value) // 压入成功的数据
},(error) => {
reject(error) // 只要失败就返回错误
return
})
})
resolve(result) // 返回结果数组
})
}
// ? race(promise) 传入一个 promise 数组,执行全部 promise,只要有一个 resolve 的情况下返回结果,其余情况下返回 reject
Promise.prototype.race = function(promise) {
return new Promise((resolve, reject) => {
if (promise.length === 0) {
return resolve()// 传入 promise 数组长度为 0,直接返回
}
// 遍历 promise 数组
promise.forEach((item, index) => {
item.then((value) => {
resolve(result) // 返回结果
},(error) => {
reject(error) // 只要失败就返回错误
return
})
})
})
}
}
扁平化数据转换树形结构
/**
* * 扁平化数据转换树形结构数据,利用递归进行实现
*
* @param {Array} arr 需要转换树形结构的数据数组
* @return {Array} 处理过后的数组
*/
function flatTransformTree(arr,root) {
const result = [] // 创建数据容器
// 遍历数组
arr.forEach(item => {
// 给定条件判断
if (item.pid === root) {
// 如果满足压入返回容器
result.push(item)
// 递归寻找子节点(改变 root 为当前项 ID 查找子项)
const currentItemChildren = flatTransformTree(arr,item,id)
// 如果有子项则将子项添加到当前项 children 属性上
if (currentItemChildren.length) item.children = currentItemChildren
}
})
return result // 返回数据容器
}
正则表达式
正则表达式概述
正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。这些模式被用于
RegExp
的exec
和test
方法,以及String
的match
、matchAll
、replace
、search
和split
方法
正则表达式语法
普通字符
[ABC] ABC ABC中任一字符
[^ABC] 除 ABC 的所有字符
[A-Z] A-Z 的任意字符
. 匹配除(\n \r)外的任意字符,相当于[^\n\r]
[\s] 匹配所有空白符
[\S] 匹配所有非空白符
[\S\s] 匹配所有字符
\w 匹配字母、数字、下划线。等价于 [A-Za-z0-9_]
\W 匹配非字母、数字、下划线。等价于 [^A-Za-z0-9_]
非打印字符
\cx 匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。
\f 匹配一个换页符。等价于 \x0c 和 \cL。
\n 匹配一个换行符。等价于 \x0a 和 \cJ。
\r 匹配一个回车符。等价于 \x0d 和 \cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。注意 Unicode 正则表达式会匹配全角空格符。
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
\t 匹配一个制表符。等价于 \x09 和 \cI。
\v 匹配一个垂直制表符。等价于 \x0b 和 \cK。
特殊字符
$ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 '\n' 或 '\r'。要匹配 $ 字符本身,请使用 \$。
( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 \( 和 \)。
* 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。
+ 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。
. 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。
[ 标记一个中括号表达式的开始。要匹配 [,请使用 \[。
? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。
\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, 'n' 匹配字符 'n'。'\n' 匹配换行符。序列 '\\' 匹配 "\",而 '\(' 则匹配 "("。
^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,当该符号在方括号表达式中使用时,表示不接受该方括号表达式中的字符集合。要匹配 ^ 字符本身,请使用 \^。
{ 标记限定符表达式的开始。要匹配 {,请使用 \{。
| 指明两项之间的一个选择。要匹配 |,请使用 \|。
限定符
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 、 "does" 中的 "does" 、 "doxy" 中的 "do" 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。
贪婪与非贪婪
通过在 *、+ 或 ? 限定符之后放置 ?,该表达式从"贪婪"表达式转换为"非贪婪"表达式或者最小匹配
定位符
^ 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与 \n 或 \r 之后的位置匹配。
$ 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与 \n 或 \r 之前的位置匹配。
\b 匹配一个单词边界,即字与空格间的位置。
\B 非单词边界匹配。
运算符优先级(从高到低)
运算符 | 描述 |
---|---|
\ | 转义符 |
(), (?😃, (?=), [] | 圆括号和方括号 |
*, +, ?, {n}, {n,}, {n,m} | 限定符 |
^, $, \任何元字符、任何字符 | 定位点和序列(即:位置和顺序) |
| | 替换,“或"操作 字符具有高于替换运算符的优先级,使得"m|food"匹配"m"或"food”。若要匹配"mood"或"food",请使用括号创建子表达式,从而产生"(m|f)ood"。 |
正则表达式标志
标志 | 描述 |
---|---|
g | 全局搜索。 |
i | 不区分大小写搜索。 |
m | 多行搜索。 |
s | 允许 . 匹配换行符。 |
u | 使用 unicode 码的模式进行匹配。 |
y | 执行“粘性 (sticky )”搜索,匹配从目标字符串的当前位置开始。 |