ES6 函数的拓展

1.函数参数的默认值

1.1 基本用法

ES5函数参数没有默认值,通常这样传默认值

function log(x,y) {
    if(typeof y === 'undefined') {
        y = 'world';
    }

    console.log(x,y);
};

log('hello');       // hello world
log('hello',false); //hello false
log('hello','');    //hello

ES6引入了函数默认值,使函数在需要使用默认值的情况下代码更加简洁。

function log(x,y = 'world') {
        console.log(x,y);
}
log('hello');       // hello world
log('hello',false); //hello false
log('hello','');    //hello

这里需要注意两点

1)函数参数在函数体内不能用let 或者const再次声明导致重复声明变量

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

2)如果函数参数的传的默认值是一个表达式,表达式每次需要重新计算

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

另一个例子

function point(x = 0, y = 0) {
    this.x = x;
    this.y = y;
}
const p = new point(3,4);
console.log(p);   //point {x: 3, y: 4}
const q = new point();
console.log(q);   //point {x: 0, y: 0}

ES6 引入函数默认值的好处在于:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。

1.2 与解构赋值默认值结合使用
code one

function foo ({x, y = 3}) {
        console.log(x,y);
}
foo();   //Cannot destructure property `x` of 'undefined' or 'null'.
foo({}); //undefined 3
foo({x:1}) //1 3
foo({x:4,y:5}); //4 5

同样与也可以与数组解构赋值结合使用

code two

function foo ([x,y]=[1,3]) {
    console.log(x,y);
}
foo([]);  //undefined undefined
foo([3,4]); // 3 4

code one中上只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值生成。如果函数foo调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,如何提供函数的参数默认值?只需等于一个空对象即可。

function foo ({x, y = 3} = {}) {
    console.log(x,y);
}
foo();  //undefined 3

下面是另一个解构赋值默认值的例子。

function fetch(url,{body = '', method = 'GET', headers = {} }) {
    console.log(method);
};
fetch('http://www.baidu.com',{});  //GET
fetch('http://www.baidu.com');     // Cannot destructure property `body` of 'undefined' or 'null'.

给第二个参数传入默认值,这样在给函数传值时可以省略第二个参数而不会报错。

function fetch(url,{body = '', method = 'GET', headers = {} } = {}) {
    console.log(method);
};
fetch('http://www.baidu.com');  //GET

考虑下面两种函数参数的区别

//函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;

function fun1({x=0,y=0} = {}) {
        console.log ([x,y]);
}

//函数参数的默认值是一个有具体属性的对象,但没有设置对象解构赋值的默认值
function fun2 ({x,y} = {x: 0, y: 0}) {
    console.log ([x,y]);
}

//函数没有传参数
fun1();   //[0,0]
fun2();   //[0,0]

//函数传参数且x,y都有值
fun1({x:1, y:3});  //[1, 3]
fun2({x:1, y:3});  //[1, 3]
//x 有值,y 无值的情况
fun1({x:1}); //[1,0]
fun2({x:1}); //[1,undefined]
//函数有参数,但x,y都无值
fun1({});  //[0,0]
fun2({});  //[undefined, undefined]

1.2 有默认值参数的位置

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

```
function f1(x=1,y) {
    console.log([x,y]);
}
f1(2); //[2, undefined]

function f2(y,x=1) {
    console.log([[x,y]]);
}
f2(2); //[1,2]
```

所以如果设置参数默认值,应该将参数默认值写在后面,没有默认值的参数写在前面。

1.3函数的length属性

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

function f1(x, y, z = 3) {
    console.log(f1.length);
};
f1();  //2

function f2(x=1, y=2, z = 3) {
    console.log(f2.length);
};
f2();   //0

//如果函数的参数有默认值且不是尾参数,那么含有默认值的参数后面的参数不计入length
function f3(x=1, y=2, z) {
    console.log(f3.length);
};
f3();  //0

1.4 作用域

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

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

上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。

