無.Flac's Blog

無.Flac

JavaScript - 数组

2024-12-26

在 JavaScript 中,数组是一种用于存储多个值的特殊对象。它提供了丰富的内置方法,如 push, pop, forEach, map, filter, reduce 等,用于操作和访问数组中的元素。数组支持动态添加和删除元素,并且可以通过索引快速访问特定位置的元素。数组的长度是可变的,这使得它在处理数据集合时非常灵活和强大。


数组(Array)

>简介

数组也是一种复合数据类型,在数组可以存储多个不同类型的数据。

数组中存储的是有序的数据,数组中的每个数据都有一个唯一的索引,可以通过索引来操作获取数据。

数组中存储的数据叫做元素,索引(index)是一组大于 0 的数。

length是用来获取数组的长度。

- 创建数组
    通过Array()来创建数组,也可以通过[]来创建数组

    const arr = new Array()
  
- 向数组中添加元素
    语法:
        数组[索引] = 元素

    arr[0] = 10
    arr[1] = 22
    arr[2] = 44
    arr[3] = 88
    arr[4] = 99
 // arr[100] = 99  // 使用数组时,应该避免非连续数组,因为它性能不好
    const arr2 = [1, 2, 3, 4, 5] // 数组字面量

- 读取数组中的元素
    语法:
        数组[索引]
        - 如果读取了一个不存在的元素,不好报错而是返回undefined

    console.log(typeof arr) // object

- length
    - 获取数组的长度,实际值就是数组的最大索引 + 1
      console.log(arr)

    - 向数组最后添加元素:==> 数组[数组.length] = 元素
      arr[arr.length] = 55
      arr[arr.length] = 99

    - length是可以修改的
      arr.length = 5 //改大会让数组变大多出很多空元素,改小则会减少多出的元素

>遍历数组

遍历数组简单理解,就是获取到数组中的每一个元素

// 任何类型的值都可以成为数组中的元素
let arr = [1, "hello", true, null, { name: "孙悟空" }, () => {}]

// 创建数组时尽量要确保数组中存储的数据的类型是相同
arr = ["孙悟空", "猪八戒", "沙和尚"]

// console.log(arr[0])
// console.log(arr[1])
// console.log(arr[2])

arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精"]

for(let i=0; i<arr.length; i++){
    console.log(arr[i])
}

 for (let i = arr.length - 1; i >= 0; i--) {
     console.log(arr[i])
}
/* 
    定义一个Person类,类中有两个属性name和age,然后创建几个Person对象,
    将其添加到一个数组中,遍历数组,并打印未成年人的信息
*/

class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
}


const personArr = [
    new Person("孙悟空", 18),
    new Person("沙和尚", 38),
    new Person("红孩儿", 8),
]


for(let i=0; i<personArr.length; i++){
    if(personArr[i].age < 18){
        console.log(personArr[i])
    }
}

>for-of语句

for-of 可以用来遍历迭代对象

语法:
    for(变量 of 可迭代的对象){
        语句...
    }

执行流程:
    for-of的循环体会执行多次,数组中有几个元素就会执行几次,
    每次执行时都会将一个元素赋值给变量


const arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]

for(let value of arr){
    console.log(value)
}

for(let value of "welcome"){
    console.log(value)
}

>数组的方法(非破坏性)

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array

Array.isArray() —— 检查一个对象是否是数组

/*
    Array.isArray()
        - 用来检查一个对象是否是数组    
*/
 console.log(Array.isArray({ name: "孙悟空" })) // false
 console.log(Array.isArray([1, 2, 3])) // true

at() —— 索引获取数组中的指定元素

/*
    at()
        - 可以根据索引获取数组中的指定元素
        - at可以接收负索引作为参数
*/

 const arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]
 console.log(arr.at(-2))
  • arr.at(-2)使用了数组的 at() 方法,它允许你通过索引来获取数组中的元素。

  • 在这个例子中,索引是 -2。at() 方法中的负索引表示从数组末尾开始计数。

  • 因此,arr.at(-2) 将获取数组中倒数第二个元素。 对于数组 ["孙悟空", "猪八戒", "沙和尚", "唐僧"],倒数第二个元素是 "沙和尚"。

  • 所以,这行代码将在控制台输出"沙和尚"

console.log(arr[arr.length - 2])
  • arr[arr.length - 2] 使用了标准的数组索引方式来获取元素。

  • arr.length 返回数组的长度,对于这个数组是 4

  • arr.length - 2 计算得到 2,这是数组中倒数第二个元素的索引。

  • 因此,arr[arr.length - 2] 将获取数组中索引为 2 的元素,即 "沙和尚"

  • 所以,这行代码也将在控制台输出 "沙和尚"

concat() —— 连接两个或多个数组

/* 
    concat()
        - 用来连接两个或多个数组
        - 非破坏性方法,不会影响原数组,而是返回一个新的数组
*/

const arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]
const arr2 = ["白骨精", "蜘蛛精", "玉兔精"]

let result = arr.concat(arr2, ["牛魔王","铁扇公主"])

console.log(result) 
// 输出:["孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精", "蜘蛛精", "玉兔精", "牛魔王", "铁扇公主"]
  • const arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]; 定义了一个包含四个字符串元素的数组 arr

  • const arr2 = ["白骨精", "蜘蛛精", "玉兔精"]; 定义了另一个包含三个字符串元素的数组 arr2

  • let result = arr.concat(arr2, ["牛魔王","铁扇公主"]); 使用 concat() 方法合并数组 arrarr2 和一个新数组 ["牛魔王","铁扇公主"]concat() 方法不会改变原有的数组,而是返回一个新的数组,这个新数组包含了所有合并的元素。合并后的数组被赋值给变量 result

  • console.log(result); 打印变量 result 的值,也就是合并后的新数组。

indexOf() | lastIndexOf()

/*
    indexOf()
        - 获取元素在数组中第一次出现的索引
        - 参数:
            1. 要查询的元素
            2. 查询的其实位置
*/

console.log(result) 
// 输出:["孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精", "蜘蛛精", "玉兔精", "牛魔王", "铁扇公主"]
let result = arr.indexOf("沙和尚", 3)
  • indexOf() 方法用于查找元素在数组中首次出现的位置。

  • 它接收两个参数:要查找的元素和(可选的)搜索的起始位置。

  • 在这个例子中,我们从索引 3 开始搜索 "沙和尚" 在数组 arr 中的位置。

  • 因为 "沙和尚" 在数组 arr 中的索引是 2,而 2 小于 3,所以 indexOf() 方法会返回 -1,表示未找到。

/*
    lastIndexOf()
        - 获取元素在数组中最后一次出现的位置

        - 返回值:
            找到了则返回元素的索引,
            没有找到返回-1
*/

console.log(result) 
// 输出:["孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精", "蜘蛛精", "玉兔精", "牛魔王", "铁扇公主"]
result = arr.lastIndexOf("沙和尚", 3)
  • lastIndexOf() 方法用于查找元素在数组中最后一次出现的位置。

  • 它同样接收两个参数:要查找的元素和(可选的)搜索的起始位置。

  • 在这个例子中,我们从索引 3 开始向前搜索 "沙和尚" 在数组 arr 中的最后一次出现的位置。

  • 因为 "沙和尚" 在数组 arr 中的索引是 2,而 2 小于等于 3,所以 lastIndexOf() 方法会返回 2。

console.log(result) 
// 输出:["孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精", "蜘蛛精", "玉兔精", "牛魔王", "铁扇公主"]
result = arr.indexOf("白骨精")
  • 这里的 indexOf() 方法查找 "白骨精" 在数组 arr 中首次出现的位置。

  • 因为 "白骨精" 不在数组 arr 中,所以 indexOf() 方法会返回 -1。

join() —— 将元素连接为一个字符串

/*
    join()
        - 将一个数组中的元素连接为一个字符串
        - ["孙悟空", "猪八戒", "沙和尚", "唐僧", "沙和尚"] -> "孙悟空,猪八戒,沙和尚,唐僧,沙和尚"
        - 参数:
            指定一个字符串作为连接符
*/
result = arr.join()
result = arr.join("@-@")
result = arr.join("")

slice() —— 用来截取数组

/*
   slice()
        - 用来截取数组(非破坏性方法)     
        - 参数:
            1. 截取的起始位置(包括该位置)
            2. 截取的结束位置(不包括该位置)   
                - 第二个参数可以省略不写,如果省略则会一直截取到最后
                - 索引可以是负值

            如果将两个参数全都省略,则可以对数组进行浅拷贝(浅复制)
*/
arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]
result = arr.slice(0, 2) //输出: ["孙悟空", "猪八戒"]
result = arr.slice(1, 3)
result = arr.slice(1, -1)
result = arr.slice()
  • result = arr.slice(0, 2);

    • 这行代码从索引 0 开始提取,直到索引 2(不包括)为止。

    • 因此,它将返回数组中的前两个元素:["孙悟空", "猪八戒"]

  • result = arr.slice(1, 3);

    • 这行代码从索引 1 开始提取,直到索引 3(不包括)为止。

    • 因此,它将返回数组中的第二和第三个元素:["猪八戒", "沙和尚"]

  • result = arr.slice(1, -1);

    • 这行代码从索引 1 开始提取,直到数组倒数第一个元素(不包括)为止。

    • 因此,它将返回数组中除最后一个元素外的所有元素:["猪八戒", "沙和尚", "唐僧"]

  • result = arr.slice();

    • 当不提供任何参数时,slice() 方法将返回数组的一个浅拷贝。

    • 因此,它将返回原数组的一个副本:["孙悟空", "猪八戒", "沙和尚", "唐僧"]

