函数形参的默认值

es5中模拟默认参数

timeout和callback 为可选参数 如果不传相应的参数 系统会给它们赋予一个默认值,
但 timeout 如果传参转换为布尔值是假值 例如:undefined null false 0 NaN “” ‘’ 都会被赋值2000;

  1. function makeRquest(url, timeout, callback) {
  2. timeout = timeout || 2000; // timeout 如果传转换为布尔值为假的值 例如:undefined null false 0 NaN "" '' 都会被赋值2000;
  3. callback = callback || function () { };
  4. console.log(url, timeout, callback);
  5. }
  6. makeRquest( 123, 0, null);; // 123 2000 ƒ () { }

更安全的做法

        function makeRquest(url, timeout, callback) {
            timeout = (typeof timeout !== "undefined") ? timeout : 2000; 
            callback = (typeof callback !== "undefined") ? callback : function () { };

            console.log(url, timeout, callback);
        }
        makeRquest( 123, 0, null); // 123 0 null

es6中的默认参数值

es6使用默认参数的方法
1传参undefined或者不写(若后面有形参需要传参则填undefined)

相较于es5的优势
可传参 NaN false null 0

        function makeRquest(url, timeout = 2000, callback = function () { }) {

            console.log(url, timeout, callback);
        }
        //使用参数timeout和参数callback的默认值
        makeRquest("/foo"); // /foo 2000 ƒ () { }
        //使用参数callback的默认值
        makeRquest("/foo", 500); // /foo 500 ƒ () { }
        //不使用默认值
        makeRquest("/foo", 500, function abc() { }); //foo 500 ƒ abc() { }
                //可传任意参数NaN false
                makeRquest("/foo", NaN, false); //foo NaN false

声明函数时,可以为任意参数指定默认值,在已指定默认值的参数后可以继续声明无默认值参数

        function makeRquest(url, timeout = 2000, callback) {

            console.log(url, timeout, callback);
        }
        //使用参数timeout的默认值
        makeRquest("/foo", undefined, 'abc'); // /foo 2000 abc
        //不使用参数timeout的默认值
        makeRquest("/foo", 500); // /foo 500 undefined
        //不使用默认值
        makeRquest("/foo", null, false); //foo null false

默认参数值对arguments对象的影响

非严格模式

在非严格模式下,命名参数的变化会同步更新到arguments对象中,所以当first和second被赋予新值时,arguments[0]和arguments[1]相应地也更新
所以比较的结果都为true

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

严格模式

然而,在ECMAScript 5的严格模式下,取消了arguments对象的这个令人感到困惑的行为,无论参数如何变化,arguments对象不再随之改变。

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

es6使用默认参数值
在es6中,如果一个函数使用了默认参数值(哪怕只是形参中的默认参数),则无论是否显式定义了严格模式,arguments对象的行为都将与es5严格模式下保持一致, 无论参数如何变化,arguments对象不再随之改变。
默认参数值的存在使得arguments对象保持与命名参数分离(形参的更改不会同步更新到arguments对象中)
arguments对象中始终是调用函数时传递的全部参数 与 默认参数值不一致

        function mixArgs(first, second = "b") {
            console.log(arguments); // ["a"]
            console.log(arguments.length); //1
            console.log(first, arguments[0], second, arguments[1]); // a a b undefined
            console.log(first === arguments[0]); //true
            console.log(second === arguments[1]); //false
            first = "c";
            second = "d";
              //形参的更改不会同步更新到arguments对象中
            console.log(first, arguments[0], second, arguments[1]); // c a d undefined
            console.log(first === arguments[0]); // false
            console.log(second === arguments[1]); // false
        }
        mixArgs("a");

默认参数表达式

可以通过函数执行来得到默认参数的值

        function getValue() {
            return 5;
        }

        function add(first, second = getValue()) {
            return first + second;
        }
        console.log(add(1, 1)); //2
        console.log(add(1)); // 6

初次解析函数声明时不会调用getValue()方法,只有当调用add()函数且不传入第二个参数时才会调用。

        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
                console.log(add()); //NaN

默认参数是在函数调用时求值(如果在函数解析时求值,后定义的参数获取不到先定义的参数就会undefined+undefined=NaN),
所以可以使用先定义的参数作为后定义参数的默认值

        function add(first, second = first) {
            return first + second;
        }
        console.log(add(1, 1)); //2
        console.log(add(1)); //2

