函数形参的默认值
JavaScript
函数语法规定,无论在函数定义中声明了多少形参,调用时可以传入任意数量的参数。可以在定义时,当已定义的形参无对应的传入参数时为其指定一个默认值。
在ES5中模拟默认参数
ES5和早期版本中,可能通过如下方法模拟默认参数
示例中,timeout
和callback
为可选参数,如果没有传入相应的参数,会有一个默认值。对于函数命名参数,如果不显示传值,默认为undefined
。在模拟默认参数时,最好不要这样写timeout = timeout || 2000
,因为,如果想给timeout
传入0,即使这个值合法,也会被视为false
,最终timeout
赋值为2000。
ES6中的默认参数
|
|
ES6简化了为形参提供默认参数的过程,声明函数时,可以为任意参数指定默认值,在已指定默认值的参数后面,可以继续声明无默认值参数。只有不为参数传入值或者 传入值为undefined
时,对应参数才会使用默认值。
默认参数值对arguments对象的影响
在ES5非严格模式中,命名参数的变化会同步更新到arguments
对象,严格模式中arguments
对象不随命名参数变化而变化。
当定义为严格模式时,输出为true
,true
,false
,false
ES6中,函数使用了默认参数,无论是否显式定义了严格模式,arguments
对象行为都与ES5中的严格模式保持一致。
以上输出为true
,false
,false
,false
,first
和second
并不会影响arguments
对象,其中arguments[1]===undefined
默认参数表达式
函数默认参数值非原始值传参时,默认参数是在函数调用时求值,也就是说在函数声明时,参数默认值是不确定的。
示例中,调用add
不给second
传值,就会调用getValue()
对second
求默认值,所以任何时候都可以改变默认值。
因为默认参数是函数调用时求值,所以可以使用先定义的参数做为后定义参数的默认值。
先定义的参数访问了后定义的参数,会抛出错误。
这是由于函数参数有自己的临时死区,定义参数时会为每个参数创建一个新的标识符绑定,该绑定在初始化之前不可被引用。
上面示例中调用add(1, 1)
和add(undefined, 1)
时,相当于
当first
初始化时second
尚未初始化,此时second
处于临时死区中,所以会导致程序抛出错误。
处理无命名参数
早先,利用arguments
来检查函数的所有参数,而不必定义每个要用的参数。ES6中,通过引入不定参数的特性来解决这些问题。
不定参数
在函数的命名参数前添加三个点(...
)就表明这是一个不定参数,该参数是一个数组,包含着自它之后传入的所有参数。
不定参数keys
包含的是object
之后传入的所有参数。
函数的
length
属性统计的是函数命名参数的数量,不包括不定参数。pick.length=1
注意
每个函数最多只能声明一个不定参数,而且要放在所有参数的末尾。
123456789//语法错误:不定参数后不能有其他命名参数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]]}return result}不定参数不能用户对象字面量setter之中,因为对象字面量
setter
的参数有且只能有一个。- 无论是否使用不定参数,
arguments
对象总是包含所有传入函数的参数。
增强的Function构造函数
Function
构造函数通常用来动态创建新的函数,该构造函数接受字符串形式的参数,分别为函数的参数及函数体。
ES6增强了Function
构造函数的功能,支持在创建函数时定义默认参数和不定参数。
展开运算符
展开运算符可以将一个数组打散后作为各自独立的参数传入函数。例如,要从一个数组的中找出最大值,我们可以利用Math.max()
方法,但Math.max()
方法不允许传入数组。在ES5及早期版本,可能实现如下
在ES6中,在数组前添加...
符号,就会将参数数组分割为各自独立的参数依次传入
此外,展开运算符还可以与其他正常传入的参数混合使用
name属性
ES6给所有函数新增了name
属性,用来辨别函数。
函数声明函数name
属性对应着声明时的函数名称,匿名函数表达式函数name
属性对应着被赋值变量的名称。
函数表达式有一个名字,该名字比函数本身被赋值的变量权重高,所以doSomething.name
的值为doSomethingElse
。firstName
是一个getter
函数,他的名称会有get
前缀,setter
函数名称也有前缀set
。
通过bind()
函数创建的函数,其名称带有’bound’前缀,使用Function
构造函数创建的函数,其名称为’anonymous’。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/name
明确函数的多重用途
ES5及早起版本,函数具有多重功能,可以结合new
作为构造函数使用,返回一个对象。
函数有两个不同的内部方法:[[Call]]
和[[Construct]]
。通过new
关键字调用函数时,执行的是[[Construct]]
函数,它负责创建实例,然后执行函数体,将this
绑定到实例上。如果不是通过new
关键字调用,则执行[[Call]]
函数,从而执行代码中的函数体。
具有[[Construct]]
方法的函数统称为构造函数,不是所有函数都有[[Construct]]
方法,比如箭头函数。
ES5中判断函数是否作为构造函数调用
在ES5中,判断函数是否通过new
关键字被调用,流行的方式如下
这个方法并不完全可靠,因为可以通过call()
或者apply()
方法将this
绑定到Person
的实例上。
元属性new.target
元属性是指非对象属性,其可以提供非对象目标的补充信息(例如new
)。当函数调用[[Construct]]
方法时,new.target
被赋值为new
操作符的目标,通常是新创建实例的构造函数。当函数调用[[Call]]
方法,则new.target
的值为undefined
也可以检查是否被某个特定构造函数所调用
new AnotherPerson('Nicholas')
调用时,真正的调用Person.call(this, name)
没有使用关键词,因此new.target
的值为undefined
抛出错误。
块级函数
ES5中,严格模式下,当在代码块内部声明函数时,程序抛出错误。ES6中,严格模式,在代码块中的声明的函数,会被提升块级作用域顶部,函数表达式不会被提升,一旦代码块结束执行,函数销毁。
当执行到typeof doAnotherThing
时,由于尚未执行let
语句声明,doAnotherThing
还在当前块作用域的临时死区,程序被迫终端执行。
ES6规定,函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
非严格模式下,可以在外围函数或全局作用域访问和调用(定义后?)。
箭头函数
箭头函数是一种使用箭头(=>
)定义函数的新语法,它与以前的函数有些许不同。
- 没有
this
、super
、argument
和new.target
绑定,这些值都由外围最近一层非箭头函数决定。 - 不能通过
new
关键字调用,因为箭头函数没有[[Construct]]
方法。 - 没有原型,由于不能通过
new
关键字调用,因而没有构建原型的需求,所以箭头函数不存在prototype
这个属性 - 不支持重复命名参数,无论在严格模式还是非严格模式。传统函数只有在严格模式下才不支持。
箭头函数语法
当箭头函数只有一个参数时,可以直接写参数名,箭头紧随其后,箭头右侧的表达式被求值后便立即返回。当箭头函数有多个参数时,要给参数加上括号。
当函数没有参数时,也要在声明的时候写一对空括号。当函数体有多个表达式时,需要用花括号包裹函数体,如果需要返回值,就得显示定义一个返回值。
当函数体只有一个表达式,并且返回一个对象字面里时,需要将其包裹在小括号内,以区分函数体。
尾调优化
尾调用指的是函数做为另一个函数的最后一条语句调用
在ES5的引擎中,尾调用的实现与其他函数调用的实现类似:创建一个新的栈帧,将其推入调用栈来表示函数调用。也就是说在循环调用中,每一个未用完的栈帧都会保存在内存中,当调用栈变得过大时会造成程序问题。
ES6中的尾调优化
ES6,在严格模式下,如果满足以下条件,JavaScript引擎自动优化(主要看引擎支持),尾调用不再创建新的栈帧,而是清除并重用当前栈帧。
- 尾调用不妨问当前栈帧的变量(也就是说函数不是一个闭包)
- 在函数内部,尾调用时最后一条语句
- 尾调用结果作为函数值返回12345function doSomething() {//优化后return doSomethingElse()}
这个函数中,尾调用doSomethingElse
的结果立即返回,不调用任何局部作用域变量。
这个函数,不返回最终结果,无法被优化
在尾调用返回后执行其他操作,无法被优化
把函数调用的结果存储在一个变量里,最后再返回这个变量,由于没有立即返回doSomethingElse
函数的值,无法被优化
func
函数访问局部变量num
,无法被优化
利用尾调用优化
|
|
上面是一个阶乘函数,由于在递归调用前执行了乘法操作,所以阶乘函数无法被优化
这个重写后的factorial
函数,参数p
用来保存乘法结果,下一次迭代可以取出它用于计算。在线ES6引擎就可以优化递归调用了。
ES6的尾调优化,最终还是取决于JavaScript引擎是否支持。