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属性不计入后面的参数。
(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) { ··· } //可省略形式为…变量名,用于获取函数多余的参数,rest参数搭配的变量是一个数组,将多余的参数放入数组中。rest参数之后不能再有其他参数,否则报错,不包括length属性。
function push(array, ...items) { items.forEach(function(item) { array.push(item); console.log(item); }); } var a = []; push(a, 1, 2, 3)ES2016中规定只要函数参数使用了默认值、解构赋值、或扩展运算符,那么函数内部就不能显式的设定为严格模式,否则报错。因为函数内部的严格模式同时适用于函数体和函数参数,但参数先于函数体执行。有两种方法可以规避这种限制,第一种是设定全局性严格模式,二是把函数包在一个无参数的立即执行函数里面。
1. 'use strict'; function doSomething(a, b = a) { // code } 2. const doSomething = (function () { 'use strict'; return function(value = 42) { return value; }; }());函数name属性,返回该函数的函数名。如果将一个匿名函数赋给一个变量,name属性也会返回实际函数名。Function构造函数返回的函数实例,name属性的值为anonymous。bind返回的函数,name属性值会加上bound前缀。
(new Function).name //"anonymous" function foo() {}; foo.bind({}).name //"bound foo" (function(){}).bind({}).name //"bound"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注意:这几种情况不属于尾调用
// 情况一 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允许函数的最后一个参数有尾逗号。
明确要求返回一模一样的原始代码
function /* foo comment */ foo () {} foo.toString() // "function /* foo comment */ foo () {}"允许省略参数