>数组的复制

xx.slice() ==> 数组

Object.assign ==> 对象

const arr = ["孙悟空", "猪八戒", "沙和尚"]
// const arr2 = arr  // 不是复制
// arr2[0] = "唐僧" // 如此修改,在此打印时会显示["唐僧", "猪八戒", "沙和尚"],且全局更改

// 如何去复制一个对象 复制必须要产生新的对象
// 当调用slice时,会产生一个新的数组对象,从而完成对数组的复制
const arr3 = arr.slice()


// console.log(arr === arr2) 
// console.log(arr2)

arr3[0] = "唐僧"

console.log(arr) // 输出:["孙悟空", "猪八戒", "沙和尚"]
console.log(arr3) // 输出:["唐僧", "猪八戒", "沙和尚"]
/* 
    浅拷贝(shallow copy)
        - 通常对对象的拷贝都是浅拷贝
        - 浅拷贝顾名思义,只对对象的浅层进行复制(只复制一层)
        - 如果对象中存储的数据是原始值,那么拷贝的深浅是不重要
        - 浅拷贝只会对对象本身进行复制,不会复制对象中的属性(或元素)
*/

// 创建一个数组
const arr = [{name:"孙悟空"}, {name:"猪八戒"}] // 数组里的元素为对象
const arr2 = arr.slice() // 浅拷贝
console.log(arr)

/*
    深拷贝(deep copy)
        - 深拷贝指不仅复制对象本身,还复制对象中的属性和元素
        - 因为性能问题,通常情况不太使用深拷贝
*/

const arr3 = structuredClone(arr) // 专门用来深拷贝的方法
console.log(arr3)
const arr = ["孙悟空", "猪八戒", "沙和尚"]

const arr2 = arr.slice()

console.log(arr === arr2) // false

/* 
    ... (展开运算符)
        - 可以将一个数组中的元素展开到另一个数组中或者作为函数的参数传递
        - 通过它也可以对数组进行浅复制
*/

// const arr3 = [arr[0], arr[1], arr[2]] 等价于 const arr3 = [...arr]

const arr3 = [...arr]
// console.log(arr) // ["孙悟空", "猪八戒", "沙和尚"]

const arr3 = ["唐僧", ...arr, "白骨精"]
// console.log(arr3)  // ["唐僧", "孙悟空", "猪八戒", "沙和尚", "白骨精"]

/*
function 关键字用于定义函数。在你的代码中,sum 函数接受三个参数 a, b, 和 c,并返回它们的和。
定义了一个数组 arr4,包含三个元素:10, 20, 和 30。

用两种方式调用 sum 函数:

直接传递数组的元素作为参数:sum(arr4[0], arr4[1], arr4[2])。这种方式结果是 60。

使用扩展运算符传递数组中的所有元素:sum(...arr4)。这种方式也是将数组中的元素作为单独的参数传递给函数,结果同样是 60。
*/

function sum(a, b, c) {
    return a + b + c
}

const arr4 = [10, 20, 30]

let result = sum(arr4[0], arr4[1], arr4[2])

    result = sum(...arr4)

// console.log(result)


/* 
    对象的复制
        - Object.assign(目标对象, 被复制的对象)
        - 将被复制对象中的属性复制到目标对象里,并将目标对象返回

    - 也可以使用展开运算符对对象进行复制
*/

const obj = { name: "孙悟空", age: 18 }

// const obj2 = Object.assign({}, obj)
const obj2 = { address: "花果山", age: 28 } // 后复制的优先替换先前的,比如 age

Object.assign(obj2, obj)
// console.log(obj2)  // 输出: { address: "花果山", name: "孙悟空", age: 18 }

const obj3 = { address: "高老庄", ...obj, age: 48 } // 将obj中的属性在新对象中展开
// 输出: { address: "高老庄", name: "孙悟空", age: 48 }

>数组的方法(破坏性)

push() —— 向数组的末尾添加元素,并返回新的长度

push()
    - 向数组的末尾添加一个或多个元素,并返回新的长度

let arr = ["孙悟空", "猪八戒", "沙和尚"]
let result = arr.push("唐僧", "白骨精")
console.log(result); // 输出:5
  • let arr = ["孙悟空", "猪八戒", "沙和尚"]; 定义了一个包含三个字符串元素的数组 arr

  • let result = arr.push("唐僧", "白骨精"); 调用了 push() 方法,向数组 arr 的末尾添加了两个新的元素:"唐僧" 和 "白骨精"。

    • push() 方法会返回更新后数组的长度。

    • 在这个例子中,push() 方法将返回 5,因为添加新元素后数组 arr 的长度变成了 5。

  • 变量 result 将被赋值为 5,这是更新后数组 arr 的长度。

