ES6函数-基础篇1

1、函数默认值

在函数声明中能指定任意一个参数的默认值,即使该参数排在未指定默认值的参数之前也是可以的。例如,下面这样是可行的:

在本例中,只有在未传递第二个参数、或明确将第二个参数值指定为 undefined 时,timeout 的默认值才会被使用,例如

在关于参数默认值的这个例子中, null 值被认为是有效的,意味着对于 makeRequest() 的第三次调用并不会使用 timeout 的默认值。

2、参数默认值如何影响 arguments 对象

使用 ES6 参数默认值的函数中, arguments 对象的表现总是会与 ES5 的严格模式一致,无论此时函数是否明确运行在严格模式下。参数默认值的存在触发了 arguments 对象与具名参数的分离。

如上图所示,此时的arguments的length的值为1,如果2个参数值都显示传递,则arguments的length的值为2。因此,arguments.length的值和实际传给函数的参数个数是一致的。

3、参数默认值表达式

参数默认值最有意思的特性或许就是默认值并不要求一定是基本类型的值。例如,你可以执行一个函数来产生参数的默认值,就像这样:

此处若未提供第二个参数, getValue() 函数就会被调用以获取正确的默认值。需要注意的是,仅在调用 add() 函数而未提供第二个参数时, getValue() 函数才会被调用,而在getValue() 的函数声明初次被解析时并不会进行调用。这意味着 getValue() 函数若被写为可变的,则它有可能会返回可变的值,例如:

由于 second参数的默认值总是在 add() 函数被调用的情况下才被计算,因此就能随时更改该参数的值。

这种行为引出了另一种有趣的能力:可以将前面的参数作为后面参数的默认值,这里有个例子:

进一步说,你可以将 first 作为参数传递给一个函数来产生 second 参数的值,正如下例:

引用其他参数来为参数进行默认赋值时,仅允许引用前方的参数,因此前面的参数不能访问后面的参数,例如:

调用 add(undefined, 1) 发生了错误,是因为 second 在 first 之后定义,因此不能将其作为后者的默认值。要理解为何会发生这种情况,需要着重回顾“暂时性死区”。

4、参数默认值的暂时性死区

上例中调用 add(1, 1) 与 add(undefined, 1) 对应着以下的后台代码:

本例中调用 add(undefined, 1) 抛出了错误,是因为在 first 被初始化时 second 尚未被初始化。此处的 second 存在于暂时性死区内,对于 second 的引用就抛出了错误。

函数参数拥有各自的作用域和暂时性死区,与函数体的作用域相分离,这意味着参数的默认值不允许访问在函数体

内部声明的任意变量。

5、剩余参数

剩余参数( rest parameter )由三个点( ... )与一个紧跟着的具名参数指定,它会是包含传递给函数的其余参数的一个数组,名称中的“剩余”也由此而来。例如:

keys 是一个包含所有在 object 之后的参数的剩余参数(这与包含所有参数的 arguments 不同,后者会连第一个参数都包含在内)。这意味着你可以对 keys 从头到尾进行迭代,而不需要有所顾虑。

函数的 length 属性用于指示具名参数的数量,而剩余参数对其毫无影响。此例中pick() 函数的 length 属性值是 1 ,因为只有 object 参数被用于计算该值。

剩余参数受到两点限制。一是函数只能有一个剩余参数,并且它必须被放在最后。

第二个限制是剩余参数不能在对象字面量的 setter 属性中使用,这意味着如下代码同样会导
致语法错误:

存在此限制的原因是:对象字面量的 setter 被限定只能使用单个参数;而剩余参数按照定义是不限制参数数量的,因此它在此处不被许可。

arguments 对象在函数被调用时反映了传入的参数,与剩余参数能协同工作,它真实反映了传递给函数的实际参数个数。

6、扩展运算符

与剩余参数关联最密切的就是扩展运算符。剩余参数允许你把多个独立的参数合并到一个数组中;而扩展运算符则允许将一个数组分割,并将各个项作为分离的参数传给函数。

