运算符

?? Nullish 合并运算符

空值合并运算符。如果第一个参数不为 nullundefined 。返回第一个参数。否则,返回第二个参数。

  • 作用:为了弥补||运算符的缺陷。

    • 换句话说,|| 无法区分 false0、空字符串 ""null/undefined。它们都一样 —— 假值(falsy values)。如果其中任何一个是 || 的第一个参数,那么我们将得到第二个参数作为结果。

    不过在实际中,我们可能只想在变量的值为 null/undefined 时使用默认值。也就是说,当该值确实未知或未被设置时。

1
2
3
result = a ?? b;
// 使用三元运算符来实现
result = (a !== null && a !== undefined) ? a : b;

!! 等价于 Boolean()

是一种用于将任意值转换为其对应的布尔值的一种简便方式。它实际上是两个逻辑非运算符 ! 的连用。

!!a 等价于 Boolean(a)

  • 第一个 ! 运算符

    • 将其后的值转换为布尔类型。

    • 如果值是一个“假值”(例如 nullundefined0、空字符串 ""NaNfalse),则第一个 ! 运算符将其转换为 true

    • 如果值是一个“真值”(非上述假值),则第一个 ! 运算符将其转换为 false

  • 第二个 ! 运算符

    • 将上一步得到的布尔值再次取反。

    • 这样做的结果就是,无论初始值是什么,最终的结果都会是 truefalse

这种双重否定的操作常见于需要明确判断一个值是否为真或为假的情况中。它不会改变原始值的类型,而是产生一个布尔值作为结果。

** 指数运算符

** 指数运算符是ES7新特性(2016)

a ** b,它与 Math.pow(a, b)相同。

数组(列表)

初始化

  • let arr = new Array(n).fill(0); 初始化一个长度为n,全0的数组

    1
    2
    let array = [1,1,1,1,1,1];
    array.fill(0,1,3); //从下标1到下标3之前将值改变为0

for 循环

for...infor...of 是常见的循环,当然还有 forEach()

for...infor...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
      13
      const 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 4
    • for…of:通常性能比 for…in 更好,因为它不需要遍历原型链上的属性。

forEach() 方法

forEach() 是数组的一个方法,用于遍历数组中的每个元素。它接受一个回调函数作为参数,该函数会对数组的每个元素执行一次。

语法

1
2
3
array.forEach((element, index, array) => {
// 执行的代码
});

示例

1
2
3
4
5
const arr = [1, 2, 3, 4, 5];

arr.forEach((item, index) => {
console.log(index, item);
});

特点

  • 简洁性: 语法简洁,代码更具可读性。
  • 不支持提前退出: 你不能使用 breakcontinue 退出 forEach() 循环,这可能在某些情况下限制了它的使用。
  • 适用于数组: 仅适用于数组,不适用于对象或其他数据结构。

返回值

在 JavaScript 中,函数可以返回对象,对象是相关数据和函数(通常称为属性和方法)的集合。

1
2
3
4
5
6
7
8
9
10
11
12
function createPerson(name, age) {
return {
name: name,
age: age,
greet: function() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
};
}

let person = createPerson("Alice", 25);
person.greet(); // "Hello, my name is Alice and I'm 25 years old."

错误处理

在 JavaScript 中,错误处理主要通过使用 throw 语句和 try...catch 块来实现。使用 throw 语句允许你创建自定义错误消息,这对于调试代码非常有用。根据你想要实现的目标,可以使用不同的方式使用 throw 语句。

抛出字符串:

你可以在 JavaScript 中抛出一个字符串。它将在 catch 块中被捕获为错误消息。

1
2
3
4
5
6
7
8
9
10
11
12
function checkName(name) {
if (name === '') {
throw "Name can't be empty!";
}
return name;
}

try {
console.log(checkName(''));
} catch (error) {
console.error(error); // "Name can't be empty!"
}

抛出 Error 实例:

一种更常见和推荐的方法是抛出一个 Error 实例。这允许在错误中包含比如堆栈跟踪的附加元数据,有助于调试。

1
2
3
4
5
6
7
8
9
10
11
12
function divide(numerator, denominator) {
if (denominator === 0) {
throw new Error("Cannot divide by zero!");
}
return numerator / denominator;
}