pop() —— 删除并返回数组的最后一个元素

pop()
    - 删除并返回数组的最后一个元素

let arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精"]
result = arr.pop() // 输出:"白骨精"
  • result = arr.pop(); 调用了 pop() 方法,它会删除数组 arr 的最后一个元素,并将其值赋给变量 result

    • pop() 方法会改变原数组,减少其长度。

    • 在这个例子中,如果 arr 的初始状态如上所述,pop() 方法将删除 "白骨精" 并返回它。

  • 变量 result 将被赋值为 "白骨精",这是原数组 arr 被删除的最后一个元素。

unshift() | shift()

unshift()
    - 向数组的开头添加一个或多个元素,并返回新的长度

let arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精"]
arr.unshift("牛魔王")
console.log(arr) // 输出: ["牛魔王","孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精"]


shift()
    - 删除并返回数组的第一个元素

arr.shift() //输出: “牛魔王”
console.log(arr) // 输出: ["孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精"]

splice() —— 删除、插入、替换数组中的元素

splice()
    - 可以删除、插入、替换数组中的元素
    - 参数:
        1. 删除的起始位置
        2. 删除的数量
        3. 要插入的元素

    - 返回值:
        - 返回被删除的元素

let arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]
console.log(arr) //输出: ['孙悟空']

result = arr.splice(1, 3) //输出:["猪八戒", "沙和尚", "唐僧"]
//将删除的元素赋值给 result 

result = arr.splice(1,2,'牛魔王')
console.log(result); // 输出:["猪八戒", "沙和尚"]
console.log(arr);    // 输出:["孙悟空", "牛魔王", "唐僧"]
  • arr.splice(1, 2, '牛魔王') 调用了 splice() 方法,它接收至少两个参数:

    • 第一个参数 1 是开始修改的索引位置。

    • 第二个参数 2 是要删除的元素数量。

    • 第三个参数 '牛魔王' 是要添加进数组的元素,它会在删除元素的位置之后插入。

  • splice() 方法会改变原数组,并返回一个包含被删除元素的新数组。

  • 在这个例子中,从索引 1 开始,删除 2 个元素(即 "猪八戒" 和 "沙和尚"),然后在相同的位置插入 '牛魔王'

  • 变量 result 将被赋值为 ["猪八戒", "沙和尚"],这是被删除的元素组成的数组。

执行这段代码后,原数组 arr 将变为 ["孙悟空", "牛魔王", "唐僧"],并且变量 result 的值是 ["猪八戒", "沙和尚"]

 result = arr.splice(1, 0, "牛魔王", "铁扇公主", "红孩儿")
  • arr.splice(1, 0, "牛魔王", "铁扇公主", "红孩儿") 调用了 splice() 方法,它接收至少两个参数:

    • 第一个参数 1 是开始修改的索引位置。

    • 第二个参数 0 是要删除的元素数量。

    • 后续的参数("牛魔王", "铁扇公主", "红孩儿")是要添加进数组的元素,它们会在删除元素的位置之后插入。

  • splice() 方法会改变原数组,并返回一个包含被删除元素的新数组。

  • 在这个例子中,从索引 1 开始,我们不删除任何元素(删除数量为 0),然后在相同的位置插入 "牛魔王", "铁扇公主", "红孩儿" 这三个元素。

  • 变量 result 将被赋值为 [],因为没有任何元素被删除。

执行这段代码后,原数组 arr 将变为 ["孙悟空", "牛魔王", "铁扇公主", "红孩儿", "猪八戒", "沙和尚", "唐僧"],并且变量 result 的值是 [](空数组)。

reverse() —— 反转数组

reverse()
    - 反转数组

let arr = ["a", "b", "c", "d"]
arr.reverse() //输出:['d', 'c', 'b', 'a']

>数组的去重

有如下一个数组arr = [1,2,1,3,2,4,5,5,6,7],编写代码,去除数组中重复的元素。

// 定义一个包含重复数字的数组
const arr = [1, 2, 1, 3, 2, 2, 4, 5, 5, 6, 7];

// 外层循环:遍历数组中的每个元素
for (let i = 0; i < arr.length; i++) {
    // 内层循环:从当前元素的下一个元素开始,检查后面的所有元素
    for (let j = i + 1; j < arr.length; j++) {
        // 如果发现外层循环的元素与内层循环的元素相等,说明找到了一个重复的元素
        if (arr[i] === arr[j]) {
            // 使用 splice 方法删除重复的元素
            arr.splice(j, 1);
            // 因为删除了一个元素,后面的元素会向前移动一位,所以需要将 j 退回一位,以确保不会漏掉任何一个元素
            j--;
        }
    }
}

// 打印处理后的数组,此时数组中不再包含重复的元素
console.log(arr); // 输出:[1, 2, 3, 4, 5, 6, 7]
// 定义一个包含重复数字的数组
const arr = [1, 2, 1, 3, 2, 2, 4, 5, 5, 6, 7];

