JS-函数

函数

JS 中函数是核心概念,函数最初通过 function 声明创建,ES6 新增了箭头函数。函数的参数通过 arguments 对象访问,函数的所有参数都是值传递。

创建函数

// 构造函数
let func = new Function ([arg1[, arg2[, ...argN]],] functionBody)
let sum = new Function('a', 'b', 'return a + b');

// 命名函数
function fn() {
    fn();
}
// 函数表达式-匿名函数
var fn = function () {
    arguments.callee();
    // arguments.callee 函数自身
    // arguments.length 函数调用时传参数量
};
// 函数表达式-命名函数
var fn = function f() {
    f();
};
// 箭头函数
var fn = () => undefined;
var fn = () => ({});
var fn = () => {};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

普通函数

  • arguments 是类数组对象
  • 函数参数默认值 undefined
  • 函数不能重载
  • 函数的所有参数都是值传递
function setName(obj) {
    obj.name = "张三";
    obj = new Object();
    obj.name = "李四";
}
var someone = new Object();
setName(someone);
console.log(someone.name); // 张三

// 如果 someone 按照引用传递,那么最后输出的就应该是"李四".所以,即是函数内部修改了值,但原始的引用并未改变。函数内部的参数只是一个局部变量,会在函数执行完后立即被销毁。
1
2
3
4
5
6
7
8
9
10

箭头函数

  • 箭头函数:可以直接返回一条语句
  • 箭头函数:直接返回对象需要使用括号抱起来
  • 箭头函数:用来简化回调函数
  • 箭头函数:没有 自己的 this,内部 this 默认指向定义时上下文的 this,是固定不可变得, 也就不可以作为构造函数
  • 箭头函数:没有 arguments 对象,可以使用参数扩展运算符代替
  • 箭头函数:不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
  • 箭头函数:不适合用作定义对象属性的函数,不适合作为 DOM 事件监听回调函数
  • 箭头函数:用于数学运算(阶乘等),简化方程式

作用域

当函数被调用时,会创建一个执行环境和相应的的作用域链。作用域链可能是很多层,但终点时全局执行环境(全局作用域)。每一个作用域都包含一个由 arguments其他命名参数一起创建的变量对象(活动对象)

闭包

有权访问另一个函数作用域中变量对象的函数。创建闭包常见的方式就是在函数内部创建另一个函数。因为闭包会携带包含它的函数作用域,因此更占内存,要尽量减少使用或者 使用 null 赋值标记内存垃圾回收变量

this

this 是对象,是在函数运行时基于函数的运行环境绑定的:在全局函数中等于 window, 而当函数作为某个对象的方法调用时,this 等于那个具体对象

call/apply/bind

改变函数执行环境的 this 对象。

var fn = Super.bind({}, ...[]); //  不能立即执行,返回函数
var val = Super.call({}, ...[]); // 立即执行, 返回执行结果
var val = Super.apply({}, []); // 立即执行, 返回执行结果
1
2
3

递归尾调用

尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

function f() {
    let m = 1;
    let n = 2;
    return g(m + n);
}
f();
1
2
3
4
5
6

尾调用优化(Tail call optimization)即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。

尾递归优化就是指某个函数的最后一步是调用自身函数。

function tco(f) {
    var value;
    var active = false;
    var accumulated = [];

    return function accumulator() {
        accumulated.push(arguments);
        if (!active) {
            active = true;
            while (accumulated.length) {
                value = f.apply(this, accumulated.shift());
            }
            active = false;
            return value;
        }
    };
}

var sum = tco(function (x, y) {
    if (y > 0) {
        return sum(x + 1, y - 1);
    } else {
        return x;
    }
});

sum(1, 100000);
// 100001
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

上面代码中,tco 函数是尾递归优化的实现,它的奥妙就在于状态变量 active。默认情况下,这个变量是不激活的。一旦进入尾递归优化的过程,这个变量就激活了。然后,每一轮递归 sum 返回的都是 undefined,所以就避免了递归执行;而 accumulated 数组存放每一轮 sum 执行的参数,总是有值的,这就保证了 accumulator 函数内部的 while 循环总是会执行。这样就很巧妙地将“递归”改成了“循环”,而后一轮的参数会取代前一轮的参数,保证了调用栈只有一层。