理解es6---第三章 函数(箭头函数,spread parameters, rest parameters, 匿名参数)

英文电子书点此阅读《understanding es6》

目录

函数

带默认参数的函数
function makeRequest(url, timeout = 2000, callback = function() {}) {

    // the rest of the function

}
arguments的变化
  • 在ES5的非严格模式下,arguments是会实时更新的,反映了函数参数的变化。
  • 在ES5的严格模式下,arguments则不会更新

example:

function mixArgs(first, second) {
    "use strict";
    console.log(first === arguments[0]); // true
    console.log(second === arguments[1]);  //true
    first = "c";
    second = "d"
    console.log(first === arguments[0]); // false
    console.log(second === arguments[1]); // false
}

mixArgs("a", "b");

而在ES6中,arguments都不会更新,表现得如同es5的严格模式一样。而如果arguments里采用了默认参数,则会以实际传进去的实参为准

function mixArgs(first, second = "b",third,fourth = 1) {
    console.log(arguments);
    console.log([first,second,third,fourth])
}

mixArgs("a",undefined,2);

/*
["a", undefined, 2]
["a","b",2, 1]
*/

mixArgs("a",null,2);
/*
["a", null, 2]
["a", null, 2, 1]
*/
默认参数表达式

注意如果默认参数是一个函数的返回结果,那么它可能是会变化的

let value = 5;

function getValue() {
    return value++;
}

function add(first, second = getValue()) {
    return first + second;
}

console.log(add(1, 1));     // 2
console.log(add(1));        // 6
console.log(add(1));        // 7

//注意,由于仅当没有传入第二个参数时,getValue才被调用,因此值的改变可能发生在任意时刻,另外一定要确保second 是 函数的返回结果而没有遗漏括号,从而误写成函数本身

//还可以在第二个参数中引用第一个参数,但不能反过来获取后面的参数

function add(first, second = first) {
    return first + second;
}

function getValue(value) {
    return value + 5;
}

function add(first, second = getValue(first)) {
    return first + second;
}

console.log(add(1, 1));     // 2
console.log(add(1));        // 7

默认实参的暂时死区(temporal dead zone)

调用add(1,1) 和 add(1)实际是在运行以下代码

let first = 1
let second = 1 // let second = getValue(1)

当function add() 被首次执行时,first 和 second 的绑定被加在了针对参数的 TDZ 中,所以虽然second可以基于first初始化,但反过来就不可以了。实际上,是这么执行的:

let first = second  // 此刻,second处于TDZ中,因此对它的任何引用都会报错。

let second = 1

函数参数有他们自己的作用域(scope)和 TDZ,是与函数主体(body)的作用域分开的。函数参数的默认值不能取得函数内部中定义的变量。

函数中的匿名参数
  • rest parameters 剩余参数

    rest parameters 是指三个点之后跟着一个命名的参数。这个命名的参数为一个数组,包含着传给这个函数的剩余所有参数,这也就是"rest"一词的含义。比起arguments来,它有两个优势:

    1. 遍历参数不用从1开始,而是从头到尾
    2. 一眼就可以看出这个函数可以传入多个参数

    另外,rest parameters 不会影响函数的length属性,length代表的是被命名了的函数参数。

    它的限制在于:

    1. 只能有一个rest parameters
    2. rest parameters必须位于最后
    3. 不能用于对象的literal setter中,比如:
    let object = {
      
      // syntax error: can't use rest param in setter
      set name(...value){
    
      }
    
    }
    这个限制存在是因为object literal setters 被限制为仅有一个参数。而剩余参数从定义上说,就是一系列数值。
    
the spread operator

等于把rest parameters反过来。并且可以将它和别的参数合在一起用。

let values = [-25, -50, -75, -100]

console.log(Math.max(...values, 0));        // 0

函数的 name 属性

貌似唯一的用处是在调试时定位问题出在哪里,非常智能地提供了一切能提供的信息

还可能会在前面加描述符 如set get bound 等

匿名函数的name就是anonymous

函数的name只是一个值而已(stirng),不能通过它来获得一个函数的reference

函数的双重用处

当 new 一个 function 的时候,函数内部的 this 值是一个新的对象,而且会被返回。

javascript 有两个不同的函数内置方法,call 和 construct,且这两个方法只供内部使用。当一个函数没有用 new 调用的时候,call 方法被执行,而 new 一个 function 的时候是 construct 被调用。拥有 construct 的函数被称为 constructors。注意不是所有的函数都有construct,比如 arrow functions, 就没有 construct 方法。

在ES5中,通常是用 instanceOf 来判断是否正确地 new 了一个函数

function Person(name) {
    if (this instanceof Person) {
        this.name = name;   // using new
    } else {
        throw new Error("You must use new with Person.")
    }
}

