没想到函数也要单独开一章。

1 基本特点

js中函数是一等公民,是很特殊的存在。

  1. function fn(){}
  2. typeof fn // 'function'
  3. fn instanceof Object //true
  4. fn instanceof Function // true

箭头函数arrow funciton、函数递归、闭包。

箭头函数中没有 arguments/this/prototype new.target的概念,简化了不少,自然也就不能 new

在js中不在乎参数个数,但可以通过 ...args 获得参数,并针对性做出判断,判断类型和个数,来模拟实现重载。

function 会函数声明提升 function declaration hoisting

函数可以作为值进行传递。

函数内部存在 arguments/this/new.target 用来表示 函数是否通过new调用,值为被调用构造函数。

函数存在prototype JS基础之继承、原型链、面向对象

函数还有两个方法 apply/call/bind ,可以改变this的指向 JS之this和改变this

2 递归

默认递归。
尾调用优化、尾调用 Tail Call Optimization
书籍《学习Javascript数据结构与算法》 第九部分,递归

理解递归、基本应用、js调用栈

2.1 概念

直接或者间接调用自身的函数,是递归函数。

比如求 5!=5*4*3*2*1 的结果

很容易用一层for循环写出来,基本上没有难度:

  1. function fun(number=1) {
  2. if(n<0) return undefined
  3. let total = 1
  4. for(let i=number;i>1;i--){
  5. total = total * n
  6. }
  7. return total
  8. }

接下来我们分解,分解为 n * (n-1),达到类似的结果:

  1. function fun(n){
  2. if(n===1||n===0){
  3. return 1
  4. }
  5. return n * fun(n-1)
  6. }

这就是最基础的递归了。先膨胀到最大,然后逐一解决缩短调用链路。

也就是使用了栈的概念

js函数 - 图1

提到栈,一定考虑爆栈,也就是超出栈的最大长度,也叫溢出。 StackOverflowError

2.2 尾调用

因此在es6之后有了 尾调用优化Tail Call Optimization : 如果函数最后一个操作是调用函数,走的是 jump,结果是代码可以一直执行下去。https://www.chromestatus.com/feature/5516876633341952

应用场景2 斐波那契数列 0 1 2 3 5 8 后者是前两个相加。

我们使用for循环很容易写出下面的代码

  1. function fun(n){
  2. if(n<1) return 0;
  3. if(n<=2) return 1
  4. let n1=0
  5. let n2=1
  6. let nn =n
  7. for(let i=2;i<=n;i++){
  8. nn = n1 + n2
  9. n2 = n1
  10. n1 = n
  11. }
  12. return nn
  13. }

我们也可以简单改为递归。

  1. funct fun(n){
  2. // if
  3. return fun(n-1)+fun(n-2)
  4. }

顺着这个思路往后看,可以引入缓存,维护了一个数组存放结果。

  1. function fun(n){
  2. const memo = [0,1]
  3. const f = n => {
  4. if(memo[n]!=null) return memo[n]
  5. return memo[n] = f(n-1,memo) + f(n-2,memo)
  6. }
  7. return f
  8. }

??

js函数 - 图2

通过数据表明,迭代比递归。

但递归思路更清楚,有了尾调用优化,递归可以进一步优化。

外部函数的返回值是一个内部函数的返回值

  1. function 外函数(){
  2. return 内函数() // 尾调用
  3. }

es6优化之前:

  • 开始执行外函数,推栈。
  • 执行到 return,要 return 就得先计算内部函数
  • 那就执行内函数,推栈
  • 执行完内函数,给 return
  • return 拿到就丢出来
  • 弹栈

es6优化之后:

  • 开始执行外函数,推栈。
  • 执行到 return,要 return 就得先计算内部函数
  • 引擎发现把外函数弹出栈外也没问题,因为内函数的函数值会被return ,也就是外函数的return,那就弹了吧,现在栈空了
  • 执行内函数,内函数推栈
  • 执行,并返回
  • 弹栈

优化了之后,无论内函数怎么折腾,外函数不参与。关键就是确定外函数真的没必要存在了。

什么情况下会启用尾调用优化:

  • 外部函数返回值是对未调用函数的调用
  • 函数返回后不需要额外逻辑
  • 不是闭包
  • 严格模式

闭包

https://www.yuque.com/xinbao37/roadmap/js-scope-and-closure

私有方法

需要review