深入理解ES6 笔记三:函数

478 阅读6分钟

普通函数

默认参数值

function sum(value1, value2 = 0, callback = function() {}) {
    // 在这个函数中,只有第一个参数被认为总要被传入值,其他两个参数都有默认值
}

function sum(value1, value2 = 0, callback = function() {}) {
    // 再这个函数中,只有当不为第二个参数传入值或者主动为第二个参数传入undefined时才会受用value2的默认值
}

可以使用先定义的参数作为后定义参数的默认值

function sum(first, second = first) {
    return first + second;
}

sum(1, 1); // 2
sum(1); // 2

只允许引用前面参数的值,即先定义的参数不能访问后定义的参数,这设计到了默认参数的临时死区——定义参数时会为每个参数创建一个新的标识符绑定,该绑定在初始化之前不可被引用,如果试图访问会导致程序抛出错误;只有当调用函数时,才会通过传入的值或参数的默认值初始化该参数。

默认参数值对arguments对象的影响

如果一个函数使用了默认参数值,会使得arguments对象保持与命名参数分离

function mixArgs(first, second = 'b') {
    console.log(arguments.length);  // 2
    console.log(first === arguments[0]);  // true
    console.log(second === arguments[1]);  // true
    first = '1';
    second = '2';
    console.log(first === arguments[0]);  // false
    console.log(second === arguments[1]);  // false
}

不定参数

在函数的命名参数前添加三个点(...)就表明这是一个不定参数,该参数为一个数组,包含着自它之后传入的所有参数,通过这个数组名即可逐一访问里面的参数。

function sum(first, ...other) {
    let num = first;
    console.log(first);
    console.log(other);
    other.forEach(function(item) {
        num += item;
    });
    return num;
}

sum(1, 2, 3, 4);    //1
                    // [2, 3, 4]

不定参数的使用限制

  • 每个函数最多只能声明一个不定参数,并且一定要放在所有参数的末尾。
  • 不定参数不能用于对象字面量setter之中,因为对象字面量setter的参数有且只能有一个。

不定参数与arguments的区别

arguments对象包含所有传入函数的参数,不定参数只包含自它之后传入的所有参数,也就是arguments对象包含或等于不定参数。

Function构造函数

通常用来动态创建新的函数。具备与声明式创建函数相同的能力。

const add = new Function('first', 'second', 'return first + second');
console.log(add(1,1));  // 2

const add = new Function('first', 'second = first', 'return first + second');
console.log(add(1,1));  // 2
console.log(add(1));  // 2

const pick = new Function('...args', 'return args[0]');
console.log(add(1,2));  // 1

展开运算符

不定参数可以让你制定多个独立的参数,并通过整合后的数组来访问;而展开运算符可以让你制定一个数组,将它们打散后作为格子独立的参数传入函数。

let values = [1, 2, 3, 4];
console.log(Math.max.apply(Math, values));  // ES5 方法

// 展开运算符
console.log(Math.max(...values));  // 4
console.log(Math.max(...values, 10));  // 10

函数的多重用途

Javascript函数有两个不同的内部方法:[[Call]]和[[Construct]]。当通过new关键字调用函数是,执行的是[[Construct]]函数,它负责创建一个通常被称作实例的新对象,然后再执行函数体,将this绑定再实例上;如果不通过new关键之调用函数,则执行[[Call]]函数,从而直接执行代码中的函数体。具有[[Construct]]方法的函数被统称为构造函数。 不是所有的函数都有[[Construct]]方法,因此不是所有函数都可以用new调用。

元属性(Metaproperty)new.target

元属性是指非对象的属性,其可以停工非对象目标的补充信息(如new)。当调用函数的[[Construct]]方法时,new.target被赋值为new操作符的目标,通常是新创建对象的实例,也就是函数体内this的构造函数;如果调用[[Call]]方法,则new.target的值为undefined。通过检查new.target可以安全的检测一个函数是否是通过new关键字调用的。

箭头函数

箭头函数是一种使用箭头(=>)定义函数的新语法,它与不同函数的区别如下:

  • 没有this、super、arguments和new.target绑定 箭头函数中的this、super、arguments和new.target这些值由外围最近一层非箭头函数决定。
  • 不能通过new关键字调用 箭头函数没有[[Construct]]方法,不能作为构造函数。
  • 没有原型 由于不可以通过new关键字调用箭头函数,因而没有构造原型的需求,所以箭头函数不存在prototype这个属性。
  • 不可以改变this的绑定 函数内部的this值不可被改变,在函数的生命周期内始终一致。
  • 不支持arguments对象 箭头函数没有arguments绑定,所以只能通过命名参数和不定参数这两种形式访问函数的参数。但是箭头函数始终可以访问外围函数的arguments对象。
  • 不支持重复的命名参数

箭头函数语法

// 情景一 一参数一表达式
let reflect = value => value;
👇相当于
let reflect = function(value) {
    return value;
}

//情景二    多参数一表达式
let sum = (num1, num2) => return num1 + num2;
👇相当于
let sum = function(num1, num2) {
    return num1 + num2;
}

//情景三    无参数1/0表达式
let getName = () => return 'Apple';
let getName = () => {}; // 空函数
👇相当于
let getName = function() {
    return 'Apple';
}


//情景四 由多个表达式组成的函数体
let getName = (value) => {
    //表达式一
    //表达式二
    return value;
};
// 注意: 返回对象字面量需要用()包起来,将其与函数体区分开
let reflect = value => ({'name': value});
👇相当于
let getName = function(value) {
    //操作一
    //操作一
    return value;
}

立即执行函数表达式

let person = ((name) => {
    return {
        getName: function() {
            return name;
        }
    };
})('Apple');
console.log(person.getName());  // Apple

注意:小括号值包裹箭头函数定义,没有包含('Apple'),这一点与正常函数有所不同,由正常函数定义的立即执行函数表达式既可以用小括号包裹函数体,也可以额外包裹函数调用的部分。

箭头函数没有this绑定

箭头函数中没有this绑定,必须通过查找作用域链来决定其值。如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this;否则,this的值会被设置为全局对象。
箭头函数缺少正常函数所拥有的prototype属性,不能用它来定义新的类型。通过new关键字调用会导致程序抛出错误。

尾调优化

尾调用指的是函数作为另一个函数的最后一条语句被调用。
在ES5的引擎中,尾调用的实现是通过创建一个新的栈帧,将其推入调用栈来表示函数调用。在循环调用中,每一个未用完的栈帧都会被保存在内存中,当调用栈变得过大时会造成程序问题。
在ES6中,如果满足以下条件,尾调用不在创建新的栈帧,而是清楚并重用当前栈帧:

  • 尾调用不访问当前栈帧的变量(也就是说函数不是一个闭包)。
  • 在函数内部,尾调用是最后一条语句。
  • 尾调用的结果作为函数值返回。

尾调用优化可以帮助函数保持一个更小的调用栈,从而减少内存的使用,避免栈溢出错误。