// 遍历数组中的每个元素
for (let i = 0; i < arr.length; i++) {
    // 查找当前元素在数组中从当前位置之后的第一次出现的位置
    const index = arr.indexOf(arr[i], i + 1);
    // 如果找到了重复元素(index不等于空值)
    if (index !== -1) {
        // 删除数组中该重复元素
        arr.splice(index, 1);
        // 由于删除了一个元素,数组的长度减少,需要调整循环变量以避免跳过元素
        i--;
    }
}

// 打印处理后的数组,此时数组中不再包含重复的元素
console.log(arr); // 输出:[1, 2, 3, 4, 5, 6, 7]
// 定义一个包含重复数字的数组
const arr = [1, 2, 1, 3, 2, 2, 4, 5, 5, 6, 7];

// 创建一个空数组,用于存储非重复的元素
const newArr = [];

// 遍历原始数组中的每个元素
for (let ele of arr) {
    // 检查当前元素是否已经在 newArr 中
    if (newArr.indexOf(ele) === -1) {
        // 如果当前元素不在 newArr 中,则将其添加到 newArr
        newArr.push(ele);
    }
}

// 打印包含非重复元素的新数组
console.log(newArr); // 输出:[1, 2, 3, 4, 5, 6, 7]

>冒泡排序

9, 1, 3, 2, 8, 0, 5, 7, 6, 4

- 比较相邻的两个元素,然后根据大小来决定是否交换它们的位置
- 例子:
    第一次排序:1, 3, 2, 8, 0, 5, 7, 6, 4, 9
    第二次排序:1, 2, 3, 0, 5, 7, 6, 4, 8, 9
    第三次排序:1, 2, 0, 3, 5, 6, 4, 7, 8, 9
    ...
    倒数第二次 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

const arr = [9, 1, 3, 2, 8, 0, 5, 7, 6, 4]

/*
for (let j = 0; j < arr.length - 1; j++) {
    for (let i = 0; i < arr.length - 1; i++) {
        // arr[i] 前边的元素 arr[i+1] 后边元素
        if (arr[i] < arr[i + 1]) {
            // 大数在前,小数在后,需要交换两个元素的位置
            let temp = arr[i] // 临时变量用来存储arr[i]的值
            arr[i] = arr[i + 1] // 将arr[i+1]的值赋给arr[i]
            arr[i + 1] = temp // 修改arr[i+1]的值
        }
    }
}
*/
优化代码:
for (let j = 0; j < arr.length - 1; j++) {
    for (let i = 0; i < arr.length - 1 - j; i++) {
        // arr[i] 前边的元素 arr[i+1] 后边元素
        if (arr[i] < arr[i + 1]) {
            // 大数在前,小数在后,需要交换两个元素的位置
            let temp = arr[i] // 临时变量用来存储arr[i]的值
            arr[i] = arr[i + 1] // 将arr[i+1]的值赋给arr[i]
            arr[i + 1] = temp // 修改arr[i+1]的值
        }
    }
}
console.log(arr)

- 这种排序方式,被称为冒泡排序,冒泡排序是最慢的排序方式,
  数字少还可以凑合用,不适用于数据量较大的排序

>选择排序

- 取出一个元素,然后将其他元素和该元素进行比较,如果其他元素比该元素小则交换两个元素的位置
- 例子:
    第一次排序:0, 9, 3, 2, 8, 1, 5, 7, 6, 4
    第二次排序:0, 1, 9, 3, 8, 2, 5, 7, 6, 4
    第二次排序:0, 1, 2, 9, 8, 3, 5, 7, 6, 4
    ...

console.log(arr)

// 遍历原始数组中的每个元素
for(let i=0; i<arr.length; i++){
    for(let j=i+1; j<arr.length; j++){
        if(arr[i] > arr[j]){
            // 交换两个元素的位置
            let temp = arr[i] // 临时变量用来存储arr[i]的值
            arr[i] = arr[j] // 将arr[i]的值赋给arr[j]
            arr[j] = temp   // 修改arr[j]的值
        }
        }
    }
}

console.log(arr)

  选择排序
    - 取出一个元素,然后将其他元素和该元素进行比较,如果其他元素比该元素小则交换两个元素的位置

>封装函数

const arr = [9, 1, 3, 2, 8, 0, 5, 7, 6, 4]
const arr2 = [9, 8, 7, 6, 5, 4, 3, 2, 1]

function sort(array) {
    const arr = [...array]
    for (let i = 0; i < arr.length; i++) {
        for (let j = i + 1; j < arr.length; j++) {
            if (arr[i] > arr[j]) {
                // 交换两个元素的位置
                let temp = arr[i]
                arr[i] = arr[j]
                arr[j] = temp
            }
        }
    }
    return arr
}

let result = sort(arr2)

