JS 高阶函数
海拥 人气:01、前言
在JavaScript中,函数实际上也是一个数据,也就是说函数也可以赋值给一个变量。本篇文章就来介绍一些JavaScript中的高阶函数的用法。
2、递归
所谓的递归,就是指函数自己调用自己;用一个故事来说呢就是:从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?“从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?‘从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?……'” 。
从某种意义上说,递归近似于循环。两者都重复执行相同的代码,并且两者都需要一个终止条件,以避免无限循环或者无限递归。
递归所需要的必要条件如下:
- 子问题跟原始问题一样,且更为简单
- 必须有个出口
在JavaScript中想要递归调用存在两种方式
- 通过使用函数名实现
- 通过使用arguments.callee属性实现。
如下代码展示了简单的递归:
var v = 1 // 出口条件 function fun() { console.log('第' + v + '次调用函数') v++ if (v <= 3) { fun() } } fun()
执行结果如下
第1次调用函数
第2次调用函数
第3次调用函数
3、回调函数
然函数与任何可以被赋值给变量的数据是相同的,那么它当然可以像其他数据那样被定义、删除、拷贝,以及当成参数传递给其他函数。
当一个函数作为参数传递给另一个函数时,作为参数的函数被称之为回调函数。作为使用回调函数的函数称为目标函数(外层函数)
示例代码如下所示:
// 定义一个函数,这个函数有两个函数类型的参数,然后分别执行那两个函数,并返回它们的和。 function sum(a, b) { // 目标函数 return a() + b() } function one() { // 回调函数 return 1 } function two() { // 回调函数 return 2 } console.log(sum(one, two)) // 3
代码执行流程如下:
当执行sum
函数时,传入两个实参,在sum
函数中,会将两个实参作为函数执行,并将返回值计算并返回。
3.1匿名回调函数
所谓匿名回调函数,就是目标函数中的参数是没有名称的函数,将上一段代码修改为使用匿名回调函数
// 定义一个函数,这个函数有两个函数类型的参数,然后分别执行那两个函数,并返回它们的和。 function sum(a, b) { // 目标函数 return a() + b() } console.log( sum( function () { // 匿名回调函数 return 1 }, function () { // 匿名回调函数 return 2 }, ), ) // 3
3.2带参数的回调函数
回调函数是可以增加参数的,示例代码如下:
function multiplyByTwo(list, callback) { list.forEach(function (v, i) { callback(v * 2, i) }) } var list = [1, 2, 3] multiplyByTwo(list, function (v, i) { list[i] = v }) console.log(list) // [ 2, 4, 6 ]
3.3回调函数的优缺点
- 匿名回调函数节省了全局命名空间
- 将私有的数据内容开放给指定位置使用
- 保证封装性——虽然可以使用私有数据,但是并不知道来源
- 有助于提升性能
但是回调函数也是有缺点的,当目标函数的参数是一个回调函数时,回调函数的参数又是另一个回调函数,另一个回调函数的参数还是一个回调函数…也就是套娃,也就形成了回调陷阱,严重一点可以说回调地狱。
4、自调函数
所谓的自调函数,就是定义后立即调用的函数,示例代码如下所示:
;(function () { console.log('自调函数') })()
这种语法看上去有点唬人,其实没有什么,我们只需将匿名函数的定义放进一对括号中,然后外面再紧跟一对括号即可。
语法结构如下图所示:
自调函数除了以上两种方式外,还有以下几种不常用的方式,示例代码如下:
;+(function (v) { // 形参 var w = 100 // 局部变量 console.log('自调函数' + v) })(1) // 实参 !(function (v) { var w = 100 // 局部变量 console.log('自调函数' + v) })(2) ~(function (v) { var w = 100 // 局部变量 console.log('自调函数' + v) })(3)
使用自调匿名函数的好处是不会产生任何全局变量。
缺点在于这样的函数是无法重复执行的(除非将它放在某个循环或其他函数中)。这也使得即时函数非常适合于执行一些一次性的或初始化的任务。
5、为值的函数
将一个函数作为另一个函数的结果并返回,作为结果返回的函数称之为作为值的函数。
示例代码如下:
function outer() { var v = 100 // 在函数的函数体中定义另一个函数 -> 内部(私有)函数 return function () { // 使用匿名函数 return v * 2 } } var result = outer() console.log(result) // [Function]
这样做的好处是:
- 有助于我们确保全局名字空间的纯净性(这意味着命名冲突的机会很小)。
- 确保私有性 — 这使我们可以选择只将一些必要的函数暴露给“外部世界”,而保留属于自己的函数,使它们不为该应用程序的其他部分所用。
6、闭包
闭包是在函数中提出的概念,简单来说就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。
实际上闭包可以看做一种更加广义的函数概念。因为其已经不再是传统意义上定义的函数。
闭包的条件:
- 外部函数中定义了内部函数。
- 外部函数是具有返回值,且返回值为内部函数。
- 内部函数还引用了外部函数的变量。
闭包的缺点:
- 作用域没有那么直观。
- 因为变量不会被垃圾回收所以有一定的内存占用问题。
闭包的作用:
- 可以使用同级的作用域。
- 读取其他元素的内部变量。
- 延长作用域。
- 避免污染全局变量
闭包的原理:
我们可以将函数的执行分成两个阶段,即预编译阶段和执行阶段;
- 在预编译阶段,如果发现内部函数使用了外部函数的变量,它就会在内存中 创建一个闭包对象并保存相对应的值,如果已经存在闭包,则只需要增加对应属性值即可。
- 在执行完成后,函数执行上下文会被校徽,函数对闭包对象的引用也会被销毁,但其内部函数还持有该闭包的引用,所以内部函数还可以继续使用外部函数的变量
闭包主要是利用作用域链的特性,一个函数内部定义的函数会将包含该函数的活动对象添加到自己本身的作用域链中,函数执行完毕,其执行作用域链销毁,但因内部函数的作用域链仍然引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被销毁后这些活动对象才会被销毁。
闭包的实现的demo:
// 1. 通过返回的内部函数来操作函数中的局部变量 function fun () { var v = 100; // 局部变量 // 通过返回一个对象的方式访问局部变量v 来完成闭包 return { set: function (x) { v = x; }, get: function () { return v } } } var result = fun(); result.set(200) console.log(result.get()); // 200
// 2. 定义一个局部变量,计算该函数一共调用几次 var generate_count = function () { var container = 0; return function () { container++ console.log(`这是第${container}次调用`); } } var result = generate_count(); result(); // 这是第1次调用 result(); // 这是第2次调用 result(); // 这是第3次调用
// 3.修改 Math.pow() 函数,让求一个数的平方或者立方时,不需要每次传递第二个参数 /* Math.pow(4, 2) // 求4的平方 Math.pow(4, 3) // 求4的立方 */ // 写一个函数生成器 function makePower (power) { return (number) => { return Math.pow(number, power) } } // 平方 let power2 = makePower(2) // 立方 let power3 = makePower(3) // 求4的平方 console.log(power2(4)) // 16 // 求4的立方 console.log(power3(4)) // 62
总结:
本篇文章介绍了JavaScript中的5个高阶函数,分别是递归、回调函数、自调函数、作为值得函数以及闭包的使用以及实现。
加载全部内容