try {
console.log(divide(5, 0));
} catch (error) {
console.error(error.message); // "Cannot divide by zero!"
}

抛出聚合错误:

有时候,你可能希望同时抛出多个错误。这在处理 Promise 时特别有用。JavaScript 有一个内置的 AggregateError 对象,可以在这类情况下使用。AggregateError 对象可以接受一个错误对象的可迭代集合和一个可选的消息作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
let error1 = new Error("First Error");
let error2 = new Error("Second Error");

try {
throw new AggregateError([error1, error2], "Two errors occurred.");
} catch (error) {
if (error instanceof AggregateError) {
console.error(error.message); // "Two errors occurred."
for (let e of error.errors) {
console.error(e.message); // 打印 "First Error" 和 "Second Error"
}
}
}

函数

map() 高阶函数用法

定义

map() 方法是数组原型的一个函数,该函数用于对数组中的每个元素进行处理,将其转换为另一个值,最终返回一个新的数组,该数组包含了经过处理后的每个元素。

  • 基本语法:array.map(callback(currentValue[, index[, array]])[, thisArg])

    • callback :表示对数组中的每个元素要执行的函数。该函数包含三个参数:

      • currentValue:表示正在处理的当前元素。

      • index:可选参数,表示正在处理的当前元素的索引。

      • array:可选参数,表示正在处理的当前数组。

    • thisArg:可选参数,表示执行 callback 函数时的 this 值。

基本用法

  • 使用 map() 方法将数组中的数字乘以 2 并返回新的数组:

    1
    2
    3
    4
    5
    6
    let 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
    5
    let 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
    6
    let 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
  • 可以方便地对数组中的每个元素进行操作,并生成一个新的数组;

  • 不会改变原始数组。

缺点:
  • 无法使用 breakcontinuereturn 等关键字控制循环,必须全部遍历完毕才能停止;
  • 对于大型数据集合而言,可能会导致性能问题。

数据小的时候,用 map() 循环非常的爽,不是很消耗性能。但数据大的情况下,用 map() 会很耗性能,因为 map() 会对数组中的每个元素执行一次callback方法。建议数据大的时候,用for循环。虽然多次for循环嵌套看着恶心,但是性能好,是底层的东西。而所谓的map()set()for infor of 都是在for循环的基础上再封装。单从性能角度考虑,远不如 for 循环优秀。

其他用法

  • map循环配合 Array.from() 去重
    1
    2
    3
    const 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
        2
        Array.from({ length:3 })
        //[undefined,undefined,undefined]
    • 可迭代的对象(iterable):ES6新增的数据结构 SetMap

  • mapFunction(可选):对每个元素进行处理的函数,类似于数组的 map 方法,用于对每个元素进行处理,将处理后的值放入返回的数组。

    1
    2
    3
    Array.from(arrayLike, x => x*x)
    //等同于
    Array.from(arrayLike).map(x => x*x)
  • thisArg(可选):执行 mapFunctionthis 的值。

基本用法

Array.from() 可以将各种值转为真正的数组,并且提供map功能。这实际上意味着,只要有一个原始数据结构,就可以先对它的值进行处理,然后转成规范的数组结构,在使用数组方法进行操作

Array.from()可以将字符串转为数组,然后返回字符串的长度,因为它可以正确处理各种Unicode字符,可以避免 JavaScript 将大于 、uFFFF的Unicode字符算作2个字符的bug

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
// 将字符串转换为数组,并对每个字符进行处理
let str = 'hello';
let newArray1 = Array.from(str, char => char.toUpperCase());
console.log(newArray1); // newArray1 => ['H', 'E', 'L', 'L', 'O']


// 传入Set类型的
const setter = new Set(['foo','bar','baz','foo'])
let newArray2 = Array.from(setter)
console.log(newArray2) // newArray2 => [ 'foo', 'bar', 'baz' ]

// 传入Map类型
const mapper = new Map([
[1,2],
[2,4],
[4,8]
])
let newArray3 = Array.from(mapper)
console.log(newArray3) // newArray3 => [ [ 1, 2 ], [ 2, 4 ], [ 4, 8 ] ]

