九 JavaScript高阶函数

九 高阶函数

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

将函数作为参数,意味着可以对另一个函数动态的传递代码

9.1 实例

9.1.1 实例1

定义一个函数,该函数可以实现根据需求动态打印数组中的数据

let arr = [1,2,3,4,5,6,7]
// 定义一个judge函数
function judge(arr,Condition){ // 接收一个数组对象和一个函数对象
    for (let i = 0; i < arr.length; i++) {
        if(Condition(arr[i])){ // if的判断条件是一个函数的返回值
            console.log(arr[i]);
        }
    }
}
judge(arr,(x)=>x>3) // 传入一个数组和一个函数,该函数的返回值是false或者true,作为for循环的判断条件

9.1.2 实例2

实现函数的日志功能

也就那个意思,看不懂不看,我也是自己瞎写的。

function judge(arr,Condition){
    for (let i = 0; i < arr.length; i++) {
        if(Condition(arr[i])){
            console.log(arr[i]);
        }
    }
}

function outer(Func,...condition){
    return ()=>{
        console.log("记录日志");
        const res = Func(...condition)
        return res
    }
}

let fn = outer(judge,[1,2,3,4,5,6,7],(x)=>x<3)
fn()

9.2 闭包

9.2.1 引子

题目:请设计一个函数,第一次调用时打印1,第二次调用打印2,以此类推

let num = 0
function fn(){
    num++
    console.log(num);
}
fn() // 1
fn() // 2
num = 100
fn() // 101

当我们调用函数fn()时能够正确打印出num的值,且num的值会自增。

但该num是处于全局作用域中,这在实际生产环境中是十分危险的。所以就产生了一种成为闭包的实践。

闭包:可以利用函数来隐藏不希望被外部访问到的变量,闭包就是能访问到外部函数作用域中变量的函数。

所以产生闭包需要两个条件:

  1. 函数嵌套(外层函数使函数内部的变量不会被外部访问,内层函数实现变量的修改)
  2. 内部函数要引用外部函数中的变量
  3. 内部函数要作为返回值返回
let num = 0
function outer(){
    let num = 0
    return ()=>{
        num++
        console.log(num);
    }
}
const fn = outer()
fn() // 1
fn() // 2
outer.num = 100
num = 100
fn() // 3

上诉写法实现了我们的要求。

9.3 词法作用域

观察以下代码,你能猜到输出是什么吗?

var a = 1;
function bar(){
 console.log(a);
}
function foo(){
 var a = 2;
 bar();
}
foo();

输出1还是输出2呢?答案是1,说明bar函数不是从foo函数的作用域查找的变量a,而是从全局作用域中获取的a的值。

所以bar函数的上层作用域在定义的时候就已经确定了 ,不会随着调用地点的不同而改变。这就成为词法作用域,又叫做静态作用域。与之相对的叫做动态作用域。

参考链接:

JavaScript词法作用域 - 知乎 (zhihu.com)

闭包利用的就是词法作用域,通常使用静态作用域的编程语言都能实现闭包…吧。

9.4 闭包的注意事项

闭包的生命周期:

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

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

注意事项:

  • 闭包主要用来隐藏一些不希望被外部访问的内容,这就意味着闭包需要占用一定的内存空间。相较于类来说,闭包比较浪费内存空间(类可以使用原型而闭包不能)

  • 需要执行次数较少时,使用闭包

  • 需要大量创建实例时,使用类

9.5 递归

递归是一种函数自己调用自己的方式。

自己调用自己绝对不可以一直调用下去直到把内存撑满,所以递归一定有一个结束的判断,称为递归的终止条件。

9.5.1 练习1

创建一个函数,可以用来求任意数的阶乘

例如:

3的阶乘 = 3*2*1

4的阶乘 = 4*3*2*1

比较容易的想法是使用一个循环:

function foo(n){
    let res = 1
    for (let i = 2; i <= n; i++) {
        res = res * i
    }
    return res
}
console.log(foo(4));

如果用递归来解决阶乘的问题?

5! = 4! x 5
4! = 3! x 4
3! = 2! x 3
2! = 1! x 2
1! = 1

