14、函数

最后更新于:2022-04-02 06:07:22

  在前面的章节中,已陆陆续续介绍了ES6为改良函数而引入的几个新特性,本章将会继续讲解ES6对函数的其余改进,包括默认参数、元属性、块级函数和箭头函数等。 ## 一、默认参数   在ES5时代,只能在函数体中定义参数的默认值,而自从ES6引入了默认参数(Default Parameter)后,就能让参数在声明时带上它的默认值,如下代码所示,func2()函数中的参数默认值在可读性和简洁性方面更为优秀。 ~~~ function func1(name) { name = name || "strick"; //ES5的参数默认值 } function func2(name = "strick") { //ES6的参数默认值 } ~~~ **1)undefined**   只有当不给参数传值或传入undefined时,才会使用它的默认值。即使传入和undefined一样的假值(例如false、null等),也得不到它的默认值,如下所示。 ~~~ function func(name = "strick") { return name; } func(undefined); //"strick" func(false); //false func(null); //null ~~~ **2)位置**   默认参数既可以位于普通参数之前,也可以位于其之后。例如下面的两个函数,都包含两个参数,其中一个带有默认值,依次执行,都能得到预期的结果。 ~~~ function func1(name = "strick", age) { return name; } function func2(name, age = 28) { return age; } func1(undefined); //"strick" func2("strick"); //28 ~~~ **3)默认值**   参数的默认值既可以是简单的字面量,也可以是复杂的表达式。在每次调用函数时,不仅参数会被重新初始化,默认值如果是表达式的话,还会将其重新计算一次。 ~~~ function expression1(name, full = "pw" + name) { return full; } expression1("strick"); //"pwstrick" expression1("freedom"); //"pwfreedom" ~~~   在上面的代码中,调用了两次expression1()函数,返回的结果互不影响。并且full参数的默认值引用了前面的name参数,这是一种有效的语法,但反之就会报错,如下所示。 ~~~ function expression2(name = full, full) { return name; } expression2(undefined, "strick"); //抛出未定义的引用错误 ~~~ **4)限制**   第一条限制是在包含默认值的参数序列中,不允许出现同名参数。无论同名的是有默认值,亦或是无默认值,都是不允许的,如下所示。 ~~~ function restrict1(name = "strick", name) { } function restrict1(name = "strick", age, age) { } ~~~   第二条限制是不能在函数体中为默认参数用let或const重新声明,如下代码所示,会抛出重复声明的语法错误。 ~~~ function restrict2(name = "strick") { let name = "freedom"; } ~~~   因为默认参数相当于是用let声明的变量,所以是不允许重复声明的。上面代码中的restrict2()函数,它的name参数的初始化类似于下面这样。 ~~~ let name = "strick"; ~~~   参数序列中只要包含了默认参数,那么其它普通参数也会用let声明。知道这一点后,就能很容易的解释上一节第二个示例,在调用expression2()函数时会抛出未定义的错误原因。函数中的两个参数的初始化相当于下面这样。 ~~~ let name = full, full; ~~~   在[第一篇](https://www.cnblogs.com/strick/p/10161595.html)中曾提到用let声明的变量,在声明之前都会被放到临时死区中,而在此时访问这些变量就会触发运行时错误。 **5)三个作用域**   根据ES6规范的9.1.2小节可知,当参数序列中包含默认参数时,将会出现三个作用域:参数作用域、函数外层作用域和函数体内作用域。关于这三个作用域需要注意两点:   (1)函数体内可以修改参数的值,但不能为其重新声明。   (2)参数作用域可以访问外层作用域中的变量,但不能访问函数体内的变量。   第一点很好理解,已在上文中做过解释。关于第二点,可先查看下面的两个函数。 ~~~ let full = "freedom"; function scope1(name = full) { return name; } scope1(); function scope2(name = en) { let en = "justify"; return name; } scope2(); ~~~   调用scope1()函数得到的返回值是“freedom”,而调用scope2()函数非但得不到结果,还会抛出en未定义的错误。接下来改造scope1()函数,把full变量改成name变量,如下所示。 ~~~ let name = "freedom"; function scope1(name = name) { return name; } ~~~   此时再次调用scope1()函数,得到的却是name未定义的错误。虽然在外层作用域中包含名为name的变量,但是参数拥有自己的作用域,会先从当前作用域中查找变量,此时的name正处在临时死区中,因此在访问它时会报错。   除了以上所列的特性之外,在之前的[第三篇](https://www.cnblogs.com/strick/p/10172871.html)的参数解构中,还介绍了解构默认值和参数默认值结合使用时的注意点。 ## 二、函数属性 **1)name**   通过函数的name属性可得到它声明时所用的名称。ES6规定此属性既不可写,也不可枚举,只允许配置。在不同场景中,它的返回值会不同,具体如下所列,每一条规则后面都给出了相应的示例。   (1)利用Function构造器创建的函数,它的名称是“anonymous”。 ~~~ var func = new Function("a", "b", "return a+b;"); func.name; //"anonymous" ~~~   (2)如果是用匿名函数表达式创建的函数,那么它的名称就是变量名;如果改用命名函数表达式创建,那么它的名称就是等号右侧的函数名称。 ~~~ var expression1 = function() { }; expression1.name; //"expression1" var expression2 = function named() { }; expression2.name; //"named" ~~~   (3)当用bind()方法绑定一个函数时,它的名称就会加“bound”前缀。 ~~~ function age() { } age.bind(this).name; //"bound age" ~~~   (4)访问器属性包含写入方法和读取方法,它们的名称会分别加“set”和“get”前缀。注意,需要调用Object.getOwnPropertyDescriptor()才能引用这两个方法。 ~~~ var obj = { get age() { }, set age(value) { } }; var descriptor = Object.getOwnPropertyDescriptor(obj, "age"); descriptor.get.name; //"get age" descriptor.set.name; //"set age" ~~~   (5)如果对象的方法是用Symbol命名的,那么这个Symbol的描述就是它的名称。 ~~~ var sym = Symbol("age"), obj = { [sym]: function() {} }; obj[sym].name; //"[age]" ~~~ **2)length**   函数的length属性可返回形参个数(即声明时的参数),但它的值会受剩余参数(已在[第二篇](https://www.cnblogs.com/strick/p/10172721.html)中做过介绍)和默认参数的影响,如下代码所示。 ~~~ (function rest(name, ...args){ }).length; //1 (function rest(name, age = 28){ }).length; //1 (function rest(name, age = 28, school){ }).length; //1 ~~~   根据上面的代码可知,形参个数的统计会忽略剩余参数,并且止于默认参数。 ## 三、块级函数   ES6允许块级函数(Block-Level Function)的声明,即在块级作用域中声明函数,而在ES5中如此操作的话,将会抛出语法错误的异常。 **1)严格模式**   在严格模式中,块级函数的声明可提升至当前代码块的顶部,在代码块之外是不可见的,如下代码所示。 ~~~ "use strict"; (function() { func("strick");   //抛出未定义的引用错误 if(true) { func("freedom");   //"freedom" function func(name) { return name; } { func("jane"); //"jane" } } func("justify"); //抛出未定义的引用错误 })(); ~~~   只有在func()函数所处的代码块或与之相邻的代码块中,才能被正确调用。 **2)普通模式**   在普通模式(即非严格模式)中,只有当块级函数所在的代码块被成功执行后,它的声明才能被提升至当前脚本文件或函数体的顶部,如下代码所示。 ~~~ (function() { func("strick"); //抛出未定义的引用错误 if(true) { func("freedom"); //"freedom" function func(name) { return name; } { func("jane"); //"jane" } } func("justify"); //"justify" })(); ~~~   在代码块之外调用了两次func()函数,由于第一次调用时,func()函数所处的代码块还未被执行(即还未声明),因此会抛出未定义的引用错误。 ## 四、元属性   元属性(Meta Property)就是非对象的属性,能够以属性访问的形式读取特殊的元信息。new.target是由ES6引入的一个元属性,可检测一个函数是否与new运算符组合使用,并且只能存在于函数体内。   在JavaScript中,new是一个关键字,而不是一个对象。但当函数作为构造函数被调用时,new.target能够指向新创建的目标对象;而当函数作为普通函数被调用时,new.target的值为undefined,如下所示。 ~~~ function func1() { typeof new.target; //"function" } new func1(); function func2() { new.target === undefined; //true } func2(); ~~~   把func1()作为构造函数使用,在其函数体中利用typeof运算符检测出new.target是一个函数对象;而在func2()函数中,让new.target和undefined进行了全等比较,得到的结果为true。 ***** > 原文出处: [博客园-ES6躬行记](https://www.cnblogs.com/strick/category/1372951.html) [知乎专栏-ES6躬行记](https://zhuanlan.zhihu.com/pwes6) 已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎浏览。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200)
';