ES6入门之函数的扩展

1. 函数参数的默认值


1.1 用法

在ES6之前是不能为函数的参数指定默认值的,要想实现默认值只能通过判断赋值的方式来实现,在ES6中允许函数为参数设置默认值,主要是为了提高代码的可阅读性,有利于代码的优化。另外注意的是在参数赋值的时候,该参数不能重复使用,不能使用let const 进行定义。

// ES6 之前实现
function log(x, y) {
  y = y || 'World';
  if (typeof y === 'undefined') {
      y = 'World';
  }
  console.log(x, y);
}

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


// ES6 中实现
function log(x, y = 'World') {
  console.log(x, y);
}

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

function Point(x = 0, y = 0) {
  this.x = x;
  this.y = y;
}

const p = new Point();
p // { x: 0, y: 0 }

function foo(x = 5,x) { 
  let x = 1; // 报错,不能同名参数,不能对参数进行let const 定义
  const x = 2;
}
1.2 与解构赋值一起使用

如果函数在调用的时候没有提供参数,内部变量就不会产生,就会产生错误,通过提供函数的默认值可以解决这种问题,如下:

function foo({x, y = 5}) {
  console.log(x, y);
}
foo() // 报错
foo({x:1}) // 1 5
foo({x:2,y:3) // 2 3
foo({}) // undefined 5

function foo ({x,y = 5} = {}){
    console.log(x,y)
}
foo() // undefined 5 
这样就是如果没有在调用的时候传值 就默认赋空对象。

如下例子:

function post(url, {b = '',type='get',h={}}){
    console.log(type)
}

post('w.b.c',{}) // get
post('w.b.c')    // 报错

// 改成这样就可以了

function post(url, {b = '',type='get',h={}} = {}){
    console.log(type)
}

post('w.b.c',{}) // get
post('w.b.c')    // get

下面例子的区别

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

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

两个都是有默认值在调用的时候都传值或者都不传值的时候情况是一样的。
但是如果传空值,或者不传值的情况会有差异如下:

m1({}) // 因为本身有默认值 所以为 [0,0]
m2({}) // 默认值为空 解构赋值没有传值 所以 [undefined,undefined]

// 其他情况同上
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]
1.3 参数默认值的位置

如果定义了默认值的参数,应该是函数的尾参数。而且这个参数是无法省略的,除非输入undefined

1.4 函数的 length 属性

函数参数指定了默认值之后,函数的length属性将会减去指定了默认值的参数个数。因为该属性认为,指定了默认值的参数将不包含在预期参数个数中。如下:

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

如果函数中的参数设置了默认值,那么函数在声明初始化的时候,参数会形成一个单独的作用域,初始化完成后这个作用域就会消失,这种情况只在参数设置了默认值的情况下。如下:

var x = 1;

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

f(2) // 2
// 因为 设置了默认值 所以在调用 f 的时候就形成了作用域,这时候因为将x赋值给y 
传入的x 为 2  所以y是2,如果这时候 调用的时候不传值,
那么x将指向全局,所以y = 1
1.6 应用

利用参数默认值,可以指定某一个参数不得省略,如果省略就报错,如下

function throwIfMissing() {
  throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}

foo()
// Error: Missing parameter
foo(2) // 2

2. rest 参数

ES6 中 增加了 rest 参数(…变量名),用于获取函数多余的参数,rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function add(...values) {
  let sum = 0;

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10

// 注意:rest 参数之后不能再有其他参数,另外rest参数也不计算在
函数的length属性中。

3. 严格模式

ES6 中,如果函数参数使用了默认值,解构赋值,或者扩展运算符,那么函数内部将不能显式设定为严格模式,否则会报错。因为函数执行的时候 先执行函数参数,在执行函数体,但是因为只有在函数体中才能知道参数是否以严格模式执行,但是参数却应该先于函数执行。有两种方法可以规避:一、 设置全局严格模式,二、把函数包在一个无参数的立即执行函数里面。

4. name属性

返回函数的函数名,如下:

function foo(){}
foo.name // foo

var f = function(){}
// ES5
f.name // ''
// ES6
f.name // f

var f = function c(){}

f.name // c

5. 箭头函数

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

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;
};

// 如果箭头函数后面的语句较多就要用大括号包裹起来 并return返回
var sum = (num1, num2) => { return num1 + num2; 

//rest 参数与箭头函数结合的例子。

const numbers = (...nums) => nums;

numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];

headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]
注意点
1. 函数体内的this对象,就是在定义时所在的对象,而不是使用时所在的对象。
2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
4. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
5. 由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。
不适用场景
1. 定义对象的方法,且该方法内部包括this
2. 动态定义this 的场合,如点击事件中this 的指向
嵌套的箭头函数

箭头函数内部可以在嵌套使用箭头函数。

6. 尾调用优化

什么是尾调用

函数式编程的一个重要概念,指某个函数的最后一步是调用另一个函数

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);
}
尾调用优化

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

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

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

// 等同于
g(3);

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

尾递归

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

ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
尾递归优化的实现

在正常模式下,可以使用减少调用栈,采用循环换掉递归的方法