// console.log(arr2)
// console.log(result)
class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
}

const personArr = [
    new Person("孙悟空", 18),
    new Person("沙和尚", 38),
    new Person("红孩儿", 8),
    new Person("白骨精", 16),
]


// filter()函数用来对数组进行过滤
function filter(arr) {

    const newArr = []

    for (let i = 0; i < arr.length; i++) {
        if (arr[i].age < 18) {
            newArr.push(arr[i])
        }
    }

    return newArr
}

result = filter(personArr)
console.log(result)


- - - - - - - - - - - - - - - - - 

目前我们的函数只能过滤出数组中age属性小于18的对象,
    - 我们希望过滤更加灵活:
        比如:过滤数组中age大于18的对象
             过滤数组中age大于60的对象
             过滤数组中age大于n的对象
             过滤数组中name为xxx的对象
             过滤数组中的偶数
             ...

>回调函数

如果将函数作为参数传递,那么我们就称这个函数为回调函数(callback)

请注意!一个函数的参数也可以是函数

class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
}

const personArr = [
    new Person("孙悟空", 18),
    new Person("沙和尚", 38),
    new Person("红孩儿", 8),
    new Person("白骨精", 16),

function filter(arr, cb) {
    const newArr = []  // 创建一个空数组用于存放符合条件的元素

    //遍历并获取里面的元素
    for (let i = 0; i < arr.length; i++) {
          
         // 当前数组元素 arr[i] 的 name 是否等于 '孙悟空'?”
        if (cb(arr[i])) {  // 这里的 cb 实际上就是 fn

        // 如果是 true(即当前元素符合条件),则将这个元素添加到结果数组中
            newArr.push(arr[i])  // 将符合条件的元素加入 newArr
        }
    }

    return newArr  // 返回新数组
}


function fn(a) {
    return a.name === "孙悟空" // a.name 是数组元素的 name 属性
}

// 调用 filter 函数,传递 personArr 数组和回调函数 fn 作为参数
let result = filter(personArr, fn)

// 打印 filter 函数的返回值,即满足条件的元素组成的数组
console.log(result)

>高阶函数

如果一个函数的参数或返回值是函数,则这个函数就称为高阶函数

class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
}

const personArr = [
    new Person("孙悟空", 18),
    new Person("沙和尚", 38),
    new Person("红孩儿", 8),
    new Person("白骨精", 16),
]

function filter(arr, cb) {
    const newArr = [] // 创建一个空数组newArr

    for (let i = 0; i < arr.length; i++) { // 遍历数组中的每个元素
        if (cb(arr[i])) {                 // 如果回调函数对当前元素返回 true
            newArr.push(arr[i])          // 将当前元素添加到 newArr 中
        }
    }

    return newArr // 返回包含所有通过测试的元素的新数组
}

// 我们这种定义回调函数的形式比较少见,通常回调函数都是匿名函数

// function fn(a) {
//     return a.name === "孙悟空"
// }


result = filter(personArr, a => a.name === "孙悟空")  // 过滤出名字为 "孙悟空" 的 Person 对象
result = filter(personArr, a => a.age >= 18)

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = filter(arr, a => a % 2 === 0)

console.log(result)
/*
    希望在someFn()函数执行时,可以记录一条日志

    在不修改原函数的基础上,为其增加记录日志的功能

    可以通过高阶函数,来动态的生成一个新函数
*/

function someFn() {
    return "hello"
}

function outer(cb){
    return () => {
        console.log("记录日志~~~~~")
        const result = cb()
        return result
    }
}

let result = outer(someFn)

// console.log(result)


function test(){
    console.log("test~~~~")
    return "test"
}

let newTest = outer(test)

newTest()

>闭包简介

闭包就是能访问到外部函数作用域中变量的函数。

使用场景:

  • 当我们需要隐藏一些不希望被别人访问的内容时就可以使用闭包。

构成闭包的要件:

  1. 函数的嵌套

  2. 内部函数要引用外部函数中的变量

  3. 内部函数要作为返回值返回

/* 
创建一个函数,第一次调用时打印1,第二次调用打印2,以此类推
可以利用函数,来隐藏不希望被外部访问到的变量
*/

function outer(){
    let num = 0 // 位于函数作用域中

    return () => {
        num++
        console.log(num)
    }
}

    const newFn = outer()

    // console.log(newFn)
    newFn()

>闭包原理

函数在作用域,在函数创建时就已经确定的(词法作用域),和调用的位置无关。

闭包利用的就是词法作用域。

function fn(){
    console.log(a)
}


function fn2(){
    let a = "fn2中的a"

    fn()
}

// fn2()


function fn3(){
    let a = "fn3中的a"

    function fn4(){
        console.log(a)
    }

    return fn4
}

let fn4 = fn3()

fn4()

>闭包的注意事项

