乐趣区

关于javascript:ES6语法总结函数的扩展

1、函数参数的默认值

根本用法

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

log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello
  • 应用参数默认值时,函数不能有同名参数
// 不报错 
function foo(x, x, y) {// ...}

// 报错
function foo(x, x, y = 1) {// ...} 
// SyntaxError: Duplicate parameter name not allowed in this context
  • 参数默认值不是传值的,而是每次都从新计算默认值表达式的值。也就是说,参数默认值是惰性求值的
let x = 99;
function foo(p = x + 1) {console.log(p);
}

foo() // 100 
x = 100;
foo() // 101

与解构赋值默认值联合应用

参数默认值的地位

  • 定义了默认值的参数,应该是函数的尾参数。因为这样比拟容易看进去,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的
// 例一 
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]

函数的 length 属性

  • 函数的 length 属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真
  • 如果设置了默认值的参数不是尾参数,那么 length 属性也不再计入前面的参数了
(function (a = 0, b, c) {}).length // 0 
(function (a, b = 1, c) {}).length // 1

作用域

  • 一旦设置了参数的默认值,函数进行申明初始化时,参数会造成一个独自的作用域(context)。等到初始化完结,这个作用域就会隐没。这种语法行为,在不设置参数默认值时,是不会呈现的
var x = 1;

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

f(2) // 2
  • 上面代码中,函数 f 调用时,参数 y = x 造成一个独自的作用域。这个作用域外面,变量 x 自身没有定义,所以指向外层的全局变量 x。函数调用时,函数体外部的局部变量x 影响不到默认值变量 x。如果此时,全局变量x 不存在,就会报错
let x = 1;

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

f() // 1
  • 上面这样写,也会报错。参数 x = x 造成一个独自作用域。理论执行的是let x = x,因为暂时性死区的起因,这行代码会报错”x 未定义“
var x = 1;

function foo(x = x) {// ...}

foo() // ReferenceError: x is not defined
  • 如果参数的默认值是一个函数,该函数的作用域也恪守这个规定。函数 bar 的参数 func 的默认值是一个匿名函数,返回值为变量 foo。函数参数造成的独自作用域外面,并没有定义变量foo,所以foo 指向外层的全局变量 foo,因而输入outer。如果外层没有什么foo 就会报错
let foo = 'outer';

function bar(func = () => foo) {
  let foo = 'inner';
  console.log(func());
}

bar(); // outer

利用

  • 利用参数默认值,能够指定某一个参数不得省略,如果省略就抛出一个谬误
function throwIfMissing() {throw new Error('Missing parameter');
}

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

foo() // Error: Missing parameter
  • 能够将参数默认值设为undefined,表明这个参数是能够省略的

2、rest 参数

  • ES6 引入 rest 参数(模式为 ... 变量名),用于获取函数的多余参数,这样就不须要应用arguments 对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中
  • 留神,rest 参数之后不能再有其余参数(即只能是最初一个参数),否则会报错
  • 函数的 length 属性,不包含 rest 参数

3、严格模式

  • 从 ES5 开始,函数外部能够设定为严格模式
function doSomething(a, b) {
  'use strict';
 // code 
}
  • ES2016 做了一点批改,规定只有函数参数应用了默认值、解构赋值、或者扩大运算符,那么函数外部就不能显式设定为严格模式,否则会报错

4、name 属性

  • 函数的 name 属性,返回该函数的函数名
  • ES6 对这个属性的行为做出了一些批改。如果将一个匿名函数赋值给一个变量,ES5 的 name 属性,会返回空字符串,而 ES6 的 name 属性会返回理论的函数名
var f = function () {};
// ES5 f.name // ""// ES6 f.name //"f"
  • 如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的 name 属性都返回这个具名函数本来的名字
const bar = function baz() {};
// ES5 bar.name // "baz"  
// ES6 bar.name // "baz"
  • Function构造函数返回的函数实例,name属性的值为anonymous
  • bind返回的函数,name属性值会加上 bound 前缀
function foo() {};
foo.bind({}).name // "bound foo" 
(function(){}).bind({}).name // "bound"

5、箭头函数

  • 如果箭头函数只有一行语句,且不须要返回值,能够采纳上面的写法,就不必写大括号了
let fn = () => void doesNotReturn();

应用留神点

1. 函数体内的 this 对象,就是定义时所在的对象,而不是应用时所在的对
2. 不能够当作构造函数,也就是说,不能够应用 new 命令
3. 不能够应用 arguments 对象
4. 不能够应用 yield 命令

不适宜场合

  • 定义对象的办法,且该办法外部包含 this。cat.jumps()办法是一个箭头函数,这是谬误的。调用 cat.jumps() 时,如果是一般函数,该办法外部的 this 指向 cat;如果写成下面那样的箭头函数,使得this 指向全局对象,因而不会失去预期后果。这是因为对象不形成独自的作用域,导致 jumps 箭头函数定义时的作用域就是全局作用域
const cat = {
  lives: 9,
  jumps: () => {this.lives--;}
}
  • 须要动静 this 的时候,也不应应用箭头函数
var button = document.getElementById('press');
button.addEventListener('click', () => {this.classList.toggle('on');
});

下面代码运行时,点击按钮会报错,因为 button 的监听函数是一个箭头函数,导致外面的 this 就是全局对象。如果改成一般函数,this就会动静指向被点击的按钮对象

嵌套的箭头函数

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]
  • 管道用法

6、尾调用

  • 尾调用(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);
}
  • 尾调用不肯定呈现在函数尾部,只有是最初一步操作即可

