ES6读书简记·函数扩展

CY 2019年01月08日 685次浏览

函数参数默认值

ES5的时候给函数参数赋值默认值的时候经常的写法是:

y = y || "World";

但是y赋值一个空字符串他也会变为默认值"World",所以有了下面的写法:

if (typeof y === 'undefined') {
  y = 'World';
}

ES6中加入了参数设置默认值的写法:

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

如果使用了默认值,存在下面的限制:

  • 函数体内不能使用letconst再次声明参数
  • 函数不能存在同名参数
  • 默认值如果是一个表达式,那么默认值每次都要重新计算的
let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101
  • 默认值是可以和解构赋值一起使用的,但是结构赋值的默认值并不是函数参数的默认值
function foo({x, y = 5}) { // 这样的写法并不是参数的默认值
  console.log(x, y);
}

function foo({x, y = 5} = {}) { // 这才是参数的默认值,这叫做双重默认值
  console.log(x, y);
}

对于双重默认只来说,推荐使用在解构赋值中使用默认值,因为这样就算真的给参数传递了一个空对象,解构赋值中的默认值依然生效

例如:

function m1({x = 0, y = 0} = {}) {
  return [x, y];
}

这时参数传递一个空对象,前面的xy依然是有值的

参数默认值的位置

推荐将参数的默认值的位置写在后面的参数,这样就能在传递参数的时候省略掉后面的参数,如果参数的默认值在前面或者在中间,都没有办法省略的,只能传递一个undefined

注意参数的默认值只有undefined生效,null不生效

函数的length属性

对于rest参数和有默认值的参数都不会被计入到length属性中,而且,默认值后面的参数也不会被计入到length属性中

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

默认参数的作用域

带有默认值参数的函数在初始化的时候,参数区域会形成一个单独的作用域,初始化完成后,这个作用域就会消失,如果没有参数的默认值,也就不会形成这种作用域。

这种单独的作用域产生的结果如下:

var x = 1;

function f(x, y = x) { // 这里的y不会等于全局的x,而是参数内部的x
  console.log(y);
}

f(2) // 2

什么时候会等于全局的x呢?

let x = 1;

function f(y = x) { // 因为参数中没有x,所以这个时候y就等于全局的x
  let x = 2;
  console.log(y);
}

f() // 1

应用

如果某一个参数一定不能省略,就可以用默认值来实现抛异常

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

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

foo()
// Error: Missing parameter

如果说参数的默认值是undefined,表明这个参数是可以省略的。

rest参数

这个东西和Java里面的可变参数是一个意思的,基本上每种语言的可变参数都是一样的,ES6中的可变参数,就是一个真实的数组,和之前的arguments这样的类数组对象不一样,可变参数可以直接使用数组能使用的方法,可变参数必须放在参数的最后一个位置

函数的length属性也不包含rest参数

对于严格模式的限制

只要参数使用了默认值、解构赋值、或者扩展运算符,就不能显式指定严格模式,否则报错。

但是可以下面这样写,就可以绕开这个限制

  • 设定全局性的严格模式
'use strict';

function doSomething(a, b = a) {
  // code
}
  • 把函数包在一个无参数的立即执行函数里面。
const doSomething = (function () {
  'use strict';
  return function(value = 42) {
    return value;
  };
}());

函数的name属性

ES6将函数的name写入标准

  • 匿名函数赋值给一个变量,那么使用这个变量.name会返回变量的名字,例如将一个匿名函数赋值给f变量,使用f.name会返回f
  • 具名函数赋值给一个变量,使用变量.name会得到具名函数原来的名字,而不是变量名,例如将一个具名函数fun赋值给f变量,这个时候使用f.name会得到fun,而不是f
  • 使用Function构造函数返回一个函数实例,name属性的值为anonymous
  • bind返回的函数,name属性会加上bound前缀,例如"bound foo"或者"bound ",注意这里是有空格的。

箭头函数(重点)

箭头函数在日常使用中的场景太多,再来记录一些它的使用注意事项

  • 如果函数只有一个参数,圆括号可以省略,这就意味着,没有参数或者又多个参数,圆括号就不能省略

  • 如果代码块只有一条语句,并且要作为返回值,可以省略大括号和return,这也就意味着,多条语句不能省略大括号和return

  • 如果函数只返回一个对象,但是不想写return,由于对象是用大括号{}括起来的,所以为了避免和函数代码块的大括号混淆,必须要在对象外面加上括号,不加括号会和预期的结果不一样。

  • 如果箭头函数只有一条语句且不需要返回值,但是还不想写大括号,这个时候需要在语句前面加上void

  • 箭头函数内部是不能使用arguments对象的,如果要这样用,可是使用rest参数代替

  • 箭头函数是没有this对象的,也就自然而然无法通过call()apply()bind()方法改变箭头函数的this作用域,它的this和它所处的环境有关,所以在回调函数中可以放心的使用this了。这个this不会变成回调函数运行时所处的环境。比如说下面的情况:

    // jQuery 的写法
    // 这个时候回调中的this指向的是定义时的环境
    // 而不会被调用回调时候的环境所影响
    $('#button').on('click', () => {/* ... */});
    
  • 箭头函数不能当作构造函数来使用,就是因为他没有this

  • 箭头函数不可以使用yield命令,因为没有办法让箭头函数声明成Generator函数

  • 箭头函数中不仅没有thisargumentssupernew.target也是不存在的

不适合使用箭头函数的情况:

  • 定义一个对象中的方法的时候
  • 需要动态this的时候

其他扩展

ES2017 允许函数的最后一个参数有尾逗号(trailing comma

ES2019 对函数实例的toString()方法做出了修改,要求返回和原来一模一样的原始代码

ES2019 允许省略catch 命令的参数

try {
  // ...
} catch {
  // ...
}