ES6-函数扩展

对先前变量解构中的一些细节做补充和一些疑难点做答(for myself)。

解构赋值的默认值问题:

function foo({x, y = 5}) {
  console.log(x, y);
}foo() // TypeError: Cannot read property 'x' of undefined

上面函数参数需求到的是一个携有两个参数的对象,如果直接调用不给予参数foo() 则会报错,如果传递一个空对象foo({})则会对该空对象进行解构赋值,得到结果也就undefined  5.

如果想要不传递对象也能够进行解构赋值的话,我们需要在后面给定一个默认的解构赋值参数对象:

function foo({x, y = 5} = {}) {
  console.log(x, y);
}

foo() // undefined 5

这样调用起来就不会报错了。

对于下面两种函数定义的区别:

// 写法一
function m1({x = 0, y = 0} = {}) {
  return [x, y];
}

// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

第一种函数是给参数赋予了默认值为一个空对象,然后给其解构对象赋予默认值为0,如果不传入对象或者传入一个空对象,其结果获取皆是返回数组[0, 0]

m1() // [0, 0]m1({}) // [0, 0];

而第二种写法给默认参数也赋予了值,解构赋值没有默认值,所以我们如果不给于对象的话,他也能输出数组[0, 0]

m2() // [0, 0]

如果给的是一个空的对象,那么参数默认值并不会启用,而是调用给定的空对象参数默认值【undefined, undefined】

单传递对象的一个属性的话,第一种写法会对该对象进行解构后并且赋予第二个参数默认值0,第二种写法如果单传递的话他会认为你的参数并不是为空,所以默认值并不启用,所以实际上传入的是【3, undefined】,打印出来的结果也就是[3, undefined]。

m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]

一个方法的参数中带有默认值的参数一般写在参数结尾,原因如下:

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]

在需要使用该参数的默认值的时候,无可避免的需要在调用方法处赋予undefined参数,所以一般把具有默认值的参数赋到参数列表最后。

同时,在赋予参数默认值后,length方法原本获取的函数的参数个数会减去携带默认值及携带默认值参数的后续参数的个数:

(function (a, b = 1, c) {}).length // 1

如果设置了参数的默认值的话,在进行函数初始化的时候参数内会自成一域,不受外面影响:

var x = 1;

function f(x, y = x) {
  console.log(y);
}

f(2) // 2

这个例子中参数y的默认值为x,而他的x指向的是参数域内的x,也就是传入参数2,而不是全局变量x = 1。

let x = 1;

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // 1

这里y的默认值为x,而在参数域内找不到这个变量,所以指向外层的全局变量X。

ES6还给我们提供了箭头来定义函数:

var sum = (num1, num2) => { return num1 + num2; }

num1,num2是参数列表,箭头后面跟着一个大括号返回函数体,如果箭头函数直接返回一个对象,需要在大括号外面包裹多一层括号,否则会报错:

// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });

箭头函数还可以与变量解构一起使用:

const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
  return person.first + ' ' + person.last;
}

    

这里引入一个尾调用的概念,尾调用就是函数返回处调用另一个函数:

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

例子中的函数在最后一步返回处调用了其他函数,这就是尾调用,但是尾调用不一定在函数末尾,只要是最后一步操作即可:

function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x);
}

这里的函数m和n都属于尾调用。

函数调用的时候会在内部形成一个“调用帧”来保存调用位置和变量信息等。如果在函数A的内部调用函数B,那么A的调用帧上方还会有B的调用帧,等到B函数运行结束后,将结果返回到A,B的调用帧才会消失,如果B函数内还有C函数的调用,就还会有C的调用帧,所有的调用帧就形成了一个调用栈。

而函数内部的尾调用函数的调用帧(A内部调用B,A的调用帧上还包着B的调用帧,这里指的是A的调用帧)其实没有存在的必要,他的调用位置和内部信息都可以通过内层函数的调用帧(这里指的是A调用帧之上的调用帧,B调用帧)获取,从而取代外部函数调用帧。这个就叫做尾调用优化,即使是多层嵌套,他都只会保留最内层函数的调用帧,因为最内层函数的调用帧是包裹在最外层的调用帧上,他包含其内所有内部变量及函数调用位置的信息,这将大大节省内存。

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

严格模式下才可启用,如若不是需要自己实现尾调用优化。

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

这是一个阶乘函数,很普通,我们要对他进行的优化点便是他返回值处调用了函数内部的n,我们需要把他改造成尾调用的形式,将n放入函数调用的参数中。

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

优化后,我们需要通过一个额外的参数total来返回阶乘的结果,这里的参数n可以理解成为阶乘数的阶乘级(对3求阶乘的话会迭代他的2和1层级),每一次的函数调用都会对n进行递减,并且利用total参数将先前的值做一个计算存放,当n=1也就是阶乘到底的时候就会返回阶乘的结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值