02 函数的扩展

87 阅读4分钟
├── 函数的扩展
│   ├── 函数的 length 属性
│   │     └─ 指定了默认值以后,函数的`length`属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,`length`属性将失真。
│   ├── 作用域
│   │     └─ 一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
│   ├── rest 参数
│   │     └─ ES6 引入 rest 参数(形式为`...变量名`),用于获取函数的多余参数,这样就不需要使用`arguments`对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
│   │     └─ `arguments`对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用`Array.from`先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。下面是一个利用 rest 参数改写数组`push`方法的例子。
│   ├── 严格模式
│   │     └─ ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
│   ├── name 属性
│   │     └─ 函数的`name`属性,返回该函数的函数名。
│   │     └─ `bind`返回的函数,`name`属性值会加上`bound`前缀。
│   ├── 箭头函数
│   │     └─ 基本用法
│   │         └─ ES6 允许使用“箭头”(`=>`)定义函数。
│   │     └─ 使用注意点
│   │         └─ 箭头函数没有自己的 `this`对象(详见下文)。
│   │         └─ 不可以当作构造函数,也就是说,不可以对箭头函数使用`new`命令,否则会抛出一个错误。
│   │         └─ 不可以使用 `arguments`对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
│   │         └─ 不可以使用 `yield`命令,因此箭头函数不能用作 Generator 函数。
│   │     └─ 不适用场合
│   │         └─ 由于箭头函数使得 `this`从“动态”变成“静态”,下面两个场合不应该使用箭头函数。
│   │         └─ 第一个场合是定义对象的方法,且该方法内部包括`this`。
│   │         └─ 第二个场合是需要动态 `this`的时候,也不应使用箭头函数。
│   │     └─ 嵌套的箭头函数
│   │         └─ 
│   ├── 尾调用优化
│   │     └─ 什么是尾调用?
│   │         └─ 尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
│   │     └─ 尾调用优化
│   │         └─ 我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数`A`的内部调用函数`B`,那么在`A`的调用帧上方,还会形成一个`B`的调用帧。等到`B`运行结束,将结果返回到`A``B`的调用帧才会消失。如果函数`B`内部还调用函数`C`,那就还有一个`C`的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数`A`的内部调用函数`B`,那么在`A`的调用帧上方,还会形成一个`B`的调用帧。等到`B`运行结束,将结果返回到`A``B`的调用帧才会消失。如果函数`B`内部还调用函数`C`,那就还有一个`C`的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
│   │         └─ 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
│   │         └─ “尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。
│   │         └─ 注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。
│   │     └─ 尾递归
│   │     └─ 递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。