js语言基础(四)—— 数组、高阶函数、闭包等
说明:
一、数组简介
- 数组也是一种复合数据类型,在数组可以存储多个不同类型的数据,数组中存储的是有序的数据,数组中的每个数据都有一个唯一的索引。
- 可以通过索引来操作获取数据
- 数组中存储的数据叫做元素,索引(index)是一组大于等于0的整数
1 | //创建数组(可以通过Array()来创建数组,也可以通过[]来创建数组) |
length
获取数组的长度,获取的实际值就是数组的最大索引 + 1
利用此,可以向数组最后添加元素:
数组[数组.length] = 元素length是可以修改的
二、遍历数组
遍历数组简单理解,就是获取到数组中的每一个元素
1 | let arr = ["张三","李四","王麻子","店小二"] |
三、数组的方法
1、Array.isArray(参数)
检查一个对象是否是数组
1 | let arr = [1,2] |
2、at(索引)
- 可以根据索引获取数组中的指定元素
- at可以接收负索引作为参数
1 | let arr = [1,2,3,4] |
3、concat()
- 用来连接两个或多个数组
- 非破坏性方法,不会影响原数组,而是返回一个新的数组
1 | let arr1 = [1,2,3] |
4、indexOf() 与 lastIndexOf()
indexOf()
获取元素在数组中第一次出现的索引
参数:
- 要查询的元素
- 查询的起始位置
lastIndexOf()
获取元素在数组中最后一次出现的位置
参数:
- 要查询的元素
- 查询的起始位置
返回值:
找到了则返回元素的索引,
没有找到返回-1
1 | let arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧", "沙和尚"] |
5、join()
- 将一个数组中的元素连接为一个字符串
- 参数:
指定一个字符串作为连接符,若不指定则默认用英文逗号连接
1 | result = arr.join() //用英文逗号连接 |
6、slice()
- 用来截取数组(非破坏性方法)
- 参数:
- 截取的起始位置(包括该位置)
- 截取的结束位置(不包括该位置)
- 第二个参数可以省略不写,如果省略则会一直截取到最后
1 | let arr = [1, 2, 3, 4] |
7、push()
向数组的末尾添加一个或多个元素,并返回新的长度
1 | let arr = [1,2,3] |
8、pop()
删除并返回数组的最后一个元素
1 | let arr = [1,2,3] |
9、unshift()
向数组的开头添加一个或多个元素,并返回新的长度
1 | let arr = [1,2,3] |
10、shift()
删除并返回数组的第一个元素
1 | let arr = [1,2,3] |
11、reverse()
反转数组
1 | let arr = [1,2,3] |
12、splice()
- 可以删除、插入、替换数组中的元素
- 参数:
1、删除的起始位置
2、删除的数量
3、要插入的元素- 返回值:
返回被删除的元素
1 | let arr = [1,2,3] |
说明:后面的方法中需要用到一些回调函数的知识,可查看第六章进行了解
13、sort()
- sort用来对数组进行排序(会对改变原数组)
- sort默认会将数组升序排列
注意:sort默认会按照Unicode编码进行排序,所以如果直接通过sort对数字进行排序
可能会得到一个不正确的结果 - 参数:
可以传递一个回调函数作为参数,通过回调函数来指定排序规则
(a, b) => a - b 升序排列
(a, b) => b - a 降序排列
1 | let arr = [2, 3, 1, 9, 0, 4, 5, 7, 8, 6, 10] |
14、forEach()
- 用来遍历数组
- 它需要一个回调函数作为参数,这个回调函数会被调用多次
数组中有几个元素,回调函数就会调用几次
每次调用,都会将数组中的数据作为参数传递 - 回调函数中有三个参数:
element 当前的元素
index 当前元素的索引
array 被遍历的数组
1 | arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"] |
15、fliter()
- 将数组中符合条件的元素保存到一个新数组中返回
- 需要一个回调函数作为参数,会为每一个元素去调用回调函数,并根据返回值来决定是否将元素添加到新数组中
- 非破坏性方法,不会影响原数组
1 | arr = [1, 2, 3, 4, 5, 6, 7, 8] |
16、map()
- 根据当前数组生成一个新数组
- 需要一个回调函数作为参数,回调函数的返回值会成为新数组中的元素
- 非破坏性方法不会影响原数组
1 | arr = [1, 2, 3, 4, 5, 6, 7, 8] |
17、reduce()
可以用来将一个数组中的所有元素整合为一个值
参数:
回调函数,通过回调函数来指定合并的规则
可选参数,初始值
1 | arr = [1, 2, 3, 4, 5] |
四、深拷贝与浅拷贝
1、概念
如何去复制一个对象 复制必须要产生新的对象
1 | const arr = ["孙悟空", "猪八戒", "沙和尚"] |
浅拷贝(shallow copy)
- 通常对对象的拷贝都是浅拷贝
- 浅拷贝顾名思义,只对对象的浅层进行复制(只复制一层)
- 如果对象中存储的数据是原始值,那么拷贝的深浅是不重要
- 浅拷贝只会对对象本身进行复制,不会复制对象中的属性(或元素)
深拷贝(deep copy)
- 深拷贝指不仅复制对象本身,还复制对象中的属性和元素
- 因为性能问题,通常情况不太使用深拷贝
1 | const arr = [{name:"孙悟空"}, {name:"猪八戒"}] |
2、展开运算符(…)
可以将一个数组中的元素展开到另一个数组中或者作为函数的参数传递
通过它也可以对数组进行浅复制
1 | const arr = ["李四","王麻子"] |
3、对象的复制
Object.assign(目标对象, 被复制的对象)
将被复制对象中的属性复制到目标对象里,并将目标对象返回
也可以使用展开运算符对对象进行复制
1 | const obj = { name: "张三", age: 18 } |
五、练习:数组去重与排序
1、数组去重
1.1 最基本的方法
1 | const arr = [1, 2, 1, 3, 2, 2, 4, 5, 5, 6, 7] |
1.2、方法二
1 | // 获取数组中的元素 |
1.3、方法三
1 | const arr = [1, 2, 1, 3, 2, 2, 4, 5, 5, 6, 7] |
2、冒泡排序与选择排序
2.1、冒泡排序
1 | const arr = [9, 1, 3, 2, 8, 0, 5, 7, 6, 4] |
2.2、选择排序
1 | console.log(arr) |
六、回调函数与高阶函数
回调函数:如果将函数作为参数传递,那么我们就称这个函数为回调函数
高阶函数:高阶函数是一个 接收函数作为参数或将函数作为输出返回 的函数
1、情景
创建一个函数对数组进行过滤
1 | class Person { |
问题:
目前我们的函数只能过滤出数组中age属性小于18的对象,我们希望过滤更加灵活,在不同条件下进行过滤
这时候就需要学习一点新东西了
事实上,一个函数的参数也可以是函数,如果将函数作为参数传递,那么我们就称这个函数为回调函数(callback)
好处:将函数作为参数,意味着可以对另一个函数动态的传递代码
2、优化(动态传递代码)
1 | function filter(arr, cb) { |
3 高阶函数练习
练习
1 | /* 希望在someFn()函数执行时,可以记录一条日志 |
七、闭包
1、情景
1 | /* |
问题:创建的变量num,我们不希望被别人访问到,否则容易被修改,出问题。这时我们就要了解一下闭包的概念了
闭包就是能访问到外部函数作用域中变量的函数
什么时候使用:
当我们需要隐藏一些不希望被别人访问的内容时就可以使用闭包构成闭包的要件:
1、函数的嵌套
2、内部函数要引用外部函数中的变量
3、内部函数要作为返回值返回
2、使用闭包
1 | function outer(){ |
3、作用域问题
函数在作用域,在函数创建时就已经确定的(词法作用域),和调用的位置无关
闭包利用的就是词法作用域
1 | let a = "全局变量中的a" |
4、闭包的注意事项
- 闭包的生命周期:
闭包在外部函数调用时产生,外部函数每次调用都会产生一个全新的闭包
在内部函数丢失时销毁(内部函数被垃圾回收了,闭包才会消失)
- 注意事项:
1、闭包主要用来隐藏一些不希望被外部访问的内容, 这就意味着闭包需要占用一定的内存空间
2、相较于类来说,闭包比较浪费内存空间(类可以使用原型而闭包不能),
- 需要执行次数较少时,使用闭包
- 需要大量创建实例时,使用类
1 | function outer2(){ |
八、递归
递归的作用和循环是一致的,不同点在于,递归思路的比较清晰简洁,循环的执行性能比较好
在实际开发中,一般的问题都可以通过循环解决,尽量去使用循环,少用递归
只在一些使用循环解决比较麻烦的场景下,才使用递归
1、如何编写递归函数
把握两个条件: 1.基线条件 —— 递归的终止条件 2.递归条件 —— 如何对问题进行拆分
1 | function fact(num){ |
2、递归练习之兔子数列
兔子数列(斐波那契数列)
一对兔子出生后两个月后每个月都能生一对小兔子
例子:1 1 2 3 5 8 13 21 34 …题目:计算n个月后兔子数量
1 | function fib(n) { |
九、可变参数与arguments
但有的时候我们并不能确定到底要传入多少数量的参数,这时可以利用可变参数,可以不受参数数量的限制,从而更加灵活地创建函数
1、函数中隐含的参数——arguments
- arguments是函数中又一个隐含参数
- arguments是一个类数组对象(伪数组)
和数组相似,可以通过索引来读取元素,也可以通过for循环变量,但是它不是一个数组对象,不能调用数组的方法 - arguments用来存储函数的实参,
无论用户是否定义形参,实参都会存储到arguments对象中
另外也可以通过该对象直接访问实参
1 | //定义一个函数可以求任意数量的数值的和 |
2、使用可变参数
可变参数可以接收任意数量实参,并将他们统一存储到一个数组中返回
可变参数的作用和arguments基本是一致,但是也具有一些不同点:
1. 可变参数的名字可以自己指定 2. 可变参数就是一个数组,可以直接使用数组的方法 3. 可变参数可以配合其他参数一起使用
补充:… (展开运算符)
- 可以将一个数组中的元素展开到另一个数组中或者作为函数的参数传递
1 | function fn2(...abc) { |
- 普通参数与可变参数配合使用
- 注意:此时需要将可变参数写到最后
1 | function fn3(a, b, ...args) { |
十、改变函数的this对象
复习函数的this
函数在执行时,JS解析器每次都会传递进一个隐含的参数
这个参数就叫做 this,this会指向一个对象this所指向的对象会根据函数调用方式的不同而不同
1.以函数形式调用时,this指向的是window
2.以方法的形式调用时,this指向的是调用方法的对象
3.构造函数中,this是新建的对象
4.箭头函数没有自己的this,由外层作用域决定
5.通过call和apply调用的函数,它们的第一个参数就是函数的this
通过this可以在方法中引用调用方法的对象
箭头函数的this
箭头函数没有自身的this,它的this由外层作用域决定,
也无法通过call apply 和 bind修改它的this
箭头函数中没有arguments
1、调用函数的方式
调用函数除了通过 函数() 这种形式外,还可以通过其他的方式来调用函数
比如,我们可以通过调用函数的call()和apply()来个方法来调用函数
函数.call() 与 函数.apply()
- call 和 apply除了可以调用函数,还可以用来指定函数中的this
- call和apply的第一个参数,将会成为函数的this
- 通过call方法调用函数,函数的实参直接在第一个参数后一个一个的列出来
- 通过apply方法调用函数,函数的实参需要通过一个数组传递
1 | function fn() { |
2、bind()方法
bind() 是函数的方法,可以用来创建一个新的函数
bind可以为新函数绑定this
bind还可以为新函数绑定参数
1 | function fn(a, b, c) { |