//传入类数组对象
function func(){
return Array.from(arguments)
}
let newArray4 = func("arg1", "arg2", "arg3")
console.log(newArray4) // newArray4 => [ 'arg1', 'arg2', 'arg3' ]

Array.of() 函数用法

定义

Array.of() 是一个静态方法,用于创建一个新的数组。它的主要目的是解决使用 Array 构造函数时可能遇到的一些问题。

基本用法

  • Array.of 接受任意数量的参数,并以这些参数作为数组的元素,返回一个新的数组。

    1
    2
    let 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
    5
    let singleElementArray = Array.of(10);
    console.log(singleElementArray); // [10]

    let arr = Array(10);
    console.log(arr); // 长度为10的空数组: [ <10 empty items> ]
  • 处理空参数:如果不传入任何参数,Array.of返回一个空数组[]

    1
    2
    let emptyArray = Array.of();
    console.log(emptyArray); // []

Array.of() 基本上可以用来替代Array()new Array()

这个方法主要是用于弥补数组构造函数Array()的不足,因为 参数个数不同 会导致Array()的行为差异

数组 .concat() 方法

定义

用途:合并 2 个或多个数组

注意:.concat() 返回的是一个 浅拷贝

用例分析

  • 连接数组

    1
    2
    var arr = [1, 2, 3].concat([4, 5]);
    console.log(arr); // [1, 2, 3, 4, 5]
  • 连接多个数组,包含直接连接值

    1
    2
    3
    4
    5
    var 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
    12
    var 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
    6
    var 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
    9
    var 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 的 数组浅拷贝

可以参考:面试题:深拷贝和浅拷贝(超级详细,有内存图)_深copy和浅copy面试-CSDN博客

浅拷贝

  • 定义:浅拷贝复制对象的引用,而不是对象本身的内容。对于对象内部的引用类型字段,浅拷贝只复制引用地址,因此原对象和拷贝对象中的引用字段仍指向同一对象
  • 结果修改拷贝对象中的引用字段会影响原对象中的相应字段。
  • 实现:在 Java 中,默认的 clone() 方法就是浅拷贝。在 C++ 中,可以通过简单的赋值操作实现浅拷贝。

深拷贝

  • 定义:深拷贝不仅复制对象本身,还递归地复制对象引用的所有对象,从而生成一个完全独立的新对象。新对象的每个字段都是原对象字段的独立副本。
  • 结果:修改拷贝对象中的任何部分不会影响原对象,因为拷贝对象和原对象完全独立。
  • 实现:在 Java 中,可以通过实现 Cloneable 接口并覆盖 clone() 方法来实现深拷贝,通常需要手动拷贝内部引用对象。在 C++ 中,可以使用拷贝构造函数或自定义拷贝逻辑来实现深拷贝。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
String name;
Address address;
Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();
p.address = (Address) address.clone(); // 深拷贝
return p;
}
}

Address addr = new Address("123 Street");
Person p1 = new Person("John", addr);

Person p2 = new Person(p1.name, p1.address); // Address为引用类型,浅拷贝
p1.address.city = "New City"; // p3.address.city 也会被改变

Person p3 = (Person) p1.clone(); // 自定义实现了 深拷贝
p1.address.city = "New City"; // p3.address.city 不会被改变

.fliter().map().reduce()

  • 有时候需要将 .filter().map() 链接在一起,以删除数组中的元素并进行转换。

    • 问题在于这种方法效率较低,因为这种数组方法会在只需要一个数组的情况下创建两个数组。
  • 你可以将 .filter().map() 合并为单个 .reduce() ,以提高性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const groceries = [
{ id: 173, name: "Soup" },
{ id: 964, name: "Apple" },
{ id: 535, name: "Cheese" }
];

/** 使用 filter 和 map */
var names = groceries
.filter(item => item.id > 500)
.map(item => item.name)

/** 使用 reduce */
var names = groceries.reduce((accumulator, val) => {
if (val.id > 500) {
accumulator.push(val.name);
}
return accumulator;
}, []);

console.log(names); // ["Apple", "Cheese"]