ES6之函数的扩展
date
Jun 13, 2018
slug
kdhfrurw
status
Published
tags
JavaScript
summary
type
Post
[toc]
函数参数的默认值
基本用法
请问下面两种写法的差别:写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。
// 写法一function m1({x = 0, y = 0} = {}) { return [x, y];}// 写法二function m2({x, y} = { x: 0, y: 0 }) { return [x, y];}// 函数没有参数的情况m1() // [0, 0]m2() // [0, 0]// x 和 y 都有值的情况m1({x: 3, y: 8}) // [3, 8]m2({x: 3, y: 8}) // [3, 8]// x 有值,y 无值的情况m1({x: 3}) // [3, 0]m2({x: 3}) // [3, undefined]// x 和 y 都无值的情况m1({}) // [0, 0];m2({}) // [undefined, undefined]m1({z: 3}) // [0, 0]m2({z: 3}) // [undefined, undefined]
函数的 length 属性
length 属性的含义是,该函数预期传入的参数个数。指定了默认值以后,length 属性将返回没有指定默认值的参数个数。所以 rest 参数也不会计入 length 属性,设置了默认值的参数不是尾参数,那么 length 属性也不再计入后面的参数了。
(function (a) {}).length // 1(function (a = 5) {}).length // 0(function (a, b, c = 5) {}).length // 2(function(...args) {}).length // 0(function (a = 0, b, c) {}).length // 0(function (a, b = 1, c) {}).length // 1
作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。 如果参数的默认值是一个函数,该函数的作用域也遵守这个规则。
!!!重点!跳转到原内容查看!!!
rest 参数
arguments 对象不是数组,而是一个类似数组的对象。rest 是一个真正的数组,数组特有的方法都可以使用。函数的 length 属性,不包括 rest 参数。
严格模式
函数内部从 ES5 开始可以显式设定为严格模式。ES2016 规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
这样规定的原因是,函数内部的严格模式,同时适用于函数体和函数参数。但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行。
两种方法可以规避这种限制。第一种是设定全局性的严格模式,这是合法的。第二种是把函数包在一个无参数的立即执行函数里面。
name 属性
返回该函数的函数名。将一个匿名函数赋值给一个变量,ES5 会返回空字符串,ES6 会返回实际的函数名。将一个具名函数赋值给一个变量,ES5 和 ES6 都返回这个具名函数原本的名字。 Function 构造函数返回的函数实例,name 属性的值为 anonymous。bind 返回的函数,name 属性值会加上 bound 前缀。取值函数(getter)和存值函数(setter)返回值是方法名前加上 get 和 set,如
"get foo" "set foo"
。箭头函数
箭头函数有几个使用注意点。
- 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
- 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this 对象的指向是可变的,但是在箭头函数中,它是固定的。 长期以来,JavaScript 语言的 this 对象一直是一个令人头痛的问题,在对象方法中使用 this,必须非常小心。箭头函数”绑定”this,很大程度上解决了这个困扰。
不适用场合
由于箭头函数使得 this 从“动态”变成“静态”,下面两个场合不应该使用箭头函数: 第一个场合是定义对象的方法,且该方法内部包括 this。调用 cat.jumps()时,如果是普通函数,该方法内部的 this 指向 cat;如果写成上面那样的箭头函数,使得 this 指向全局对象,因此不会得到预期结果。对象不构成单独的作用域 ,导致 jumps 箭头函数定义时的作用域就是全局作用域。
const cat = { lives: 9, jumps: () => { this.lives--; }}
第二个场合是需要动态 this 的时候,也不应使用箭头函数。
尾调用优化
指某个函数的最后一步是调用另一个函数,与其他调用不同,就在于它的特殊的调用位置。
我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数 A 的内部调用函数 B,那么在 A 的调用帧上方,还会形成一个 B 的调用帧。等到 B 运行结束,将结果返回到 A,B 的调用帧才会消失。如果函数 B 内部还调用函数 C,那就还有一个 C 的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。这就叫做“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。
以下三种情况,都不属于尾调用。
// 情况一function f(x){ let y = g(x); return y;}// 情况二function f(x){ return g(x) + 1;}// 情况三function f(x){ g(x);}function f(x){ g(x); return undefined;}// 只要是最后一步操作是尾调,就是尾调用。function f(x) { if (x > 0) { return m(x) } return n(x);}
注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。内层函数 inner 用到了外层函数 addOne 的内部变量 one。
function addOne(a){ var one = 1; function inner(b){ return b + one; } return inner(a);}
目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持。ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
尾调用优化对递归函数有重大优化作用。
其他
函数参数的尾逗号
ES2017 允许函数的最后一个参数有尾逗号(trailing comma),使得函数参数与数组和对象的尾逗号规则保持一致了。
function clownsEverywhere( param1, param2,) { /* ... */ }clownsEverywhere( 'foo', 'bar',);
Function.prototype.toString()
ES2019 要求 toString()方法返回一模一样的原始代码,以前会省略注释和空格。
catch 命令的参数省略
很多时候,catch 代码块可能用不到这个参数,ES2019 做出了改变,允许 catch 语句省略参数。
try { // ...} catch { // ...}