可以将参数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 不传second参数时则调用getValue()方法

在引用参数默认值的时候,只允许引用前面参数的值,即先定义的参数不能访问后定义的参数

        function add(first = second, second) {
            return first + second;
        }
        console.log(add(1, 1)); //2
        console.log(add(undefined, 1)); //报错

默认参数的临时死区

默认参数也有临时死区,在这里的参数不可访问。与let声明类似,定义参数时会为每个参数创建一个新的标识符绑定,该绑定在初始化之前不可被引用

add(first = second, second)

add(1,1)           
//等价于
let first = 1; 
let second = 1;

add(undefined,1)
//等价于 
let first = second;
let second = 1;

处理无命名参数

es5中的无命名参数
arguments对象包含的则是所有传入的参数,(arguments[0]是源数组object 所以查找索引从1开始 排除object)

        function pick(object) {
            let result = Object.create(null);
          // 
            for (let i = 1; len = arguments.length, i < len; i++) {
                result[arguments[i]] = object[arguments[i]]; //遍历传入对象每一个属性 并赋值给新对象
            }
            return result;
        }
        let book = {
            title: "abc",
            author: "ABC",
            year: 2020
        };
        let bookData = pick(book, "title", "author", "year");
        console.log(bookData.title, bookData.author, bookData.year); // abc ABC 2020

不定参数

在命名参数前添加三个点(…)就表明这是一个不定参数 该参数为一个数组,包含着自它之后传入的所有参数,通过这个数组名可逐一访问里面的参数
不定参数keys包含的是实参bookt之后传入的所有参数此例中是(”title”, “author”, “year”)
(而arguments对象包含的则是所有传入的参数,(book, “title”, “author”, “year”))

        function pick(object, ...keys) {
            console.log(object, keys);  // {title: "abc", author: "ABC", year: 2020}  ["title", "author", "year"]
            let result = Object.create(null);
            for (let i = 0; len = keys.length, i < len; i++) {
                result[keys[i]] = object[keys[i]]; //遍历传入对象每一个属性 并赋值给新对象
            }
            console.log(result); //{title: "abc", author: "ABC", year: 2020}
            return result;
        }
        let book = {
            title: "abc",
            author: "ABC",
            year: 2020
        };
        let bookData = pick(book, "title", "author", "year");
        console.log(bookData.title, bookData.author, bookData.year); // abc ABC 2020


                console.log(pick.length); //1

注:函数的length属性统计的是函数命名参数的数量,不定参数的加入不会影响length属性的值.
上例中 pick()函数的length的值为1,因为只会计算命名参数 objcet

不定参数的使用限制

1.每个函数最多只能声明一个不定参数,而且一定要放在所有参数的末尾

        //语法错误:不定参数后不能有其他命名参数 不定参数keys只能放在末尾,不能在不定参数后声明其他参数
        function pick(object, ...keys,last) {
            let result = Object.create(null);
            for (let i = 0; len = keys.length, i < len; i++) {
                result[keys[i]] = object[keys[i]];
            }
            console.log(result);
            return result;
        }

2.不定参数不能用于对象字面量setter之中

        let object = {
            // 语法错误,不可以在setter中使用不定参数
            set name(...value) {
                // 执行一些逻辑
            }
        }

因为对象字面量seter的参数有且只能有一个,而在不定参数的定义中,参数的数量可以无限多,所以在当前的上下文中不允许使用不定参数

不定参数对arguments对象的影响

不定参数的传入并不会影响arguments
原本es4 草案中打算移除arguments添加不定参数的特性,es4从未标准化,直到es6引入了不定参数 arguments也被保留下来

        function checkArgs(...args) {
            console.log(args.length);
            console.log(arguments.length);
            console.log(args[0],arguments[0]);
            console.log(args[1],arguments[1]);
        }
        checkArgs("a","b");
        // 2
        // 2
        // a a
        // b b

arguments对象总是包含所有传入函数的参数 不定参数只包含不定参数的参数

增强的Function构造函数

通常用来动态创建新的函数 这种构造函数接收字符串形式的参数,分别为函数的参数及函数体。

        var add = new Function("first", "second", "return first + second");
        console.log(add(1, 1)); //2

es6增强了Function构造函数的功能,支持在创建函数时定义默认参数和不定参数
默认参数

        var add = new Function("first", "second = first", "return first + second");
        console.log(add(1)); //2

