ES6函数的扩展

一、函数参数的默认值

1、基本用法

(1)ES6允许为参数设置默认值

function log(x, y) {
  y = y || 'World';
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

(2)参数变量是默认声明的,所以不能用let或const再次声明,用var可以

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

(3)使用参数默认值时,函数不能有同名参数

// 不报错
function foo(x, x, y) {
  // ...
}

// 报错
function foo(x, x, y = 1) {
  // ...
}

(4)参数默认值不传值,每次都要重新计算表达式的值。

2、与解构赋值默认值结合使用

(1)参数默认值可以与解构赋值的默认值结合起来使用。

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

foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

//没有参数时,使用默认值。
//如果没有提供默认值,就会报错
//因此如果有默认值,就可以避免报错的情况

2、参数默认值的位置

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

function f(x, y = 5, z) {
  return [x, y, z];
}

f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
//除非显示化的输入Undefined,才能保留默认值
f(1, undefined, 2) // [1, 5, 2]

3、函数的length属性

返回函数中没有设定默认值的参数个数。

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

如果设置的默认值参数不是尾参数,那么length属性也不再计入后面的参数个数了

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

4、作用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。

var x = 1;
function f(x, y = x) {
  console.log(y);
}
f(2) // 2
--------------------------------------------------
let x = 1;
function f(y = x) {
  let x = 2;
  console.log(y);
}
f() // 1

二、rest参数

(1)ES6引入rest参数(形式为…变量名),当有多个参数传入时,会转化为数组存在该变量里。

function add(...values) {
  let sum = 0;
  for (var val of values) {
    sum += val;
  }

  return sum;
}
add(2, 5, 3) // 10

(2)rest参数只有不能有任何其他参数(即只能是最后一个参数),否则会报错。

// 报错
function f(a, ...b, c) {
  // ...
}

(3)函数的length属性,不包括rest参数。

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

三、严格模式

(1)从ES5开始,函数内部可以设置严格模式。

function doSomething(a, b) {
  'use strict';
  // code
}

(2)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
  }
};

有两种方法可以规避这种限制:
(1)设定全局性严格模式
(2)把函数包在一个无参数的立即执行的函数里面

四、name属性

函数的name属性,返回函数名。
(1)当把匿名函数赋给一个变量时,该变量的name属性会返回变量名。

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"

(2)当把有函数名的函数赋给变量,name变量的name属性为函数名。

const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"

五、箭头函数

1、基本用法

var f = v => v;

// 等同于
var f = function (v) {
  return v;
};
var f = () => 5;
// 等同于
var f = function () { return 5 };

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

(1)如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

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

由于代码块被解释为代码块,因此如果箭头函数直接返回一个对象,需用()括在{}外面。

let getTempItem = id => ({ id: id, name: "Temp" });

如果箭头函数只有一行语句且不用返回值,就可以用下列写法:

let fn = () => void doesNotReturn();

(2)【注意】
a.函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

b.不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

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

d.不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
(3)不适用场景:
a.定义对象的方法,且该方法内部包括this。
b.需要动态this的时候,也不应使用箭头函数。
(4)嵌套的箭头函数
函数的内部还可以使用箭头函数。

//原代码:
function insert(value) {
  return {into: function (array) {
    return {after: function (afterValue) {
      array.splice(array.indexOf(afterValue) + 1, 0, value);
      return array;
    }};
  }};
}
insert(2).into([1, 3]).after(1); //[1, 2, 3]


//简化为:
let insert = (value) => ({into: (array) => ({after: (afterValue) => {
  array.splice(array.indexOf(afterValue) + 1, 0, value);
  return array;
}})});

insert(2).into([1, 3]).after(1); //[1, 2, 3]

六、尾调用优化

1、什么是尾调用?

在某个函数的最后一步调用另一个函数。

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);
}
  • 尾调用不一定只出现在函数的尾部,只要是最后一步计科
//m(x)和n(x)都属于尾调用,他们都是函数f的最后一步操作
function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x);
}

2、尾调用优化

只保留内层函数的调用帧,如果所有函数都是尾调用,那么完全可以做到每次执行的时候,调用帧只有一帧,浙江大大节省内存。
【注】:当内层函数用到外层函数中的变量时,不可以进行尾调用优化。

//此时,inner()函数内部用到了外层函数addOne()函数的one变量
//所以不可以进行尾调用优化
function addOne(a){
  var one = 1;
  function inner(b){
    return b + one;
  }
  return inner(a);
}

3、尾递归

函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

  • 递归非常耗时,因为要再同时保存成千上百个调用帧,很容易发生“栈溢出”。但对于尾递归来说,只存在一个调用帧,就不会发生“栈溢出”错误。
function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

//优化后:
function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

4、递归函数的改写

尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。但这样改写往往可读性不高,以下有两种解决方法:
(1)在尾递归函数之外,再提供一个正常形式的函数。

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

function factorial(n) {
  return tailFactorial(n, 1);
}

factorial(5) // 120

(2)采用 ES6 的函数默认值。

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

factorial(5) // 120

5、严格模式

ES6尾调用优化只在严格模式下开启,正常模式无法使用。

6、尾调用优化的实现

在非严格模式下,可用循环来替代代码递归。

七、函数参数的尾逗号

ES2017允许函数的最后一个参数带尾逗号。

function aa (a,b,){}	//可以执行

八、Function.prototype.toString()

ES2019明确要求toString()方法,返回与函数原本一模一样的原始代码。

function /* foo comment */ foo () {}

foo.toString()
// "function /* foo comment */ foo () {}"

九、catch命令的参数省略

ES2019允许catch语句省略参数,不会报错。

try {
  // ...
} catch {
  // 不会报错
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值