函数是所有编程语言的最重要的组成部分,ES6的出现大力度的更新了函数的特性,在ES5的基础上进行了许多改进,让JavaScript编程更少出错,更加灵活。
-
ECMAScript5默认参数
function makeRequest(url, timeout, callback) {
timeout = timeout || 6000;
callback = callback || function () {
}
// 其他函数执行功能
}
在本示例中,timeout和callback为可选参数, 如果不传入参数系统会赋一个默认值。对于函数的命名参数,如果不显式传值,则会默认为undefined。但是如果我们
给第二个形参timeout传入值 0 ,也会被视为一个假值,并最终将timeout赋值为6000。
这种情况下,需要通过typeof检查参数类型:
function makeRequest(url, timeout, callback) {
timeout = (typeof timeout != 'undefined') ? timeout : 6000;
callback = (typeof callback != 'undefined') ? callback : function () {
};
// 其他函数执行功能
}
-
ECMAScript6中的默认参数值
ES6简化了为形参提供默认值的过程,如果没有传入值则为其提供一个默认值:
function makeRequest(url, timeout = 6000, callback = function () {}) {
// 其他函数执行功能
}
上述示例中只有第一个参数是需要为其传值的,其他两个参数都有默认值。
如果调用makeRequest()方法传入3个参数,则不使用默认值
// 使用timeout 和 callback的默认值
makeRequest("/ES6");
// 使用callback的默认值
makeRequest("/ES6", 666);
// 不使用默认值
makeRequest("/ES6", 666,function (ar) {
doSomething(ar);
})
按照ES6的语法,上述三次调用都传入了“/ES6”, 其余两个有默认参数的都是可选参数。 声明函数时,可以为任意参数指定默认值,在已指定默认值的参数后可以继续声明无默认值参数
function makeReques(url, timeout = 6000, callback) {
}
只有当不为第二个参数传入值或者主动为第二个参数传入undefined时才会使用timeout的默认值:
// 使用timeout的默认值
makeRequest("/ES6", undefined, function (ar) {
})
// 使用timeout的默认值
makeRequest("/ES6");
// 不使用timeout的默认值
makeRequest("/ES6", null, function (ar) {
})
第三次调用函数时,不使用timeout的默认值,其值最终为null
-
默认参数值对arguments对象的影响
function mixArgs(first, second) {
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d";
console.log(first === arguments[0]);
console.log(second === arguments[1]);
}
mixArgs("a", "b");
输出如下:
true
true
true
true
在非严格模式下,命名参数的变化会同步更新到arguments对象中,所以当first和second被赋予新值时,arguments[0]和arguments[1]相应地也就更新了,最终所有===全等比较的结果为true。
然而,在ECMAScript 5的严格模式下,取消了arguments对象的这个令人感到困惑的行为,无论参数如何变化,arguments对象不再随之改变。依然是mixArgs()函数,我们将其设置为严格模式:
function mixArgs(first, second) {
"use strict";
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d";
console.log(first === arguments[0]);
console.log(second === arguments[1]);
}
mixArgs("a", "b")
再次调用mixArgs()方法,输出以下内容
true
true
false
false
此次调用改变first和second的值不会导致arguments改变, 在ECMAScript 6中,如果一个函数使用了默认参数值,则无论是否显式定义了严格模式,arguments对象的行为都将与ECMAScript 5严格模式下保持一致。
默认参数值的存在使得arguments对象保持与命名参数分离,这个微妙的细节将影响你使用arguments对象的方式,
// 非严格模式
function mixArgs(first, second="b") {
console.log(arguments.length);
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d";
console.log(first === arguments[0]);
console.log(second === arguments[1]);
}
mixArgs("a");
这段代码会输出以下内容
1
true
false
false
false
-
默认参数表达式
可用通过函数执行来得到默认参数的值
function getValue() {
return 666;
}
function add(first, second = getValue()) {
return first + second;
}
console.log(add(1,1)); // 结果为 2
console.log(add(1)); // 结果为6
值得注意的是,首次解析函数声明时不会调用getValue()方法,只有当调用add()函数且不传入第二个参数时才会调用。我们稍微改动一下getValue()函数, 让它每次返回不同的值
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
变量value的初始值为5,每次调用getValue()时加1。第一次调用add(1)返回6,第二次调用add(1)返回7,因为变量value已经被加了1。
因为只要调用add()函数就有可能求second的默认值,所以任何时候都可以改变那个值。
注意,当使用函数调用结果作为默认参数值时,如果忘记写小括号,例如,second= getValue, 则最终传入的是对函数的引用,而不是函数调用的结果。
正因为默认参数是在函数调用时求值,所以可以使用先定义的参数作为后定义参数的默认值:
function add(first, second = first) {
return first + second;
}
console.log(add(1,1)) // 结果为2
console.log(add(1)) // 结果为2
参数second的默认值为参数first的值,如果只传入一个参数,则两个参数的值相同,从而add(1, 1)返回2, 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(first),所以尽管add(1, 1)仍然返回2,但是add(1)返回的是(1+6)也就是7。
在引用参数默认值的时候,只允许引用前面参数的值,即先定义的参数不能访问后定义的参数
function add(first = second, second) {
return first + second;
}
console.log(add(1,1)) // 结果为2
console.log(add(undefined, 1)) // 错误
调用add(undefined, 1)会抛出错误,因为second比first晚定义,因此其不能作为first的默认值。这就涉及到临时死区(TDZ) 的原理。
-
默认参数的临时死区
与let声明类似,定义参数时会为每个参数创建一个新的标识符绑定,该绑定在初始化之前不可被引用,如果试图访问会导致程序抛出错误。 当调用函数时,会通过传入的值或参数的默认值初始化该参数。
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
调用add(1, 1)和调用add(1)实际上相当于执行以下代码来创建first和second参数值:
// 表示调用add(1,1)的javaScript代码
let first = 1;
let second = 1;
// 表示调用add(1)的JavaScript代码
let first = 1;
let second = getValue(first);
初次执行函数add()时,绑定first和second被添加到一个专属于函数参数的临时死区(与let的行为类似)。
由于初始化second时first已经被初始化,所以它可以访问first的值,但是反过来就错了。
// 表示调用add(1,1)的javaScript代码
let first = 1;
let second = 1;
// 表示调用add(undefined)的JavaScript代码
let first = second;
let second = 1;
调用add(undefined, 1)函数,因为当first初始化时second尚未初始化,所以会导致程序抛出错误,此时second尚处于临时死区中,
所有引用临时死区中绑定的行为都会报错。初始化后变量会移出临时死区,反过来说引用了还在临时死区 中的变量都是未初始化的,会抛出referenceerror错误。
函数参数有自己的作用域和临时死区,其与函数体的作用域是各自独立的,也就是说参数的默认值不可访问函数体内声明的变量。
还可关注作者 【菜鸟1024】 更多文章请识别以下二维码并关注交流
参考资料
《深入理解ES6》