ES6:函数的扩展

函数参数的默认值rest 参数严格模式name 属性箭头函数尾调用优化函数参数的尾逗号Function.prototype.toString()catch 命令的参数省略

一、函数参数的默认值

通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。

// 例一function f(x = 1, y) { return [x, y];}
f() // [1, undefined]f(2) // [2, undefined]f(, 1) // 报错f(undefined, 1) // [1, 1]
// 例二function f(x, y = 5, z) { return [x, y, z];}
f() // [undefined, 5, undefined]f(1) // [1, 5, undefined]f(1, ,2) // 报错f(1, undefined, 2) // [1, 5, 2]

二、rest 参数

ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function add(...values{ let sum = 0;
for (var val of values) { sum += val; }
return sum;}
add(2, 5, 3) // 10

三、严格模式

ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

// 报错function doSomething(a, b = a) { 'use strict'; // code}
// 报错const doSomething = function ({a, b}) { 'use strict'; // code};
// 报错const doSomething = (...a) => { 'use strict'; // code};
const obj = { // 报错 doSomething({a, b}) { 'use strict'; // code }};

四、name 属性

const bar = function baz() {};
// ES5bar.name // "baz"
// ES6bar.name // "baz"

五、箭头函数

ES6 允许使用“箭头”(=>)定义函数。

var f = () => 5;// 等同于var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;// 等同于var sum = function(num1, num2) { return num1 + num2;};

使用注意点-箭头函数有几个使用注意点。

(1)箭头函数没有自己的this对象(详见下文)。

(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

上面四点中,最重要的是第一点。对于普通函数来说,内部的this指向函数运行时所在的对象,但是这一点对箭头函数不成立。它没有自己的this对象,内部的this就是定义时上层作用域中的this。也就是说,箭头函数内部的this指向是固定的,相比之下,普通函数的this指向是可变的。

六、尾调用优化

什么是尾调用?

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

function f(x){ return g(x);}

以下三种情况,都不属于尾调用。

// 情况一function f(x){ let y = g(x); return y;}
// 情况二function f(x){ return g(x) + 1;}
// 情况三function f(x){ g(x);}

上面代码中,情况一是调用函数g之后,还有赋值操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。情况三等同于下面的代码。

尾调用优化

尾调用之所以与其他调用不同,就在于它的特殊的调用位置。

我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。


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

// 等同于function f() { return g(3);}f();

// 等同于g(3);

上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除f(x)的调用帧,只保留g(3)的调用帧。

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

注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

function addOne(a){ var one = 1; function inner(b){ return b + one; } return inner(a);}

上面的函数不会进行尾调用优化,因为内层函数inner用到了外层函数addOne的内部变量one。

注意,目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持。

蹦床函数并不是真正的尾递归优化,下面的实现才是。

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

七、函数参数的尾逗号

八、Function.prototype.toString()

九、catch 命令的参数省略:

很多时候,catch代码块可能用不到这个参数。但是,为了保证语法正确,还是必须写。ES2019 做出了改变,允许catch语句省略参数。

try { // ...} catch (err) { // 处理错误}上面代码中,catch命令后面带有参数err。try { // ...} catch { // ...}

ES6:函数的扩展》来自互联网,仅为收藏学习,如侵权请联系删除。本文URL:http://www.hashtobe.com/796.html