let x = 1;
function f(y=x) {
    let x = 2;
    console.log(y);
}
f();  //1
f(3); //3, y值为3,默认值失效

上面代码中,函数f调用时,参数y = x形成一个单独的作用域。这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x。函数调用时,函数体内部的局部变量x影响不到默认值变量x。

这里特别说明一点:在ES6中 let x = x;无论在声明变量,还是在函数给定默认参数都会报错。

var x = x;   //不报错,x为undefined
let x = x;   //报错,x is not defined

再看

let x = 3;
function f(x = x) {
    console.log(x + 1);
};
f(); //  ReferenceError: x is not defined
f(3); //4

所以在项目中尽量避免let x = x;这种写法。

函数的参数也可以是一个默认值,同样遵守作用域规则,这种情况在实际中很少用到,这里就不作讨论,想了解更多细节请参考ES6入门

1.5 应用
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。

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

function foo(mustBeProvided = throwIfMissing()) {
  console.log(mustBeProvided+1);
}

foo();   // Missing parameter
foo(1);  //2

2.rest参数

ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function add(...values) {
let sum = 0;
    for(let item of values) {
        sum += item ;
    }
    console.log(sum);
}
add(1,2,3);

上面代码的add函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。
注意,这里let item不能写成let var,ES6入门中是let var of values
会报错:Unexpected token var 。var是一个关键词,不能用作变量名。

rest 参数代替arguments变量

// arguments变量的写法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。

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

function f1(...items,a) {
    return;
}
f1(); //Rest parameter must be last formal parameter

函数的length属性不包括rest参数

function f1(a,...items) {
        return;
    }
function f2(a=3,...items) {
    return;
}
console.log(f1.length); //1
console.log(f2.length); //0

3.严格模式
从 ES5 开始,函数内部可以设定为严格模式。

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

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)设定全局性的严格模式

'use strict';

function doSomething(a, b = a) {
  // code
}

2)函数包在一个无参数的立即执行函数里面

const doSomething = (function () {
  'use strict';
  return function(value = 42) {
    return value;
  };
}());

4.name属性
函数的name属性,返回该函数的函数名。

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

如果将一个匿名函数赋值给一个变量,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。

function foo() {};
foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "

5.箭头函数

5.1 基本用法
ES6 允许使用“箭头”(=>)定义函数。一个参数可以省略圆括号

var f = v => v;
<=>
var f = function(v) {
    return v;
}

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

//一个参数
var f = () => 6;
<=>
var f = function() {
    return 6;
}
console.log(f());  //6


----------
//多个参数,参数用圆括号包括起来

var f = (num1,num2) => num1 + num2;
<=>
var f = function(num1, num2) {
    return num1 + num2;
}
console.log

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

var f = (num1,num2) => {
    let num3 = num1 + 1;
    let num4 = num2 + 2;;
    return [num3,num4];
}
console.log(f(1,2));  //[2,4]

如果箭头函数返回一个对象,需要在对象外面加上圆括号否则会报错。

var f = (x,y) => ({x:1,y:2});
console.log(f());  //{x: 1, y: 2}

如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。

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

箭头函数可以与变量解构结合使用。

const full = ({first,last}) => first + ' ' + last;
<=>
function full(person) {
  return person.first + ' ' + person.last;
}
full({first:'harry',last:'potter'}) //harry potter

箭头函数使得表达更加简洁。

const isEven = n => n % 2 == 0;
const square = n => n * n;
console.log(isEven(3));   //false
console.log(square(2));   //4

箭头函数的一个用处是简化回调函数。

// 正常函数写法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭头函数写法
[1,2,3].map(x => x * x);

另一个例子是

// 正常函数写法
var result = values.sort(function (a, b) {
  return a - b;
});

// 箭头函数写法
var result = values.sort((a, b) => a - b);

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]]

5.2 使用注意点
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

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

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

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。
this在JS中是一个比较复杂的问题,我会在另一篇文章中介绍传统函数的this和箭头函数this的区别。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值