函数的扩展

函数的扩展

1.函数参数的默认值
基本用法

ES6允许为函数参数设置默认值,直接写在参数定义后面。

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

此方法除了简洁,有两个好处。一是,方便阅读代码,不用看函数体,意识到哪些参数可以省略。二是,有利于优化代码,即使拿掉参数,也不会导致代码无法运行。
函数参数变量是默认声明的,不能用let或const再次声明。函数不能有同名参数。参数默认值不是传值,每次都重新计算表达式的值。

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

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

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
与解构赋值默认值结合使用

有两种情况,一是对象解构赋值默认值,二是函数参数的默认值。

function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
  console.log(method);
}

fetch('http://example.com')
// "GET"

函数fetch没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量method才会取到默认值GET,出现了双重默认值。
练习:

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

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

上面两种写法都对函数的参数设定了默认值,区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值(x,y)的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值(x,y)的默认值。

// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]

// x 和 y 都有值的情况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]

// x 有值,y 无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]

// x 和 y 都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]

m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]
参数默认值的位置

一般情况下,定义了默认值的参数应该在尾部,因为赋值时容易看出省略哪个参数,如果在非尾部,无法省略,除非显示的输入undefined,null没有这个效果。

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属性不计入后面的参数。

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

设置参数的默认值后,参数会形成一个单独的作用域,等到初始化结束,作用域消失。
例1:

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

let x = 1;
function f(y = x) {       //相当于let y=x
  let x = 2;
  console.log(y);
}
f() // 1      如果没有let x = 1;会报错

例2:

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

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

上面代码中,函数foo的参数形成一个单独的作用域,声明了变量x和y,y的默认值是一个匿名函数,内部变量x指向第一个参数x。函数foo内部又声明了一个内部变量x,该变量与第一个参数x不在一个作用域,执行y后内部变量和全局变量x的值都没变。去掉var后,函数foo的内部变量x就指向第一个参数x,但全局变量依然不受影响。

应用

利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。也可以将参数默认值设为undefined,表明可省略。

function throwIfMissing() { //不可省略
  throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}
foo() //如果无参数,就会调用默认值 throwIfMissing函数,抛出错误
// Error: Missing parameter 

function foo(optional = undefined) { ··· } //可省略
2.rest函数

形式为…变量名,用于获取函数多余的参数,rest参数搭配的变量是一个数组,将多余的参数放入数组中。rest参数之后不能再有其他参数,否则报错,不包括length属性。

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}
var a = [];
push(a, 1, 2, 3)
3.严格模式

ES2016中规定只要函数参数使用了默认值、解构赋值、或扩展运算符,那么函数内部就不能显式的设定为严格模式,否则报错。因为函数内部的严格模式同时适用于函数体和函数参数,但参数先于函数体执行。有两种方法可以规避这种限制,第一种是设定全局性严格模式,二是把函数包在一个无参数的立即执行函数里面。

1.
'use strict';
function doSomething(a, b = a) {
  // code
}
2.
const doSomething = (function () {
  'use strict';
  return function(value = 42) {
    return value;
  };
}());
4.name属性

函数name属性,返回该函数的函数名。如果将一个匿名函数赋给一个变量,name属性也会返回实际函数名。Function构造函数返回的函数实例,name属性的值为anonymous。bind返回的函数,name属性值会加上bound前缀。

(new Function).name //"anonymous"
function foo() {};
foo.bind({}).name //"bound foo"
(function(){}).bind({}).name //"bound"
5.箭头函数
基本用法

ES6允许使用“箭头”定义函数。如果箭头函数不需要参数或多个参数,就使用圆括号代替参数部分。如果代码块部分多于一条语句,就要用大括号将他们括起来,并用return语句返回。如果返回一个对象,必须在对象外面加上括号,否则会报错。

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

箭头函数可以使表达式更简洁

const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
  return person.first + ' ' + person.last;
}

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 函数。

不适用场合

因为箭头函数使this从“动态”变成“静态”,所以有两种情况下,不能使用箭头函数。
(1)定义对象的方法,且该方法内部包括this。
(2)需要动态this的时候,也不应使用箭头函数。

箭头函数的嵌套

箭头函数内允许嵌套,可能可读性存在问题,可以采用下面的写法。

const plus1 = a => a + 1;
const mult2 = a => a * 2;
mult2(plus1(5))
// 12
6.尾调用优化(某个函数的最后一步是调用另一个函数)

注意:这几种情况不属于尾调用

// 情况一
function f(x){
  let y = g(x);
  return y;
}
// 情况二
function f(x){
  return g(x) + 1;
}
// 情况三
function f(x){
  g(x);
}
//情况三同下
function f(x){
  g(x);
  return undefined;
}
尾调用优化(只保留内层函数的调用帧)

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

function addOne(a){
  var one = 1;
  function inner(b){
    return b + one;
  }
  return inner(a);
}
尾递归

函数调用自身,称为递归。如果尾调用自身,就称为尾递归。防止“栈溢出”和超时。

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}
factorial(5) // 120

上面的代码计算n的阶乘,复杂度 O(n)。
如果改成尾递归,复杂度O(1)。

递归函数的改写

(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
7.函数参数的尾逗号

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

8.Function.prototype.toString()

明确要求返回一模一样的原始代码

function /* foo comment */ foo () {}
foo.toString()
// "function /* foo comment */ foo () {}"
9.catch 命令的参数省略
try {
  // ...
} catch {
  // ...
}

允许省略参数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值