不定参数

        var add = new Function("...args", "return args[0]");
        console.log(add(1,2)); //2

展开运算符

判断参数中的最大值

        let value1 = 25,
            value2 = 50;
        console.log(Math.max(value1,value2)); //50

es5判断数组中的最大值

        let values = [25, 50, 75, 100];
        console.log(Math.max.apply(Math, values)); //100

apply的风险
image.png

        let values = [25, 50, 75, 100];
        console.log(Math.max(...values)); //100

限定最小值为0

        let values = [-25, -50, -75, -100];
        console.log(Math.max(...values,0)); //0

name属性

如何选择合适的名称

  • 函数声明:声明时对应的名称
  • 匿名函数:被赋值为该匿名函数的变量名称(函数表达式的名字)

          function doSomething() {
    
          }
          var doAnotherThing = function () {
    
          }
          console.log(doSomething.name); //doSomething
          console.log(doAnotherThing.name); //doAnotherThing
    

    name属性的特殊情况

  • 函数表达式与函数声明 : 取函数表达式 函数表达式的名字权重比函数声明的名字权重高

  • 方法:取对象的属性名
  • getter函数:取get + 函数名

          var doSomething = function doSomethingElse() {
    
          }
          var person = {
              get firstName(){
                  return "Nicholas"
              },
              sayName:function(){
                  console.log(this.name);
              }
          }
          console.log(doSomething.name); //doSomethingElse
          console.log(person.sayName.name); //sayName
          console.log(person.firstName.name); //get firstName
    

特例

  • 通过bind()函数创建的函数,其名称将带有”bound”前缀
  • 通过Function构造函数创建的函数,其名称将是”anonymous”

          var doSomething = function () {
    
          };
          console.log(doSomething.bind().name); // bound doSomething
          console.log((new Function()).name); // anonymous
    

明确函数的多用途

        function Person(name) {
            this.name = name;
        }

        var person = new Person("Nicholas");
        var notAPerson = Person("Nicholas");
        console.log(person);        //Person {name: "Nicholas"}
                                    // name: "Nicholas"
                                    // __proto__: Object

        console.log(notAPerson);// undefined

给notAPerson变量赋值时,没有通过new关键字来调用Person(),最终返回undefined(如果在非严格模式下,还会在全局对象中设置一个name属性)。只有通过new关键字调用Person()时才能体现其能力,就像常见的JavaScript程序中显示的那样。而在ECMAScript 6中,函数混乱的双重身份终于将有一些改变。JavaScript函数有两个不同的内部方法:[[Call]]和[[Construct]]。当通过new关键字调用函数时,执行的是[[Construct]]函数,它负责创建一个通常被称作实例的新对象,然后再执行函数体,将this绑定到实例上;如果不通过new关键字调用函数,则执行[[Call]]函数,从而直接执行代码中的函数体。具有[[Construct]]方法的函数被统称为构造函数。切记,不是所有函数都有[[Construct]]方法,因此不是所有函数都可以通过new来调用,例如,我们在本章后面讲解的箭头函数就没有这个[[Construct]]方法。

es5中判断函数被调用的方法

        function Person(name) {
            if (this instanceof Person) {
                this.name = name; //如果通过new关键字调用
            } else {
                throw new Error("必须通过new关键字来调用Person。");
            }
        }
        var person = new Person("Nicholas");
        var notAPerson = Person("Nicholas");

在这段代码中,首先检查this的值,看它是否为构造函数的实例,如果是,则继续正常执行;如果不是,则抛出错误。由于[[Construct]]方法会创建一个Person的新实例,并将this绑定到新实例上,通常来讲这样做是正确的,但这个方法也不完全可靠,因为有一种不依赖new关键字的方法也可以将this绑定到Person的实例上,如下所示:

        function Person(name) {
            if (this instanceof Person) {
                this.name = name; //如果通过new关键字调用
            } else {
                throw new Error("必须通过new关键字来调用Person。");
            }
        }
        var person = new Person("Nicholas");
        var notAPerson = Person.call(person, "Michael"); //有效
        console.log(person);
        console.log(notAPerson);

调用Person.call()时将变量person传入作为第一个参数,相当于在Person函数里将this设为了person实例。对于函数本身,无法区分是通过Person.call()(或者是Person.apply())还是new关键字调用得到的Person的实例。