递归的核心思想就是将一个大的问题拆分为一个一个小的问题,小的问题解决了,大的问题也就解决了

递归的作用和循环是一致的,不同点在于,递归思路的比较清晰简洁,循环的执行性能比较好

在开发中,一般的问题都可以通过循环解决,也是尽量去使用循环,少用递归

只在一些使用循环解决比较麻烦的场景下,才使用递归

感觉这里使用递归也不是很好… …

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)

9.5.2 练习2

由不死神兔引出对递归问题的思考 - 知乎 (zhihu.com)

9.6 可变参数

arguments 是一个对应于传递给函数的参数的类数组对象。

类数组对象我记得之前已经写了,再写一遍:

和数组相似,可以通过索引来读取元素,也可以通过for循环变量,但是它不是一个数组对象,不能调用数组的方法

// 类似如下的对象:
{
    length:3
    0:"你好,世界!"
    1:"Hello"
    2:"World"
}

实践:

function test(a,b,c){
    console.log("a:",a);
    console.log("b:",b);
    console.log("c:",c);
    console.log("arguments[0]:",arguments[0]);
    console.log("arguments[1]:",arguments[1]);
    console.log("arguments[2]:",arguments[2]);
}
test(1,2,3)
/*
a: 1
b: 2
c: 3
arguments[0]: 1
arguments[1]: 2
arguments[2]: 3
*/

当我们把形参删除,同时注释掉形参的打印语句:

function test(){
    // console.log("a:",a);
    // console.log("b:",b);
    // console.log("c:",c);
    console.log("arguments[0]:",arguments[0]);
    console.log("arguments[1]:",arguments[1]);
    console.log("arguments[2]:",arguments[2]);
    // 
}
test(1,2,3)

发现实参能够正常打印,相信你已经懂了argumens的用法了,但是挺无聊的。

他还有一个length属性可以打印

function test(){
    console.log(arguments.length);
}
test(1,2,3) // 3

s如果编写兼容ES6的代码,推荐使用剩余参数,写法如下所示:

function test(...argus){
    console.log(argus);
}
test(1,2,3) // [1,2,3]

9.7 函数的this

9.7.1 调用方式

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

  • 以函数形式调用,this是window
  • 以方法形式调用,this是调用方法的对象
  • 构造函数中,this是新建的对象
  • 箭头函数没有自己的this,由外层作用域决定
  • 通过call和apply调用的函数,它们的第一个参数就是函数的this
function test(){
    console.log(this);
}
let testObj = {
    user:"大眼神龙",
    testFn:test
}
test() // global {global: global, clearInterval: ƒ, clearTimeout: ƒ, setInterval: ƒ, setTimeout: ƒ, …}
testObj.testFn() // {user: '大眼神龙', testFn: ƒ}

9.7.2 call和apply

调用函数除了通过 函数() 这种形式外,还可以通过其他的方式来调用函数,比如,我们可以通过调用函数的call()和apply()来个方法来调用函数。

函数.call()

函数.apply()

call 和 apply除了可以调用函数,还可以用来指定函数中的this

call和apply的第一个参数,将会成为函数的this

通过call方法调用函数,函数的实参直接在第一个参数后一个一个的列出来

通过apply方法调用函数,函数的实参需要通过一个数组传递

function test(){
    console.log(this);
    console.log(arguments.length);
}
test.call({user:"大眼神龙"},1,2,3,4,5) // {user: '大眼神龙'} 5
test.apply({user:"大眼神龙"},[1,2,3,4,5]) // {user: '大眼神龙'} 5

9.7.3 bind

bind() 是函数的方法,可以用来创建一个新的函数

  • bind可以为新函数绑定this

  • bind可以为新函数绑定参数

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

function test(){
    console.log(this);
}
const newFunc = test.bind({user:"大眼神龙"})
newFunc() // {user: '大眼神龙'}

而且不能再通过call和apply修改

function test(){
    console.log(this);
}
const newFunc = test.bind({user:"大眼神龙"})
newFunc() // {user: '大眼神龙'}
newFunc.call({test:"测试"}) // {user: '大眼神龙'}
newFunc.apply({test:"测试"}) // {user: '大眼神龙'}
  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值