Math.max() 方法并不允许你传入一个数组,因此在 ES5 或更早版本中,你必须自行搜索整个数组,或像下面这样使用apply() 方法:

ES6 的扩展运算符令这种情况变得简单。无须调用 apply() ,你可以像使用剩余参数那样在该数组前添加 ... ,并直接将其传递给 Math.max() 。 JS 引擎将会将该数组分割为独立参数并把它们传递进去,就像这样:

现在调用 Math.max() 看起来更传统一些,并避免了为一个简单数学操作使用复杂的 this绑定(即在上个例子中提供给 Math.max.apply() 的第一个参数)。

你可以将扩展运算符与其他参数混用。假设你想让 Math.max() 返回的最小值为 0 (以防数组中混入了负值),你可以将参数 0 单独传入,并继续为其他参数使用扩展运算符,正如下例:

本例中传给 Math.max() 的最后一个参数是 0 ,它跟在使用扩展运算符的其他参数之后传入。

7、ES6 的名称属性

定义函数有各种各样的方式,在 JS 中识别函数就变得很有挑战性。此外,匿名函数表达式的流行使得调试有点困难,经常导致堆栈跟踪难以被阅读与解释。正因为此, ES6 给所有函数添加了 name 属性。

8、明确函数的双重用途

JS 为函数提供了两个不同的内部方法: [[Call]] 与 [[Construct]] 。当函数未使用 new进行调用时, [[call]] 方法会被执行,运行的是代码中显示的函数体。而当函数使用 new进行调用时, [[Construct]] 方法则会被执行,负责创建一个被称为新目标的新的对象,并且使用该新目标作为 this 去执行函数体。拥有 [[Construct]] 方法的函数被称为构造器。

记住并不是所有函数都拥有 [[Construct]] 方法,因此不是所有函数都可以用 new 来调用。在“箭头函数”小节中介绍的箭头函数就未拥有该方法。

 9、new.target 元属性

ES6 引入了 new.target 元属性。元属性指的是“非对象”(例如 new )上的一个属性,并提供关联到它的目标的附加信息。当函数的 [[Construct]] 方法被调用时, new.target 会被填入 new 运算符的作用目标,该目标通常是新创建的对象实例的构造器,并且会成为函数体内部的 this 值。而若 [[Call]] 被执行, new.target 的值则会是undefined 。

通过检查 new.target 是否被定义,这个新的元属性就让你能安全地判断函数是否被使用new 进行了调用。

也可以检查 new.target 是否被使用特定构造器进行了调用,例如以下代码:

 

 ES6 通过新增 new.target 而消除了函数调用方面的不确定性。在该主题上,ES6 还随之解决了本语言之前另一个不确定的部分——在代码块内部声明函数。

10、块级函数

ES5 的严格模式为代码块内部的函数声明引入了一个错误,就像这样:

在 ES5 中,这段代码会抛出语法错误。然而 ES6 会将 doSomething() 函数视为块级声明,并允许它在定义所在的代码块内部被访问。例如:

块级函数会被提升到定义所在的代码块的顶部,因此 typeof doSomething 会返回"function" ,即便该检查位于此函数定义位置之前。一旦 if 代码块执行完毕,doSomething() 也就不复存在。函数定义会被提前且能正常执行。

块级函数与 let 函数表达式相似,在执行流跳出定义所在的代码块之后,函数定义就会被移除。关键区别在于:块级函数会被提升到所在代码块的顶部;而使用 let 的函数表达式则不会,正如以下范例所示:

此处代码在 typeof doSomething 被执行时中断了,因为 let 声明尚未被执行,将doSomething() 放入了暂时性死区。

11、非严格模式的块级函数

ES6 在非严格模式下同样允许使用块级函数,但行为有细微不同。块级函数的作用域会被提升到所在函数或全局环境的顶部,而不是代码块的顶部。

本例中的 doSomething() 会被提升到全局作用域,因此在 if 代码块外部它仍然存在。 ES6标准化了这种行为来移除浏览器之前存在的不兼容性,于是在所有 ES6 运行环境中其行为都会遵循相同的方式。 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值