闭包的生命周期:

  • 闭包在外部函数调用时产生,外部函数每次调用都会产生一个全新的闭包。

  • 在内部函数丢失时销毁(内部函数被垃圾回收了,闭包才会消失)。

注意事项:

  • 闭包主要用来隐藏一些不希望被外部访问的内容,这就意味着闭包需要占用一定的内存空间。

  • 相对于类来说,闭包比较浪费内存空间(类可以使用原型而闭包不能),
    需要执行次数较少时,使用闭包,需要大量创建实例时,使用类。

function outer2(){
    let num = 0
    return () => {
        num++
        console.log(num)
    }
}

let fn1 = outer2() // 独立闭包
let fn2 = outer2() // 独立闭包

fn1()
fn2()

fn1 = null
fn2 = null

>递归

调用自身函数称为递归函数,递归的作用和循环是基本一致的.

编写递归函数,一定要包含两个要件:

  • 基线条件 —— 递归的终止条件

  • 递归条件 —— 如何对问题进行拆分

// 创建一个函数,可以用来求任意数的阶乘
/* 
    1! 1
    2! 1 x 2 = 2
    3! 1 x 2 x 3 = 6
    ...
    10! 1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9 x 10 = xxx

    如果用递归来解决阶乘的问题?
        5! = 4! x 5
        4! = 3! x 4
        3! = 2! x 3
        2! = 1! x 2
        1! = 1
*/

function jieCheng(num){
    // 创建一个变量用了记录结果
    let result = 1

    for(let i=2; i<=num; i++){
        result *= i
    }

    return result
}

let result = jieCheng(3)


/*
    递归的作用和循环是一致的,不同点在于,递归思路的比较清晰简洁,循环的执行性能比较好
        在开发中,一般的问题都可以通过循环解决,也是尽量去使用循环,少用递归
        只在一些使用循环解决比较麻烦的场景下,才使用递归
*/


function jieCheng2(num){

    // 基线条件
    if(num === 1){
        return 1
    }

    // 递归条件
    // num! = (num-1)! * num
    return jieCheng2(num-1) * num

}

result = jieCheng2(5)
/* 
    jieCheng2(5)
        - return jieCheng2(4) * 5
            - return jieCheng2(3) * 4
            - return jieCheng2(2) * 3
            - return jieCheng2(1) * 2
                - return 1
*/

console.log(result)

>递归练习

/* 
    一对兔子出生后的两个月后每个月都能生一对小兔子
        - 编写一个函数,可以用来计算第n个月的兔子的数量

    1   2   3   4   5   6   7   8   9   10  11  12
    1   1   2   3   5   8   13  21  34 ....
    - 规律,当前数等于前两个数之和(斐波那契数列)
*/

// 求斐波那契数列中的第n个数
function fib(n) {
    // 确定基线条件
    if (n < 3) {
        return 1
    }

    // 设置递归条件
    // 第n个数 = 第n-1个数 + 第n-2个数
    return fib(n - 1) + fib(n - 2)
}

let result = fib(10)

console.log(result)

>数组的方法(高阶函数)

sort() —— 排序[破坏性]

sort用来对数组进行排序(会改变原数组)。

sort默认会按照 Unicode 编码进行排序,所以如果直接通过 sort 对数字进行排序,可能会得到一个不正确的结果。

/* 
    参数:
      - 可以传递一个回调函数作为参数,通过回调函数来指定排序规则
      - (a, b) => a - b 升序排列
      - (a, b) => b - a 降序排列  
*/ 

let arr = ["a", "c", "e", "f", "d", "b"]
arr = [2, 3, 1, 9, 0, 4, 5, 7, 8, 6, 10]
arr.sort()

forEach() —— 用来遍历数组

forEach 需要一个回调函数作为参数。

数组中有几个元素,就会被调用几次;每次调用,都会将数组中的数据作为参数传递。

/* 
    参数:
      - element 当前的元素
      - index 当前元素的索引
      - array 当前的数组
*/ 

arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]

arr.forEach((element, index, array) => {
    console.log(array)
})

arr.forEach((element, index) => console.log(index, element))

filter() —— 将符合条件的数组保存到新数组并返回

filter 需要一个回调函数作为参数。

会为每一个元素去调用回调函数,并根据返回值来决定是否将元素添加到新数组中。

filter()是一个非破坏性方法,不会影响原数组。

/* 
    参数:
      - element 当前的元素
      - index 当前元素的索引
      - array 当前的数组
*/ 

arr = [1, 2, 3, 4, 5, 6, 7, 8]

// 获取数组中的所有偶数
let result = arr.filter((ele) => ele % 2 === 0)

result = arr.map((ele) => ele * 2)

map() —— 根据当前数组生成一个新数组

map() 需要一个回调函数作为参数。

回调函数的返回值会成为新数组中的元素。

map() 是一个非破坏性方法,不会影响原数组。