元属性 new.target

为了解决判断函数是否通过new关键字调用的问题,ECMAScript 6引入了new.target这个元属性。元属性是指非对象的属性,其可以提供非对象目标的补充信息(例如new)。当调用函数的[[Construct]]方法时,new.target被赋值为new操作符的目标,通常是新创建对象实例,也就是函数体内this的构造函数;如果调用[[Call]]方法,则new.target的值为undefined。有了这个元属性,可以通过检查new.target是否被定义过来安全地检测一个函数是否是通过new关键字调用的,就像这样:

        function Person(name) {
                   console.log(new.target); //如果是new 关键字调用则 new.target 等于构造函数本身 fn Person(name){}
              console.log(typeof new.target); //如果是new 关键字调用则 new.target 等于function
            if (typeof new.target !== "undefined") {
                this.name = name;
            } else {
                throw new Error("必须通过new关键字来调用Person。");
            }
        }
        var person = new Person("Nicholas");
        var notAPerson = Person.call(person, "Michael"); //抛错
        console.log(person,notAPerson);

也可以检查new.target是否别某个特定构造函数所调用,

        function Person(name) {
            //     console.log(new.target); //如果是new 关键字调用则 new.target 等于构造函数本身 fn Person(name){}
              //      Person 等于 fn Person(name){}
            if (new.target === Person) {
                this.name = name;
            } else {
                throw new Error("必须通过new关键字来调用Person。");
            }
        }

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

        var person = new Person("Nicholas");
        console.log(person); //Person {name: "Nicholas"}
        var anotherPerson = new AnotherPerson("Nicholas"); //抛错

在这段代码中,如果要让程序正确运行,new.target一定是Person。当调用newAnotherPerson(“Nicholas”)时,真正的调用Person.call(this, name)没有使用new关键字,因此new.target的值为undefined会抛出错误。

注:在函数外使用new.target是一个语法错误。

块级函数

严格模式

es5中

        "use strict"

        if(true){

            // 在es5中抛出语法错误,在es6中不报错
            function doSomething() {
                // 空函数
            }
        }

es6中
在ECMAScript 6中,会将doSomething()函数视作一个块级声明,从而可以在定义该函数的代码块内访问和调用它

        "use strict"

        if (true) {

            console.log(typeof doSomething); //function
            // 在es5中抛出语法错误,在es6中不报错
            function doSomething() {
                // 空函数
            }
        }

在es6严格模式中:代码块内 块级函数会被提升至顶部, 代码块外:块级函数不存在 undefined
在定义函数的代码块内,块级函数会被提升至顶部,所以typeof doSomething的值为”function”,这也佐证了,即使你在函数定义的位置前调用它,还是能返回正确结果;但是一旦if语句代码块结束执行,doSomething()函数将不再存在。

块级函数的使用场景

  • 块级函数会被提升至块的顶部,
  • let顶一顶函数表达式不会被提升

          "use strict"
    
          if (true) {
    
              console.log(typeof doSomething); //报错
              // 在es5中抛出语法错误,在es6中不报错
              let doSomething = function () {
                  // 空函数
              }
              doSomething();
          }
          console.log(typeof doSomething);
    

    非严格模式下的块级元素

    标准 在es6非严格模式中 块级函数将提升至外围函数或全局作用域的顶部

          if (true) {
    
              console.log(typeof doSomething); //报错
              // 在es5中抛出语法错误,在es6中不报错
              function doSomething() {
                  // 空函数
              }
              doSomething();
          }
          console.log(typeof doSomething);
    

箭头函数

与普通函数的区别

· 没有this、super、arguments和new.target绑定 箭头函数中的this、super、arguments及new.target这些值由外围最近一层非箭头函数决定。(super将在第4章进行讲解。)
· 不能通过new关键字调用 箭头函数没有[[Construct]]方法,所以不能被用作构造函数,如果通过new关键字调用箭头函数,程序会抛出错误。
· 没有原型 由于不可以通过new关键字调用箭头函数,因而没有构建原型的需求,所以箭头函数不存在prototype这个属性。
· 不可以改变this的绑定 函数内部的this值不可被改变,在函数的生命周期内始终保持一致。
· 不支持arguments对象 箭头函数没有arguments绑定,所以你必须通过命名参数和不定参数这两种形式访问函数的参数。
· 不支持重复的命名参数 无论在严格还是非严格模式下,箭头函数都不支持重复的命名参数;而在传统函数的规定中,只有在严格模式下才不能有重复的命名参数。
箭头函数同样也有一个name属性,这与其他函数的规则相同

