九 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: JavaScript 中的高阶函数是指可以接受函数作为参数或返回函数作为返回值的函数。它可以帮助我们实现抽象、灵活地处理函数,是函数式编程的重要工具。 常见的 JavaScript 高阶函数包括 map、filter、reduce 等,这些函数都可以对数组进行操作,并且可以传入一个函数作为参数来定制具体的操作方式。 例如,我们可以使用 map 函数将一个数组中的所有元素都转换成另一种形式: ``` const numbers = [1, 2, 3, 4, 5]; const squared = numbers.map(n => n * n); // squared is now [1, 4, 9, 16, 25] ``` 我们也可以使用 filter 函数过滤数组中的元素: ``` const numbers = [1, 2, 3, 4, 5]; const evens = numbers.filter(n => n % 2 === 0); // evens is now [2, 4] ``` 使用 reduce 函数可以对数组中的所有元素进行累积操作: ``` const numbers = [1, 2, 3, 4, 5]; const sum = numbers.reduce((accumulator, n) => accumulator + n, 0); // sum is now 15 ``` 这些函数都是高阶函数,因为它们可以接受函数作为参数,并且本身也是函数高阶函数的使用可以帮助我们写出更加简洁、抽象的代码,是函数式编程的重要工具。 ### 回答2: JavaScript 高阶函数是指能够接收函数作为参数,并且返回一个新函数函数高阶函数JavaScript中的一种特性,它的存在使得我们可以更灵活地处理函数,并使代码更简洁、模块化。 高阶函数的应用场景有很多,其中之一是函数的装饰器。我们可以使用一个高阶函数来包装一个函数,以添加额外的功能或修改函数的行为,而无需修改原始函数的代码。这在某些情况下非常实用,例如我们可以用高阶函数函数添加性能监测、日志输出等功能。 除了函数的装饰器,高阶函数还可以用于函数的柯里化。柯里化是指将多个参数的函数转化为一个参数的函数。通过使用高阶函数,我们可以将一个多参数函数转化为一个只接收部分参数的函数,并返回一个新函数来处理剩余的参数。这样做的好处是可以更方便地复用函数,同时也使代码更加清晰。 高阶函数还可以用于函数组合。函数组合是指将多个函数组合在一起,构成一个新的函数。通过使用高阶函数,我们可以将多个函数按照特定的顺序组合起来,并返回一个新的函数来处理输入。这种方式可以使得代码更加模块化、易于维护和测试。 总之,JavaScript高阶函数是一种非常有用的特性,它可以让我们更灵活地处理函数,并使代码更加简洁和模块化。在实际开发中,熟练掌握高阶函数的应用,可以提高代码的可读性、可维护性和可复用性。 ### 回答3: JavaScript高阶函数是指能够接受函数作为参数,或者返回一个函数函数。在JavaScript中,函数可以被当作一等公民,就像其他数据类型一样,可以被赋值给变量,作为参数传递给其他函数,或者作为其他函数的返回值。 高阶函数的优点之一是,它们可以更灵活地处理代码逻辑。通过将函数作为参数传递给高阶函数,我们可以将代码逻辑抽象出来,减少代码的重复性,提高代码的可维护性和复用性。例如,我们可以编写一个高阶函数来实现数组的map、filter、reduce等操作,而不必重复编写循环和条件语句的代码。 另一个优点是,高阶函数可以实现函数的柯里化(currying)。柯里化是一种将多参数函数转换为一系列单参数函数的技术。通过将函数转换为柯里化的形式,我们可以更方便地进行函数组合、部分应用和延迟求值等操作。 除此之外,高阶函数还可以用于实现函数的装饰器(decorator)。装饰器是一种在函数被调用之前或之后执行一些额外逻辑的方法。通过将函数作为参数传递给装饰器函数,我们可以在不修改原函数的情况下,给函数添加日志记录、性能分析、异常处理等功能。 总而言之,JavaScript高阶函数是一种非常有用和强大的特性,它能够提高代码的可读性、可维护性和复用性,同时也可以用于实现函数的柯里化和装饰器等功能。因此,掌握和运用高阶函数JavaScript开发中的重要技能之一。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值