JavaScipt语法
运算符
??
Nullish 合并运算符
空值合并运算符。如果第一个参数不为 null
或 undefined
。返回第一个参数。否则,返回第二个参数。
作用:为了弥补
||
运算符的缺陷。- 换句话说,
||
无法区分false
、0
、空字符串""
和null/undefined
。它们都一样 —— 假值(falsy values)。如果其中任何一个是||
的第一个参数,那么我们将得到第二个参数作为结果。
不过在实际中,我们可能只想在变量的值为
null/undefined
时使用默认值。也就是说,当该值确实未知或未被设置时。- 换句话说,
1 | result = a ?? b; |
!!
等价于 Boolean()
是一种用于将任意值转换为其对应的布尔值的一种简便方式。它实际上是两个逻辑非运算符 !
的连用。
!!a
等价于 Boolean(a)
第一个
!
运算符:将其后的值转换为布尔类型。
如果值是一个“假值”(例如
null
、undefined
、0
、空字符串""
、NaN
或false
),则第一个!
运算符将其转换为true
。如果值是一个“真值”(非上述假值),则第一个
!
运算符将其转换为false
。
第二个
!
运算符:将上一步得到的布尔值再次取反。
这样做的结果就是,无论初始值是什么,最终的结果都会是
true
或false
。
这种双重否定的操作常见于需要明确判断一个值是否为真或为假的情况中。它不会改变原始值的类型,而是产生一个布尔值作为结果。
**
指数运算符
**
指数运算符是ES7新特性(2016)
a ** b
,它与 Math.pow(a, b)
相同。
数组(列表)
初始化
let arr = new Array(n).fill(0);
初始化一个长度为n,全0的数组1
2let array = [1,1,1,1,1,1];
array.fill(0,1,3); //从下标1到下标3之前将值改变为0
for 循环
for...in
和 for...of
是常见的循环,当然还有 forEach()
for...in
和 for...of
区别
适用对象类型:
- for…of:主要用于遍历可迭代对象(例如数组、字符串、Set、Map等),可以获取到迭代对象的值。
- for…in:主要用于遍历对象的属性(包括原型链上的属性,尤其注意数组情况),可以获取到键名(属性名)。【string 类型】
遍历顺序:
- for…of:按照对象的顺序迭代,一般用于遍历有序集合。
- for…in:无法保证属性的遍历顺序,可能会导致属性的无序输出。
迭代内容:
- for…of:迭代的是对象的值本身,例如数组中的元素、字符串中的字符等。
- for…in:迭代的是对象的属性名,需要通过属性名访问属性值。【string 类型】
支持情况:
- for…of:在 ES6 中引入,适用于可迭代对象,如数组、字符串等。
- for…in:在早期版本的 JavaScript 中就存在,用于遍历对象的属性。但是不适用于数组等可迭代对象,因为它会遍历出额外的属性。
性能:
二者都用于处理稀疏数组
1
2
3
4
5
6
7
8
9
10
11
12
13const sparseArray = [1, , , 4]; // 稀疏数组,其中两个元素是未定义的
for (const value of sparseArray) {
console.log(value);
}
// Output:
// 1
// 4
for (const index in sparseArray) {
console.log(index, sparseArray[index]);
}
// Output:
// 0 1
// 3 4for…of:通常性能比 for…in 更好,因为它不需要遍历原型链上的属性。
forEach()
方法
forEach()
是数组的一个方法,用于遍历数组中的每个元素。它接受一个回调函数作为参数,该函数会对数组的每个元素执行一次。
语法
1 | array.forEach((element, index, array) => { |
示例
1 | const arr = [1, 2, 3, 4, 5]; |
特点
- 简洁性: 语法简洁,代码更具可读性。
- 不支持提前退出: 你不能使用
break
或continue
退出forEach()
循环,这可能在某些情况下限制了它的使用。 - 适用于数组: 仅适用于数组,不适用于对象或其他数据结构。
返回值
在 JavaScript 中,函数可以返回对象,对象是相关数据和函数(通常称为属性和方法)的集合。
1 | function createPerson(name, age) { |
错误处理
在 JavaScript 中,错误处理主要通过使用 throw
语句和 try...catch
块来实现。使用 throw
语句允许你创建自定义错误消息,这对于调试代码非常有用。根据你想要实现的目标,可以使用不同的方式使用 throw
语句。
抛出字符串:
你可以在 JavaScript 中抛出一个字符串。它将在 catch 块中被捕获为错误消息。
1 | function checkName(name) { |
抛出 Error 实例:
一种更常见和推荐的方法是抛出一个 Error
实例。这允许在错误中包含比如堆栈跟踪的附加元数据,有助于调试。
1 | function divide(numerator, denominator) { |
抛出聚合错误:
有时候,你可能希望同时抛出多个错误。这在处理 Promise
时特别有用。JavaScript 有一个内置的 AggregateError
对象,可以在这类情况下使用。AggregateError
对象可以接受一个错误对象的可迭代集合和一个可选的消息作为参数。
1 | let error1 = new Error("First Error"); |
函数
map()
高阶函数用法
定义
map()
方法是数组原型的一个函数,该函数用于对数组中的每个元素进行处理,将其转换为另一个值,最终返回一个新的数组,该数组包含了经过处理后的每个元素。
基本语法:
array.map(callback(currentValue[, index[, array]])[, thisArg])
callback
:表示对数组中的每个元素要执行的函数。该函数包含三个参数:currentValue
:表示正在处理的当前元素。index
:可选参数,表示正在处理的当前元素的索引。array
:可选参数,表示正在处理的当前数组。
thisArg
:可选参数,表示执行callback
函数时的this
值。
基本用法
使用
map()
方法将数组中的数字乘以 2 并返回新的数组:1
2
3
4
5
6let numbers = [1, 2, 3, 4];
let doubled = numbers.map(function(num) {
return num * 2;
});
console.log(doubled); // 输出 [2, 4, 6, 8]
console.log(numbers); // 输出 [1, 2, 3, 4],原数组并没有发生变化字符串处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 将字符串数组转换为数字数组:
let strings = ['1', '2', '3'];
let numbers = strings.map(function(str) {
return parseInt(str, 10); //第二个参数表示转化为十进制
});
console.log(numbers); // 输出 [1, 2, 3]
// 将一个数组中的字符串转换为另一个数组,只保留长度大于等于5的字符串:
const words = ['apple', 'banana', 'cherry', 'date', 'elderberry'];
const longWords = words.map(function(word) {
if (word.length >= 5) {
return word;
}
});
console.log(longWords); // ['apple', 'banana', 'cherry', undefined, 'elderberry']对象数组处理:
1
2
3
4
5let objects = [{ name: 'apple', color: 'red' }, { name: 'banana', color: 'yellow' }, { name: 'orange', color: 'orange' }];
let colors = objects.map(function(obj) {
return obj.color;
});
console.log(colors); // 输出 ["red", "yellow", "orange"]可以选择传递可选参数
thisArg
来设置回调函数的this
值。如果不传递thisArg
参数,则默认情况下,回调函数的this
值为undefined
。1
2
3
4
5
6let numbers = [1, 2, 3];
let obj = { multiplier: 2 };
let doubled = numbers.map(function(num) {
return num * this.multiplier;
}, obj);
console.log(doubled); // 输出 [2, 4, 6]- 上面的例子中,我们定义了一个名为
numbers
的数组,其中包含三个数字。我们还定义了一个名为obj
的对象,其中包含一个名为multiplier
的属性,该属性的值为 2。我们在回调函数中使用this.multiplier
,其中this
值为obj
,来将数组中的每个元素乘以 2。
- 上面的例子中,我们定义了一个名为
优缺点
优点:
map()
默认会有返回值,一般返回一个数组。所以这个也是map()
的一个特色- 这里要强调一下,函数默认没有返回值。如果函数内部没有明确使用 return 语句返回一个值,那么该函数执行完毕后会自动返回
undefined
。
- 这里要强调一下,函数默认没有返回值。如果函数内部没有明确使用 return 语句返回一个值,那么该函数执行完毕后会自动返回
可以方便地对数组中的每个元素进行操作,并生成一个新的数组;
不会改变原始数组。
缺点:
- 无法使用
break
,continue
,return
等关键字控制循环,必须全部遍历完毕才能停止; - 对于大型数据集合而言,可能会导致性能问题。
数据小的时候,用 map()
循环非常的爽,不是很消耗性能。但数据大的情况下,用 map()
会很耗性能,因为 map()
会对数组中的每个元素执行一次callback
方法。建议数据大的时候,用for循环。虽然多次for循环嵌套看着恶心,但是性能好,是底层的东西。而所谓的map()
,set()
,for in
,for of
都是在for循环的基础上再封装。单从性能角度考虑,远不如 for 循环优秀。
其他用法
- map循环配合
Array.from()
去重1
2
3const arr = [1, 2, 2, 3, 4, 4, 5];
const newArr = Array.from(new Map(arr.map(item => [item, item])).values());
console.log(newArr); // [1, 2, 3, 4, 5]- 先使用
map
方法将数组元素映射为键值对的数组。 - 使用
Map
构造函数将键值对数组转换为Map
对象,其中键和值均为数组的元素。由于Map
对象中键是唯一的,这样就自动去除了数组中的重复项。 - 最后,通过
Array.from()
方法将去重后的Map
对象的值转换为新的数组。
- 先使用
Array.from()
函数用法
定义
Array.from()
是一个静态方法,用于从类数组对象或可迭代对象创建一个新的数组。
基本语法:Array.from(arrayLike [, mapFunction [, thisArg]])
arrayLike
:要转换为数组的类数组对象或可迭代对象。类似数组的对象:DOM操作返回的 NodeList 集合,以及函数内部的 arguments 对象 等
本质特征只有一点,即必须有 length属性
任何有length对象,都可以通过
Array.from()
方法转换为数组1
2Array.from({ length:3 })
//[undefined,undefined,undefined]
可迭代的对象(iterable):ES6新增的数据结构 Set 和 Map 等
mapFunction
(可选):对每个元素进行处理的函数,类似于数组的map
方法,用于对每个元素进行处理,将处理后的值放入返回的数组。1
2
3Array.from(arrayLike, x => x*x)
//等同于
Array.from(arrayLike).map(x => x*x)thisArg
(可选):执行mapFunction
时this
的值。
基本用法
Array.from()
可以将各种值转为真正的数组,并且提供map功能。这实际上意味着,只要有一个原始数据结构,就可以先对它的值进行处理,然后转成规范的数组结构,在使用数组方法进行操作
Array.from()
可以将字符串转为数组,然后返回字符串的长度,因为它可以正确处理各种Unicode字符,可以避免 JavaScript 将大于 、uFFFF的Unicode字符算作2个字符的bug
1 | // 将字符串转换为数组,并对每个字符进行处理 |
Array.of()
函数用法
定义
Array.of()
是一个静态方法,用于创建一个新的数组。它的主要目的是解决使用 Array
构造函数时可能遇到的一些问题。
基本用法
Array.of
接受任意数量的参数,并以这些参数作为数组的元素,返回一个新的数组。1
2let arr = Array.of(1, 2, 3, 4, 5); // 等价于 Array(1, 2, 3, 4, 5) 或 new Array(1, 2, 3, 4, 5)
console.log(arr); // [1, 2, 3, 4, 5]处理单个参数:与
Array
构造函数不同,Array.of
不会根据参数的数量和类型来决定数组的初始化方式1
2
3
4
5let singleElementArray = Array.of(10);
console.log(singleElementArray); // [10]
let arr = Array(10);
console.log(arr); // 长度为10的空数组: [ <10 empty items> ]处理空参数:如果不传入任何参数,
Array.of
会返回一个空数组:[]
1
2let emptyArray = Array.of();
console.log(emptyArray); // []
Array.of()
基本上可以用来替代Array()
或new Array()
这个方法主要是用于弥补数组构造函数
Array()
的不足,因为 参数个数不同 会导致Array()
的行为差异
数组 .concat()
方法
定义
用途:合并 2 个或多个数组
注意:.concat()
返回的是一个 浅拷贝
用例分析
连接数组
1
2var arr = [1, 2, 3].concat([4, 5]);
console.log(arr); // [1, 2, 3, 4, 5]连接多个数组,包含直接连接值
1
2
3
4
5var arr1 = [1, 2];
var arr2 = [3, 4];
var num1 = 5;
var arr3 = arr1.concat(arr2, num1);
console.log(arr3); // [1, 2, 3, 4, 5]若是数组中有引用类型的值,那么
.concat()
方法返回这部分的是浅拷贝的值。1
2
3
4
5
6
7// 若是数组中都为基本类型,则修改原数组,不会影响拷贝后的数组
var arr1 = [1];
var arr2 = [3, 4];
var arr3 = arr1.concat(arr2);
console.log(arr3); // [1, 3, 4]
arr1.push(2);
console.log(arr3); // [1, 3, 4]1
2
3
4
5
6
7
8
9// 若是数组中包含引用类型,则修改原数组,会影响拷贝后的数组
var arr1 = [[1]];
var arr2 = [3, 4, [5]];
var arr3 = arr1.concat(arr2);
console.log(arr3); // [[1], 3, 4, [5]]
arr1[0].push(2); // 修改了引用类型 [1] => [1, 2]
arr2[2].push(6); // 修改了引用类型 [5] => [5, 6]
arr2.push(7); // 不会修改拷贝后的数组
console.log(arr3); // [[1, 2], 3, 4, [5, 6]]连接对象
1
2
3
4
5
6
7
8
9
10
11
12var arr1 = [1]
var arr2 = [3, 4]
var obj = {
a: 1,
b: 2
}
var arr3 = arr1.concat(arr2, obj)
console.log(arr3) // [1, 3, 4, {a: 1, b: 2}]
// 我们尝试修改引用类型
obj.a = 10;
console.log(arr3); // [1, 3, 4, {a: 10, b: 2}]
连接时是否展开
通过设置 Symbol.isConcatSpreadable
属性值,来控制是否展开
数组默认是展开的:
arr[Symbol.isConcatSpreadable] => true
1
2
3
4
5
6var arr1 = [1];
var arr2 = [3, 4];
console.log(arr2[Symbol.isConcatSpreadable]); // 默认情况下,Symbol.isConcatSpreadable 属性在 arr2 上是未定义的,因此输出 undefined
arr2[Symbol.isConcatSpreadable] = false; // 手动设置为不展开
var arr3 = arr1.concat(arr2);
console.log(arr3); // [1, [3, 4]]对象是默认不展开的:
obj[Symbol.isConcatSpreadable] => false
1
2
3
4
5
6
7
8
9var arr1 = [1];
var obj = {
length: 2, // 注意这个地方要加length属性,如果你要展开的话
0: 'a',
1: 'b'
}
obj[Symbol.isConcatSpreadable] = true; // 手动设置为不展开
var arr2 = arr1.concat(obj);
console.log(arr2); // [1, 'a', 'b']
Math库常见计算函数的使用
Math.max(x, y, z, ...)
取最大值
Math.min(x, y, z, ...)
取最小值
Math.ceil(x)
向上取整
Math.floor(x)
向下取整
Math.trunc(x)
保留整数部分
- 注意区别于
ceil()
和floor()
Math.round(x)
四舍五入x
- 函数返回一个数字四舍五入后最接近的整数
Math.random()
伪随机数
- 函数返回一个浮点, 伪随机数在范围 **[0,1)**。可以根据需要缩放到所需的范围。
- 实现将初始种子选择到随机数生成算法;它不能被用户选择或重置
Math.abs(x)
取绝对值
- 传入一个非数字形式的字符串或者 undefined/empty 变量,将返回
NaN
。 - 传入 null 将返回
0
Math.pow(x, n)
指数幂
Math.sqrt(x)
取平方根
Math.cbrt(x)
取立方根
杂项知识
浅拷贝 与 深拷贝
c++ 的
vector
是深拷贝c++ 和 java 的 数组 是 浅拷贝
浅拷贝
- 定义:浅拷贝复制对象的引用,而不是对象本身的内容。对于对象内部的引用类型字段,浅拷贝只复制引用地址,因此原对象和拷贝对象中的引用字段仍指向同一对象。
- 结果:修改拷贝对象中的引用字段,会影响原对象中的相应字段。
- 实现:在 Java 中,默认的
clone()
方法就是浅拷贝。在 C++ 中,可以通过简单的赋值操作实现浅拷贝。
深拷贝
- 定义:深拷贝不仅复制对象本身,还递归地复制对象引用的所有对象,从而生成一个完全独立的新对象。新对象的每个字段都是原对象字段的独立副本。
- 结果:修改拷贝对象中的任何部分不会影响原对象,因为拷贝对象和原对象完全独立。
- 实现:在 Java 中,可以通过实现
Cloneable
接口并覆盖clone()
方法来实现深拷贝,通常需要手动拷贝内部引用对象。在 C++ 中,可以使用拷贝构造函数或自定义拷贝逻辑来实现深拷贝。
1 | class Person { |
.fliter()
、.map()
和 .reduce()
有时候需要将
.filter()
和.map()
链接在一起,以删除数组中的元素并进行转换。- 问题在于这种方法效率较低,因为这种数组方法会在只需要一个数组的情况下创建两个数组。
你可以将
.filter()
和.map()
合并为单个.reduce()
,以提高性能。
1 | const groceries = [ |