箭头函数的语法

单个参数

        let reflect = value => value;

            // 实则上相当于;

        let reflect = function (value) {
            return value;
        }

多个参数

用小括号将参数包裹起来

        let sum = (num1, num2) => num1 + num2;

                // 实则上相当于;

        let sum = function (num1, num2) {
            return num1 + num2;
        }

没有参数

没有参数也要用小括号

        let getName = () => "Nicholas";
            // 实则上相当于;
        let getName = function () {
            return "Nicholas";
        }

若希望编写由多个表达式组成的更传统的函数体,那么需要用话花括号包裹函数体,并显示地定义一个返回值

        let sum = (num1, num2) => {
            return num1 + num2;
        };
        // 实则上相当于;
        let sum = function (num1, num2) {
            return num1 + num2;
        }

空函数

如果想创建一个空函数 一对没有内容的花括号

        let doNothing = () => { };
            // 实则上相当于;
        let doNothing = function () { };

返回一个对象

将对象包裹在小括号里

        let getTempItem = id => ({ id: id, name: "Temp" });
            // 实则上相当于;
        let getTempItem = function (id) {

            return {
                id: id,
                name: "Temp"
            };
        };

创建立即执行函数表达式

es5写法

        let person = function (name) {

            return {
                getName: function () {
                    return name;
                }
            }
        }("Nicholas");
        console.log(person.getName());  // "Nicholas"

箭头函数写法
去掉fn关键字用小括号替代 + 箭头=>
注意,小括号只包裹箭头函数定义,没有包含(“Nicholas”),这一点与正常函数有所不同,由正常函数定义的立即执行函数表达式既可以用小括号包裹函数体,也可以额外包裹函数调用的部分。

        let person = ((name) => {

            return {
                getName: function () {
                    return name;
                }
            }
        })("Nicholas");
        console.log(person.getName());  // "Nicholas"

箭头函数没有this绑定

