目录
一、递归
递归
- 调用自身的函数称为递归函数
- 递归的作用和循环基本一致
递归的核心思想就是将一个大的问题拆分为一个一个小的问题,小的问题解决了,大的问题也就解决了
编写递归函数,一定要包含两个要件:
1. 基线条件 —— 递归的终止条件
2. 递归条件 ——如何对问题进行拆分
递归的作用和循环是一致的,不同点在于,递归的思路比较清晰简洁,但循环的执行性能比较好(因为在递归中,一直在调用自己,但每次调用函数都会产生函数作用域,会占内存同时也会耗费时间)
在开发中,一般的问题都可以通过循环解决,也就是尽量使用循环,少用递归
只在一些使用循环解决比较麻烦的场景下,才使用递归。
<script>
// 创建一个函数,可以用来求任意数的阶乘
// 如果用递归来解决阶乘问题
function jieCheng(num){
// 基线条件
if(num === 1){
return 1
}
// 递归条件
return num * jieCheng(num-1)
}
let result = jieCheng(5)
/*
jieCheng(5)
-return 5 * jieCheng(4)
-return 4 * jieCheng(3)
-return 3 * jieCheng(2)
-return 2 * jieCheng(1)
-return 1
*/
console.log(result);
</script>
二、递归的练习
一对兔子出生后的两个月后,每个月都能生一对小兔子
编写一个函数,可以用来计算第 n 个月的兔子数量
月份 1 2 3 4 5 6 7 8 9 10 11
对数 1 1 2 3 5 8 13 21 34 55 ......
- 规律: 当前数等于前两个数之和(斐波那契数列)
也就是求斐波那契数列中的第 n 个数
<script>
function fib(n){
// 确定基线条件
if(n === 1 || n === 2){
return 2
}
// 设置递归条件
// 第 n 个数 = 第 n-1个数 + 第 n-2个数
return fib(n-1) + fib(n-2)
}
let result = fib(8)
console.log(result);
</script>
三、数组的方法
sort( )
- sort 用来对数组进行排序(会改变原数组,即破坏性方法)
- sort 默认会将数组升序排列
注意:sort 默认会按照Unicode 编码进行排序,所以如果直接通过 sort 对数字进行排序
可能会得到一个不正确的结果
- 参数:
- 可以传递一个回调函数作为参数,通过回调函数来指定排序规则
(a , b)=> a - b 升序排列
(a , b)=> b - a 降序排列
<script>
let arr = ['a','c','e','f','d','b']
console.log(arr);//打印['a', 'c', 'e', 'f', 'd', 'b']
arr.sort()
console.log(arr);//打印['a', 'b', 'c', 'd', 'e', 'f']
arr = [2,4,6,3,1,8,9,0,10]
arr.sort()
console.log(arr);//[0, 1, 10, 2, 3, 4, 6, 8, 9]
arr.sort((a,b)=> a - b)
console.log(arr);//升序排列[0, 1, 2, 3, 4, 6, 8, 9, 10]
arr.sort((a,b)=> b - a)
console.log(arr);//降序排列, [10, 9, 8, 6, 4, 3, 2, 1, 0]
</script>
forEach( )、filter( )、map( )均需要回调函数,且回调函数中都有三个参数。
forEach( )
- 用来遍历数组
- 它需要一个回调函数作为参数,这个回调函数会被调用多次
数组中有几个元素,回调函数就会调用几次
每次调用,都会将数组中的元素作为参数
- 回调函数中有三个参数:
element 当前的元素,第一个参数
index 当前元素的索引,第二个参数
array 被遍历的数组,第三个参数
- 非破坏性方法,不影响原数组
<script>
let arr = ['孙悟空','猪八戒','沙和尚']
arr.forEach((element,index,array)=>{
console.log(element,index,array);
})
arr.forEach((element)=>{//只需要元素
console.log(element);
})
</script>
filter( )
- 将数组中符合条件的元素保存到一个新数组中(非破坏性方法)
- 需要一个回调函数作为参数,会为每一个元素去调用回调函数,并根据返回值来决定是否将元素添加到新数组中,返回值为真则添加,否则不添加;注意回调函数中与 forEach( ) 一样也有三个参数
- 非破坏性方法,不会影响原数组
<script>
let arr = [1,2,3,4,5,6,7,8]
// 获取数组中的所有偶数
let result = arr.filter((ele)=>
// ele > 5
ele % 2 === 0
)
console.log(result);//[2, 4, 6, 8]
result = arr.filter((ele)=>{
return ele > 4
})
console.log(result);// [5, 6, 7, 8]
</script>
map( )
- 根据当前数组生成一个新数组(非破坏性方法)
- 需要一个回调函数作为参数,
回调函数的返回值会成为新数组中的元素
关键是根据数组的值生成一个新数组
- 非破坏性方法,不会影响原数组
<script>
let arr = [1,2,3,4,5,6,7,8]
let result = arr.map((ele)=>123)
console.log(result);//(8)[123, 123, 123, 123, 123, 123, 123, 123]
result = arr.map(ele => ele * 2)
console.log(result);//(8) [2, 4, 6, 8, 10, 12, 14, 16]
// 想为如下数组加上标签
arr = ['孙悟空','猪八戒','沙和尚']
result = arr.map(ele => '<li>' + ele + '</li>')
console.log(result);//(3) ['<li>孙悟空</li>', '<li>猪八戒</li>', '<li>沙和尚</li>']
</script>
reduce( )
- 可以用来将一个数组中的所有元素整合为一个值
- 也需要使用回调函数作为参数
-参数:
1.回调函数,通过回调函数来指定合并的规则,回调函数有两个参数,分别表示第一个值或者返回值,第二个参数表示数组后面的值
2.可选参数,初始值,在返回值处
-依次执行”回调函数,并传入前一个元素上的计算返回值。第一次运行回调函数时,没有“前一次计算的返回值”。如果提供,则可以使用初值代替其位置。否则,索引为0的数组元素被用作初始值,迭代从下一个元素开始(索引1而不是索引0)
<script>
let arr = [1,2,3,4,5,6,7,8]
let result = arr.reduce((a,b)=>{
/*
a,b
1,2
3,3
6,4
10,5
以此类推
*/
console.log(a,b);
return a + b
})
console.log(result);//36
result = arr.reduce((a,b)=> a+b,10)//10为初始值
//10 + 1 + 2+ 3+ 4+ 5+ 6+ 7+ 8
console.log(result);//46
</script>
四、可变参数
arguments
-arguments 是函数中又一个隐含参数(另一个隐含参数是this)
- arguments 是一个类数组对象(伪数组)
和数组相似,可以使用 .length 得到实参的数量,可以通过索引来读取元素,也可以通过for循环遍历,但它不是一个数组对象,不能调用数组的方法。
- arguments 用来存储函数的实参,
无论用户是否定义形参,实参都会存储到arguments对象中
可以通过该对象直接访问实参
<script>
function fn(){
// console.log(arguments.length);
// console.log(arguments[0]);
console.log(Array.isArray(arguments));//false
for(let i=0;i<arguments.length;i++){//for循环遍历
console.log(arguments[i]);
}
for(let v of arguments){//for- of遍历
console.log(v);
}
}
fn(10,2,3)
</script>
如当我们想定义一个函数,求任意个数值的和时,可以通过使用arguments,不受参数数量的限制更加灵活的创建函数。
<script>
//定义一个函数,可以求任意个数值的和
function sum(){
let result = 0
// 通过arguments,可以不受参数数量的限制更加灵活的创建函数
for(let num of arguments){
result += num
}
return result
}
let result = sum(1,3,5)
console.log(result);//9
</script>
arguments 缺点:
1. 有些函数不是我们定义的,以为其是不需要参数的,直接调就行,但其实需要我们传入实参,也就是不知道需不需要传实参
2. arguments不是一个数组,不能使用数组的方法,如想为实参做一个累加,不能使用reduce( ) 方法,必须遍历
可变参数,在定义函数时可以将参数指定为可变参数。
- 可变参数使用 ...参数名 定义
- 可变参数可以接收任意数量的实参,并将它们统一存储到一个数组中返回
- 可变参数的作用和arguments 基本一致,但是也有一些不同点:
1.可变参数的名字可以自己指定
2. 可变参数就是一个数组,可以直接使用数组的方法
3. 可变参数可以配合其他参数一起使用
注意:当可变参数和普通参数一起使用时,需要将可变参数写到最后
<script>
// 可变参数
function fn(...args){
console.log(args);
}
fn()//[]
fn(1)//[1]
fn('hello',123,true)//(3) ['hello', 123, true]
// 可变参数可以直接使用数组的方法
function sum(...num){
return num.reduce((a,b)=> a + b, 0)//直接使用数组方法reduce( )
}
let result = sum(2,5,7)
console.log(result);//14
// 可变参数可以配合其他参数一起使用
// 当可变参数和普通参数一起使用时,需要将可变参数写到最后
function fn2(a,b,...args){
console.log(args);
}
fn2(12,67,'fff','hello')//(2) ['fff', 'hello']
</script>
五、call 和 apply
根据函数调用方式的不同,this 的值也不同:
1. 以函数形式调用,this 是window
2. 以方法形式调用,this 是调用方法的对象
3. 构造函数中,this 是新建的对象
4. 箭头函数没有自己的this, 由外层作用域决定
5. 通过call 和 apply 调用函数,它们的第一个参数就是函数的 this
调用函数除了通过 函数( )这种形式外,还可以通过其他的方式来调用函数
比如,我们可以通过调用函数的call( ) 和 apply( ) 两个方法来调用函数
函数.call( )
函数.apply( )
- call 和 apply 除了可以调用函数外,还可以用来指定函数中的 this
- call 和 apply 的第一个参数,将会成为函数的 this
<script>
function fn(){''
console.log('函数执行了~',this);
}
fn()//以函数形式调用,this 是window
const obj = {
name:'孙悟空',
fn //把fn 函数设置为obj的属性
}
obj.fn()//函数执行了~ {name: '孙悟空', fn: ƒ},因为以方法调用,this 是调用方法的对象
fn.call()//函数执行了~ Window,this 为window
fn.call(obj)//函数执行了~ {name: '孙悟空', fn: ƒ},this为对象
fn.call(console)//函数执行了~ console,this为console
// fn.apply()
</script>
call 和 apply 的区别:
- 通过call 方法调用函数,函数的实参直接在第一个参数后一个一个的列出来 - 通过apply 方法调用函数,函数的实参需要通过一个数组传递
<script>
function fn(){''
console.log('函数执行了~',this);
}
const obj = {
name:'孙悟空',
fn //把fn 函数设置为obj的属性
}
function fn2(a,b){
console.log('a =', a,'b = ', b, this);
}
fn2(12,3)//a = 12 b = 3 Window
//call调用函数时,实参在第一个参数后一个一个列举出来
fn2.call(obj,1,2)//a = 1 b = 2 {name: '孙悟空', fn: ƒ}
//apply调用函数时,实参通过一个数组传递
fn2.apply(obj,['hello',true])//a = hello b = true {name: '孙悟空', fn: ƒ}
</script>
六、bind
bind( ) 是函数的方法,可以用来创建一个新的函数
- bind可以为新函数绑定 this,绑定就是无论用什么方式调用,都始终是这个this
- bind 可以为新函数绑定参数
- bind第一个参数为this,之后为相应的实参
bind为新函数绑定this的例子
<script>
function fn(){
console.log('fn执行了~~~',this);
}
// fn.call()
const obj = {name:'孙悟空'}
//bind为新函数绑定this
const newFn = fn.bind(obj)
newFn()//fn执行了~~~ {name: '孙悟空'},this是obj
newFn.call({})//fn执行了~~~ {name: '孙悟空'},this仍然还是obj
</script>
bind 为新函数绑定参数的例子:
<script>
function fn(a,b,c){
console.log('fn执行了~~~',this);
console.log(a,b,c);
}
const obj = {name:'孙悟空'}
const newFn = fn.bind(obj,1,2,3)
newFn()//fn执行了~~~ {name: '孙悟空'} 1 2 3
newFn.call(345,2,3)//fn执行了~~~ {name: '孙悟空'} 1 2 3,this和实参已经绑定了
</script>
根据函数调用方式的不同,this 的值也不同:
1. 以函数形式调用,this 是window
2. 以方法形式调用,this 是调用方法的对象
3. 构造函数中,this 是新建的对象
4. 箭头函数没有自己的this, 由外层作用域决定
5. 通过call 和 apply 调用函数,它们的第一个参数就是函数的 this
6. 通过bind 返回的函数,this 由 bind第一个参数决定(无法修改),与其调用方式无关
箭头函数没有自身的this,它的this由外层作用域决定,
也无法通过call apply 和 bind 修改它的 this
<script>
function fn(a,b,c){
console.log('fn执行了~~~',this);
console.log(a,b,c);
}
const obj = {name:'孙悟空'}
const newFn = fn.bind(obj,1,2,3)
const arrowFn = ()=>{
console.log(this);
}
arrowFn()//Window,箭头函数的this由外层作用域决定
arrowFn.apply(obj)//Window,箭头函数的this由外层作用域决定,无法通过apply修改
const newArrowFn = arrowFn.bind(obj)
newArrowFn()//Window,箭头函数的this由外层作用域决定,无法通过bind修改
class MyClass{
fn = ()=>{
console.log(this);
}
}
const mc = new MyClass()
mc.fn()//this是MyClass {fn: ƒ},箭头函数的this由外层作用域决定
</script>
箭头函数中没有arguments
<script>
const arrowFn = ()=>{
console.log(arguments);
}
arrowFn()//报错,Uncaught ReferenceError: arguments is not defined
// at arrowFn
</script>