普通函数
默认参数值
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中,如果满足以下条件,尾调用不在创建新的栈帧,而是清楚并重用当前栈帧:
- 尾调用不访问当前栈帧的变量(也就是说函数不是一个闭包)。
- 在函数内部,尾调用是最后一条语句。
- 尾调用的结果作为函数值返回。
尾调用优化可以帮助函数保持一个更小的调用栈,从而减少内存的使用,避免栈溢出错误。