ES6中则引入了元属性new.target。当函数的 construct 方法被调用时,new.target 会是 调用 new 的函数的 body。而当函数的 call 方法被调用时,new.target 是Undefined。

function Person(name) {
	// or  typeof new.target !== 'undefined'
    if (new.target === Person) {
        this.name = name;   // using new
    } else {
        throw new Error("You must use new with Person.")
    }
}

function AnotherPerson(name) {
    Person.call(this, name);
}

var person = new Person("Nicholas");
var notAPerson = Person.call(person, "Michael");    // error!
var anotherPerson = new AnotherPerson("Nicholas");  // error!

这里用 new AnotherPerson 也引发了报错,这是因为 new.target 必须是 Person (new.target === Person) , 或必须用 new 的方式调用Person (typeof new.target !== ‘undefined’)。 当 AnotherPerson 被调用的时候,接下来是用 Person.call(this, name)

block-level functions 定义在块级作用域内的函数

es5的严格模式下,不能在block 内部声明函数,会引发报错,最佳实践是函数表达式。

es6的严格模式下,则可以在block内声明函数,并且该声明会被提升到block的顶部,当block执行完毕后,该函数就不复存在了,这一点与let式的函数表达式很像。

es6的非严格模式下,block-level function 会被提升到全局、

箭头函数
  • 没有 this, super, arguments, new.target 绑定, 箭头函数中的这些值绑定的是包裹箭头函数的最内层非箭头函数。
  • 不能用new来调用,箭头函数没有construct内置功能
  • 没有prototype
  • 不能改变内部的 this 值。在函数的生命周期内this值是恒定的。
  • 没有 arguments 对象,只能用命名的形参或者 rest parameters 来获得函数的参数。
  • 不管在严格还是非严格模式下,都不能有重复命名的参数

this 问题得到了很好的解决

var PageHandler = {

    id: "123456",

    init: function() {
        document.addEventListener("click",
                event => this.doSomething(event.type), false);
    },

    doSomething: function(type) {
        console.log("Handling " + type  + " for " + this.id);
    }
};

箭头函数的意图是 用过即扔,所以不能作 constructor,在数组中应用特别方便

function createArrowFunctionReturningFirstArg() {
    return () => arguments[0];
}

var arrowFunction = createArrowFunctionReturningFirstArg(5);

console.log(arrowFunction());       // 5

使用箭头函数,则只有下面的写法是有效的: (() => {/*函数体*/})();

尾递归优化

es6在严格模式下做了尾递归优化,但需要满足以下条件:

  1. 尾部调用不需要用到当前栈的那一帧中的变量,即函数不是闭包
  2. 进行 尾调用 的函数在尾调用后不做任何事(尾调用尾调用,就是在最后的调用!)
  3. 尾调用的结果被作为函数的值返回
"use strict"
//这是满足尾递归优化的例子
function doSomething() {
    // optimized
    return doSomethingElse();
}

//以下都是不满足尾递归优化的例子

function doSomething() {

    // not optimized - no return
    doSomethingElse();
    
    //-----//

    // not optimized - must add after returning
    return 1 + doSomethingElse();
    
    //-----//

    // not optimized - call isn't in tail position
    var result = doSomethingElse();
    return result;

    //-----//

    var num = 1,
    func = () => num;

    // not optimized - function is a closure
    return func();
}


尾调用的优化最重要的应用是在函数的递归上。注意要满足优化条件,把用到的变量到放到函数的参数里面传回去。

function factorial(n) {

    if (n <= 1) {
        return 1;
    } else {

        // not optimized - must multiply after returning
        return n * factorial(n - 1);
    }
}


function factorial(n, p = 1) {

    if (n <= 1) {
        return 1 * p;
    } else {
        let result = n * p;

        // optimized
        return factorial(n - 1, result);
    }
}
Summary
  • 默认函数参数值
  • Rest Parameters 用数组的形式拿到传入的参数。而且不耽误前面的命名参数,比arguements更灵活
  • spread parameters 可以将数组解构成一个一个的参数。之前只能用apply来完成这一工作。
  • name属性 可以帮助 识别一个函数,用于debug中的定位,block-level的函数被正式定义,不会在严格模式下报错。
  • 函数内部的new.target属性可以用来识别该函数是用内置的call方式调用的还是用construct调用的
  • 引入箭头函数,本义是代替匿名函数。语法更简洁,this是用的外层的非箭头函数,没有arguments对象。不能用作constructor
  • 尾调用优化让函数在一个较小的堆栈空间内执行,内存更少,防止了栈溢出。优化在满足优化条件时自动完成,主要用于递归。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值