实际上,因为this绑定的是事件目标对象的引用(在这段代码中引用的是document),而没有绑定PageHandler,且由于this.doSonething()在目标document中不存在,所以无法正常执行,尝试运行这段代码只会使程序在触发事件处理程序时抛出错误

        let PageHandler = {

            id: "123456",

            init: function () {
                document.addEventListener("click", function (event) {
                    this.doSomething(event.type); //报错
                }, false);
            },

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

es5解法

        let PageHandler = {

            id: "123456",

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

            doSomething: function (type) {
                console.log("Handling" + type + "for" + this.id); //Handling click for 123456
            }
        };
        PageHandler.init();

箭头函数解法

        let PageHandler = {

            id: "123456",

            init: function () {
                document.addEventListener("click",
                    event => this.doSomething(event.type), false); // this指的是最近一层非箭头函数的this 
                                                                                                                      // 也就是init方法里的this :就是PageHandler{}对象
            },

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

// 实际上等同于
"use strict";

var PageHandler = {

    id: "123456",

    init: function init() {
        var _this = this;

        document.addEventListener("click", function (event) {
            return _this.doSomething(event.type);
        }, false); //this指的是最近一层非箭头函数的this 也就是init方法里的this :也急速PageHandler{}对象
    },

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

箭头函数中没有this绑定,必须通过查找作用域链来决定其值。如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this;否则,this的值会被设置为全局对象。

箭头函数不能用new关键字创建实例
箭头函数缺少正常函数所拥有的prototype属性,它的设计初衷是“即用即弃”,所以不能用它来定义新的类型。如果尝试通过new关键字调用一个箭头函数,会导致程序抛出错误,就像这个示例一样:

        var MyType = () => { },
            object = new MyType(); //报错 不能通过new关键字调用箭头函数

在这段代码中,MyType是一个没有[[Construct]]方法的箭头函数,所以不能正常执行newMyType()。也正因为箭头函数不能与new关键字混用,所以JavaScript引擎可以进一步优化它们的行为。同样,箭头函数中的this值取决于该函数外部非箭头函数的this值,且不能通过call()、apply()或bind()方法来改变this的值。

箭头函数和数组

自定义排序函数

        var result = values.sort(function (a, b) {
            return a - b;
        });

        var result = values.sort((a, b) => a - b);

诸如sort()、map()及reduce()这些可以接受回调函数的数组方法,都可以通过箭头函数语法简化编码过程并减少编码量。

箭头函数没有arguments绑定

箭头函数没有自己的arguments对象,且未来无论函数在哪个上下文中执行,箭头函数始终可以访问外围函数的arguments对象

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

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

箭头函数的辨识方法

可用typeof 和 instanceof 操作符调用箭头函数与调用其他函数并无二致

        var comparator = (a, b) => a - b;

        console.log(typeof comparator);  // function

        console.log(comparator instanceof Function) // true

可在箭头函数上调用 call()、apply()、bind()、但箭头函数的this值不会受这些方法的影响

        var sum = (num1, num2) => num1 + num2;

        console.log(sum.call(null, 1, 2));//3

        console.log(sum.apply(null, [1, 2])); //3

        var boundSum = sum.bind(null, 1, 2);

        console.log(boundSum()); //3

尾调用优化

尾调用:函数作为另一个函数的最后一条语句被调用

        function doSomething() {
            return doSomethingElse(); //尾调用
        }

ECMAScript 6中的尾调用优化

若满足以下条件,则搜索引擎自动进行尾调用

  • 尾调用不访问当前栈帧的变量(也就是说函数不是一个闭包)。
  • 在函数内部,尾调用是最后一条语句。
  • 尾调用的结果作为函数值返回。

例如

        "use strict"
        function doSomething() {
          // 优化后
            return doSomethingElse(); 
        }

不符合

        "use strict"
        function doSomething() {
            // 无法优化,无返回
           doSomethingElse(); 
        }
        "use strict"
        function doSomething() {
            // 无法优化,必须在返回值后添加其他操作。 *???存疑问
            return 1 + doSomethingElse();
        }
        "use strict"
        function doSomething() {
            // 无法优化,调用不在尾部
            var result = doSomething();
            return result;
        }
        "use strict"
        function doSomething() {
            var num = 1,
                func = () => num;
            // 无法优化,该函数是一个闭包
            var result = doSomething();
            return func();
        }

如何利用尾调用优化

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

                   // 无法优化,必须在返回后执行乘法操作
                return n * factorial(n - 1);
            }
        }

优化后

        function factorial(n, p = 1) {
            if (n <= 1) {
                return 1 * p;
            } else {
                let result = n * p;
            }
            // 优化后
            return factorial(n - 1, result);
        }

小结

在ECMAScript 6中,除了一些语法改进外函数没有太大的变化,但是变得比以前更易于使用了。
现在,可以为函数定义默认参数,而在ECMAScript 6之前,可能需要在函数体内添加额外的代码来检查参数是否存在,如若不存在则需要手动赋一个默认值。
也可以为函数定义不定参数,这个数组中包含其后所有的参数,由于使用的是真实数组,且可以根据需要决定要囊括到数组中的参数,因此不定参数是一个比arguments对象更灵活的决方案。展开运算符与不定参数形似,可以通过它解构数组并将每一个元素作为函数的独立参数使用。
在ECMAScript 6以前,如果要将数组中的元素作为独立参数传递给函数,只有以下两种方式:手动指定每一个参数或使用apply()方法。只要使用展开运算符,就可以轻松地将数组传入到任何函数中,且由于不再使用apply()方法,也就不需要担心函数的this绑定问题。函数中新增的name属性,有助于通过函数名称来对其进行调试或评估。ECMAScript 6也正式定义了块级函数的行为,即使在严格模式下块级函数也不再是一个语法错误了。
在ECMAScript 6中,普通的函数调用会触发函数的[[Call]]方法调用,通过new关键字调用函数会触发函数的[[Construct]]方法调用。
新增的元属性new.target可以帮助你检测函数是通过何种方式调用的。
ECMAScript 6中函数方面最大的改变是添加了箭头函数。箭头函数的设计目标是用来替代匿名函数表达式,它的语法更简洁,具有词法级的this绑定,没有arguments对象,函数内部的this值不可被改变,因而不能作为构造函数使用。尾调用优化可以帮助函数保持一个更小的调用栈,从而减少内存的使用,避免栈溢出错误。当程序满足优化条件时,引擎会自动对其进行优化。当然,你可能希望重写递归函数,从而使引擎更好地优化你的程序。