/* 
    参数:
      - element 当前的元素
      - index 当前元素的索引
      - array 当前的数组
*/ 

result = arr.map((ele) => ele * 2)

arr = ["孙悟空", "猪八戒", "沙和尚"]

result = arr.map((ele) => "<li>" + ele + "</li>")

reduce() —— 将数组中的元素整合成一个值

reduce()需要一个回调函数作为参数。

reduce()需要两个参数:

  1. 回调函数,通过回调函数来指定合并的规则

  2. 可选参数,初始值

arr = [1, 2, 3, 4, 5, 6, 7, 8]

result = arr.reduce((a, b) => {
    /* 
        1, 2
        3, 3
        6, 4
        10, 5
    
    */
    // console.log(a, b)

    return a * b
})

// result = arr.reduce((a, b) => a + b, 10)


console.log(result)

>可变参数

arguments 是函数中有一个隐含参数。

   /* 
        arguments
            - arguments是函数中又一个隐含参数
            - arguments是一个类数组对象(伪数组)
                和数组相似,可以通过索引来读取元素,也可以通过for循环变量,但是它不是一个数组对象,不能调用数组的方法
            - arguments用来存储函数的实参,
                无论用户是否定义形参,实参都会存储到arguments对象中
                可以通过该对象直接访问实参
    */

    // console.log(arguments[2])
    // console.log(Array.isArray(arguments))
    // for (let i = 0; i < arguments.length; i++) {
    //     console.log(arguments[i])
    // }

    // for(let v of arguments){
    //     console.log(v)
    // }

    arguments.forEach((ele) => console.log(ele))
}

// fn(1, 10, 33)

// 定义一个函数,可以求任意个数值的和
function sum() {
    // 通过arguments,可以不受参数数量的限制更加灵活的创建函数
    let result = 0

    for (let num of arguments) {
        result += num
    }

    return result
}

可变参数可以接收任意数量实参,并将他们统一存储到一个数组中返回。

/* 
    可变参数,在定义函数时可以将参数指定为可变参数
        - 可变参数可以接收任意数量实参,并将他们统一存储到一个数组中返回
        - 可变参数的作用和arguments基本是一致,但是也具有一些不同点:
            1. 可变参数的名字可以自己指定
            2. 可变参数就是一个数组,可以直接使用数组的方法
            3. 可变参数可以配合其他参数一起使用
*/

function fn2(...abc) {
    console.log(abc)
}

function sum2(...num) {
    return num.reduce((a, b) => a + b, 0)
}

// 当可变参数和普通参数一起使用时,需要将可变参数写到最后
function fn3(a, b, ...args) {
    // for (let v of arguments) {
    //     console.log(v)
    // }

    console.log(args)
}

fn3(123, 456, "hello", true, "1111")

>函数补充

根据函数调用方式的不同,this的值也不同:

  • 以函数形式调用,this是window

  • 以方法形式调用,this是调用方法的对象

  • 构造函数中,this是新建的对象

  • 箭头函数没有自己的this,由外层作用域决定

  • 通过call和apply调用的函数,它们的第一个参数就是函数的this

  • 通过bind返回的函数,this由bind第一个参数决定(无法修改)

function fn() {
    console.log("函数执行了~", this)
}

const obj = { name: "孙悟空", fn }

/* 
    调用函数除了通过 函数() 这种形式外,还可以通过其他的方式来调用函数
        比如,我们可以通过调用函数的call()和apply()来个方法来调用函数
            函数.call()
            函数.apply()
            - call 和 apply除了可以调用函数,还可以用来指定函数中的this
            - call和apply的第一个参数,将会成为函数的this
            - 通过call方法调用函数,函数的实参直接在第一个参数后一个一个的列出来
            - 通过apply方法调用函数,函数的实参需要通过一个数组传递
*/

// fn.call(obj)
// fn.apply(console)

function fn2(a, b) {
    console.log("a =", a, "b =", b, this)
}

// fn2.call(obj, "hello", true)
fn2.apply(obj, ["hello", true])
/* 
    bind() 是函数的方法,可以用来创建一个新的函数
        - bind可以为新函数绑定this
        - bind可以为新函数绑定参数

    箭头函数没有自身的this,它的this由外层作用域决定,
        - 也无法通过call apply 和 bind修改它的this 
        - 箭头函数中没有arguments
*/

function fn(a, b, c) {
    console.log("fn执行了~~~~", this)
    console.log(a, b, c)
}

const obj = {name:"孙悟空"}

const newFn = fn.bind(obj, 10, 20, 30)

// newFn()


const arrowFn = () => {
    console.log(this)
}

// arrowFn.call(obj)

const newArrowFn = arrowFn.bind(obj)

// newArrowFn()

class MyClass{
    fn = () => {
        console.log(this)
    }
}

const mc = new MyClass()

// mc.fn.call(window)