ES6之函数
ES6之函数
本文知识点主要有:
- 函数默认参数
- 展开运算符
- 函数的其他优化
- 箭头函数
- 尾调用的内存优化
函数参数默认值
ES6之前,函数对与参数的默认值设置通常采用以下方式:
function makeRequest (url, timeout, callback) {
timeout = timeout || 2000;
callback = callback || function () {};
// ...
}
但此方法有个小缺陷,就是timeout在传入 0 / false 时,都会默认采用2000。对此进行优化如下:
function makeRequest (url, timeout, callback) {
timeout = (typeof timeout !== "undefined") ? timeout : 2000;
callback = (typeof callback !== "undefined") ? callback : functipn () {};
// ...
}
上段代码中 undefined
也可以换成void 0
。
看起来仍旧有点繁琐。为此,ES6提供了默认参数值的用法,更改如下:
function makeRequest (url, timeout = 2000, callback = function () {} {
// ...
}
看起来简洁了很多。代码中,url
是必填值,timeout 和 callback
是可选参数。可以选择不传、或者传undefined
让其使用默认值。
此外,参数默认值也可以写成表达式。
function add (first, second = getValue(first)) {
return first + second;
}
function getValue(num) {
return num + 1
}
add(1, 1) // 2
add(1) // 3
second
的默认参数值,使用了first
,且调用了getValue
函数。值得注意的是,默认参数也存在临时死区,即未声明变量之前,无法进行访问。
function add (first, second = first) {}
等价于
function add () {
let first = arguments[0];
let second = first;
{
// 函数体
}
}
参数的默认值是不可以访问函数体内声明的变量。因此,需要用代码块的形式将其隔离。
对于 arguments
的影响
在ES5 非严格模式中。arguments
会随着形参被重新赋值进行改变。
function mixArgs (first, second) {
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = 'c';
second = 'd';
console.log(first === arguments[0]);
console.log(second === arguments[1]);
}
mixArgs("a", "b");
// true true true true
ES5严格模式时,arguments
便不会随着形参被重新赋值进行改变。ES6 与ES5严格模式保持一致。
展开运算符
展开运算符由三个点组成 ( … ),主要有两种用途:
- 函数参数的收集
- 数据展开
函数参数的收集
与arguments
相同,可以收集传入函数中的多个无命名参数。不同点有以下几点:
arguments
为一个类数组集合,而 … 为数组arguments
在形参声明时,可以省略。而 … 必须声明,且只能声明一个放在形参的最后。arguments
收集所有的参数。而 … 只收集其他形参声明后,剩余的参数。
最后, …不能在setter 函数中使用,set函数只能有一个参数。
function add (...args) {
let sum = 0;
for (let i = 0; i < args.length; i++) {
sum += args[i]
}
}
add(1, 2, 3, 4, 5, 6); // 21
数据展开
展开运算符可以简化给函数传参的过程, 大多数使用apply() 方法的情况,使用展开运算符会更方便。
let values = [21, 50, 75, 100];
console.log(Math.max(...values));
console.log(Math.max.apply(Math, values));
// 100 100
函数的其他优化
- 增强的Function构造函数 。我们可以在Function中使用默认参数和展开运算符。
var add = new Function("first", "second = first", "...args", "return first + second");
console.log(add(1, 2);
name属性
:用于便于辨识函数。
function func1 () { //... };
var func2 = function () { // ... };
var func3 = function func4 () { // ... };
var person = {
get func5 () { // ... }
};
var func6 = Object.getOwnPropertyDescriptor(person, "func5");
console.log(func1.name); // func1
console.log(func2.name); // func2
console.log(func3.name); // func4 (函数声明权重较高)
console.log(func6.get.name); // get func6
console.log(func1.bind().name); // bound func1
console.log((new Function()).name); // anonymos
最后的四个console
的结果需要注意。由于函数提升原则,函数的name
值被提前绑定。而set(get)函数,保留其前缀。同样的,使用bind
会产生bound
前缀。使用Function
构造函数的匿名函数,其name
值为anonymos
- new.target
当使用new
关键字调用函数时,会默认执行函数的[[Construct]]
函数,创建一个实例的新对象,然后执行函数体。最后将this绑定到新对象上完成创建过程。
不通过new
关键字调用函数时,会执行函数的[[Call]]
函数,直接执行函数体。
ES6在函数中新增的new.target
就为了判断函数是否通过new
进行调用。
function Person(name) {
if (typeof new.target !== 'undefined') {
this.name = name;
} else {
throw new Error("必须通过new关键字来调用Person")
}
}
var p1 = new Person("leo");
var p2 = Person(p1); // 报错
var p3 = Person.call(p1, "leo"); // 报错
箭头函数
与传统的函数相比,有以下几个方面的不同:
- 无
arguments, new.target
的绑定. - 不能使用
new
关键字进行调用。(因为没有[[Construct]]
构造方法) - 没有原型,不存在
prototype
属性, - 不能改变
this
的指向。 - 参数不能重名
var sum = function (num1, num2) => num1 + num2;
箭头函数的写法有以下简写规则:
- 没有参数时,使用一对空的小括号
() => {}
- 有一个参数时,直接使用 :
参数名 =>
- 有两以上参数,用括号包裹:
(参数1, 参数2) => {}
- 只有一条执行语句时,函数体的花括号和
return
可以省略:() => 1
箭头函数更加纯粹,简洁且易用的表示方式应普遍使用。
- 尾调函数的内存优化
在递归,闭包函数通常会使用更多的内存来保存作用域,产生栈堆的大量占用。ES6对此进行优化,让重复的函数只使用一个栈堆。为了使用此内存的优化,需要符合以下的规则:- 尾调函数不访问当前栈内的变量(非闭包函数)
- 尾调函数为最后一条语句
- 尾调用的结果作为函数值返回
举例熟悉的斐波拉切函数
"use strict";
function factorila(n) {
if (n <= 1) {
return 1;
} else {
return n * factorila(n - 1);
}
}
可优化为:
"use strict";
function factorila(n, p - 1) {
if (n <= 1) {
return 1 * p;
} else {
let result = n * p;
return factorila(n - 1, result);
}
}