尾调用优化

  • 尾调用之所以与其余调用不同,就在于它的非凡的调用地位。其余函数调用会在内存造成一个“调用记录”,又称“调用帧”,保留调用地位和内存变量等信息。如果在函数 A 的外部调用函数 B,那么在 A 的调用帧上方,还会造成一个 B 的调用帧。等到 B 运行完结,将后果返回到 A,B 的调用帧才会隐没。以此类推,就造成了调用栈
  • 尾调用因为是函数的最初一步操作,所以不须要保留外层函数的调用帧,因为调用地位、外部变量等信息都不会再用到了,只有间接用内层函数的调用帧,取代外层函数的调用帧就能够了
function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

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


f();
// 等同于 
g(3);
  • “尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么齐全能够做到每次执行时,调用帧只有一项,这将大大节俭内存

尾递归

  • 函数调用本身,称为递归。如果尾调用本身,就称为尾递归
  • 递归十分消耗内存,因为须要同时保留成千上百个调用帧,很容易产生“栈溢出”谬误(stack overflow)。但对于尾递归来说,因为只存在一个调用帧,所以永远不会产生“栈溢出”谬误

严格模式

  • ES6 的尾调用优化只在严格模式下开启,失常模式是有效的

尾递归优化的实现

  • 就是采纳“循环”换掉“递归”
function sum(x, y) {if (y > 0) {return sum(x + 1, y - 1);
  } else {return x;}
}

sum(1, 100000) // Uncaught RangeError: Maximum call stack size exceeded(…)

// 改写为蹦床函数
function trampoline(f) {while (f && f instanceof Function) {f = f();
  }
  return f;
}

function sum(x, y) {if (y > 0) {return sum.bind(null, x + 1, y - 1); // 这里会返回一个新的函数,并传入参数
  } else {return x;}
}
  • 蹦床函数并不是真正的尾递归优化,上面的实现才是
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;
    }
    return undefined;
  };
}

var sum = tco(function(x, y) {if (y > 0) {return sum(x + 1, y - 1) // 始终都是 undefined,所以防止了递归
  }
  else {return x}
});

sum(1, 100000) // 100001

7、函数参数的尾逗号

8、Function.proptype.toString()

  • toString()办法返回函数代码自身,以前会省略正文和空格
function /* foo comment */ foo () {}

foo.toString() 
// function foo() {}
  • 批改后的 toString() 办法,明确要求返回截然不同的原始代码
function /* foo comment */ foo () {}

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

9、catch 命令的参数省略

  • ES2019 做出扭转能够省略
try {// ...} catch {// ...}
退出移动版