ES6中函数的扩展

函数参数的默认值

1. 基本用法

之前不能为参数指定默认值,必须写判断

function log(x, y) {
    if(typeof y === 'undefined') {
        y = 'world';
    }
    console.log(x, y);
}
log('hello'); // hello world
log('hello, 'china'); // hello china

现在可以直接写默认值,直接写在参数的后面

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

好处:可以让人知道那些参数是可以省略的,利于代码优化

参数变量是默认声明的,所以不能在用let和const再次声明,否则会报错。

参数默认值不是传值,每次调用函数会重新计算默认值表达式的值,不是一成不变的,若表达式值有所变化,默认值也会随之改变

let x = 9;
function foo(y = x + 1) {
    console.log(p);
}
foo(); // 10
x = 10;
foo(); // 11

2. 与解构默认值一块使用

函数的参数可以解构、解构也可以设置默认值。也叫双重默认值。

解题方法先考虑解构,再考虑参数的默认值

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

例子:

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

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

m1(); // [0, 0]   先看解构,右侧没有值,但左侧有解构默认值为x = 0, y = 0
m2(); // [0, 0]     先看解构,解构右侧没有值,左侧解构出来了,但没有默认值,应该为undefined,但参数有默认值,所以以默认值为准x = 0, y = 0

m1({x: 3, y: 8}); // [3, 8] 直接解构
m1({x: 3, y: 8}); // [3, 8]  直接解构

m1({x: 3}); // [3, 0] 直接解构 右侧x存在为3, y不存在但解构出来y有默认值
m2({x: 3}); // [3, undefined]

m1({}); // [0, 0]  直接解构
m2({}); // [0, 0]

3. 参数默认值的位置

如果参数有默认值,要写在参数的末尾。

因为要不是写尾部,则必须填写这个参数,否则将这个参数写成undefined才能使这个参数的默认值有效。

function f(x = 1, y) {
    return [x, y];
}

f(, 1); // 报错

4. 函数的length属性

如果某个参数设置了默认值,则length属性时返回是不包括该参数

function f1(x, y, z = 1) {
    
}
f1.length // 2  因为参数z设置了默认值

因为函数length属性含义是该函数预期要传入的参数个数,因为某个参数有了默认值,则不用传也没事,所以就不包括。

5. 作用域

只有在参数有默认值的情况下存在,如果参数一旦有了默认值,函数进行声明初始化时,参数就就会形成一个作用域,等到初始化结果后,这个作用域才会消失。

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


在调用f时,参数y = x 会形成一个作用域,在这个作用域中x没有声明,就会往上找,找到全局x,赋值给参数y。如果全局也没有x,就会报错。

也存在暂时性死区,不能没有声明前就使用变量。

6. 应用

利用参数默认值可以指定某个参数不能省略,如果省略就报错。

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

function foo(mustBeProvided = throwIfMissing()) {
    return mustBeProvided;
}
foo(); // 报错

上面代码中,如果调用foo函数时,不传参数就直接会走默认值,而默认值是一个抛异常的函数。

rest参数

形式为...变量名,用来获取函数的多余参数。这样就不必使用arguments对象,rest参数搭配的变量是一个数组,该变量将多余的参数放入其中。

function add(...values) {
    console.log(values);
}
add(1, 2, 3); // [1, 2, 3]

rest参数必须写在最后,否则就会报错。

函数中的length属性不会计算rest参数

(function(a) {}).length // 1
(function(...a){}).length // 0

严格模式

如果函数参数使用了默认值,解构赋值或者扩展运算符,那么函数内部就不能显式设定严格模式,否则就会报错。

// 报错
function foo(a, b = a) {
    'use strict'
    // code
}

原因是因为,函数执行时,先执行参数,在执行函数体,而是否使用严格模式是在函数体内才知道。

例如:

function doSomething(value = 070) {
    'use strict'
}

在严格模式下八进制不能使用0表示,但是一开始的时候可以执行成功,后来又报错。

解决方法

设置成全局的严格模式

'use strict'
function foo (x =1) {}

将函数包在一个无参的立即执行函数内

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

name属性

返回该函数的函数名。

在ES5中一个匿名函数赋值给变量时,name返回空字符串,而在ES6中会返回实际的函数名。

Function构造函数返回的函数实例,name属性的值为anonymous

(new Function).name // anonymous

bind返回的函数,name属性值会加上bound前缀

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

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

5 箭头函数

使用一个箭头来定义函数,当参数为多个时,参数部分用括号包裹。

和变量解构结合使用

注意点:

  • 函数体内的this对象就是定义时所在的对象,而不是使用时所在的对象,没有自己的this。
  • 不可以当作构造函数,也就是说不能使用new命令,否则会抛出错误。
  • 不可以使用argument对象,该对象在函数体内不存在。如果要用,可以使用rest参数。
  • 不可以使用yield命令,因此箭头函数不能用作Generator函数。

由于箭头函数没有自己的this,所以也不能使用call()、apply()、bind()这些方法去改变this的指向。

箭头函数还可以嵌套使用

6. 绑定this

使用双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象作为上下文环境(this对象)绑定到右边的函数上。

foo::bar
// 等同于
bar.bind(foo)

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上。

var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;

双冒号运算符返回的是原对象,所以可以采用链式写法。

7. 尾调用优化

1. 什么是尾调用?

是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数。

注意点:

一定是最后一步,一定是调用另一个函数

不能是含有函数的表达式,或者返回的是上一步函数调用的结果

2. 如何优化尾调

需要知道的是:在函数调用时会在内存中形成一个调用记录,又称调用帧,用来保存位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方还会形成一个B的调用帧,等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用C,那就还有一个C的调用帧,依次类推。所有的调用帧就形成一个调用栈

而尾调是函数的最后一步,不需要保留外层函数的调用帧,因为调用的位置,内部信息都不会再用到了,直接用内层函数的调用帧取代外层函数的即可。

尾调用优化就是只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时调用帧只有一项,就会大大节省内存,这也是尾调用优化的意义。

注意:

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

3. 尾递归

函数调用自身就是递归,如果尾调用自身就称为尾递归。

递归非常耗费内存,因为需要同时保存成百上千个调用帧,很容易造成栈溢出,但对于尾递归来说,只存在一个调用帧,因此不会发生栈溢出

在ES6中只要尾递归就一定不会造成栈溢出

4. 递归函数如何改写成尾递归

尾递归的实现往往需要改写递归函数,来确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。

如何做到

  • 在尾递归函数之外再提供一个正常形式的函数
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
  • ES6的函数默认值
function factorial(n, total = 1) {
    if (n === 1) return total;
    return factorial(n - 1, n * total);
}
factorial(5); // 120

5. 严格模式

ES6的尾调模式只在严格模式下开启。

你可能感兴趣的:(es6,javascript,前端)