es6新增了箭头函数,函数默认参数等功能,方便了代码的间接性,可读性
-
函数的参数默认值
es6以前要想实现函数参数的默认值,是需要做一些变通的
function foo (x, y) {
x = x || 'hello';
y = y || 'es6';
console.log(x, y)
}
foo();
// hello es6
foo('hello');
// hello es6
foo('hello', 'fn');
// hello fn
foo('hello', '');
// hello es6
foo(undefined, undefined);
// hello es6
foo(null, null);
// hello es6
上面代码有一个弊端,就是y明明赋了值,但是并没有替换掉默认值,虽然传入的是(‘’ , undefined, null)
可以判断一下
typeof y === 'undefined' ? y = 'es6' : y = y;
这样就避免这些(’ ', undefined , null)返回默认值了
想想一下如果多个变量都需要这样写默认值,是很繁琐的,此时es6引入了新特性,参数默认值
es6允许函数参数设置默认值,即写在形参后面用等号赋值。
function bar (x, y = 'es6') {
console.log(x, y);
}
bar('hello');
// hello es6
bar('hello', '');
// hello
bar('hello', 'ECMA2015');
// hello ECMA2015
bar('hello', undefined);
// hello es6
bar('hello', null);
// hello null
参数默认值这种写法,运用了等号右边必须为(===)undefined默认值才会生效。
上面有一个null,如我们自己封装的方法不一样
因为null === undefined 返回false。所以null会被返回。
可以看到这样写非常简洁
function Test (x = 0, y = 0) {
this.x = x
this.y = y
}
let test = new Test();
test
// {
x: 0,
y: 0
}
构造函数也可以使用默认值。
并且这种写法,当外部删除实参,内部函数也不会因为实参的丢失,而报错。大大减少了维护的成本。
使用参数默认值,就不能使用let const声明
function foo (x = 0) {
let x = 1;
const x = 2;
}
以上都会报错,因为参数默认已经声明了,不可以使用let, const再次声明。
使用参数默认值时,函数不能有同名属性
// 正常执行
function foo (x, x, y) {
}
// 报错
function foo (x, x, y = 1) {
}
// SyntaxError: Duplicate parameter name not allowed in this context
// 改函数的上下文中不允许重复的参数名称
另外,一个容易忽略的地方是,参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。
p = 99
function test (p = x + 1) {
return p;
}
test();
// 100
p = 100;
test();
// 101
上面代码中,每次调用test,x + 1都重新执行,而不是默认p = 100。
解构赋值和参数默认值结合使用的好处
function test ({x, y = 5}) {
console.log(x, y)
}
test({});
// undefined 5
test({x: 1,y: 2});
// {x: 1, y: 2}
test({x: 1});
// {x: 1, y: 5}
test();
// error
只有函数执行对象才能正常,执行因为你没有设置默认值,必须进行传值,
上面代码前三个都不是问题,最后一个为什么报错了。
因为你传入的是空,而参数({}x, y = 5})默认没有值。所以x会报错
通过给参数设置默认值,就可以解决了
function bar ({x, y = 1} = {}) {
console.log(x, y)
}
bar();
此时你就算不穿,他的默认值也是一个对象,x只能返回undefined ,不会导致程序报错
下面有两种写法
function foo ({x = 0, x = 0} = {}) {
return [x, y]
}
function bar ({x, y} = {x:0, y: 0}) {
return [x, y]
}
foo(); // [0, 0]
bar(); // [0, 0]
foo({}); // [0, 0]
bar({}); // [undefined, undefined]
foo({x:1}) // [1, 0]
bar({x:1}) // [1, undefined]
foo({x: 5, y: 2}) // [5, 2]
bar({x: 5, y: 2}) // [5, 2]
foo({z: 1}) // [0, 0]
bar({z: 1}) // [undefined, undefined]
参数默认值的位置
一般来说形参的末尾的值,是设置默认值最好的位置。这样方便别人阅读代码。
如果默认值的形参非末尾,那么不可以省略。否则报错
function test (x, y = 1, z) {
return [x, y, z]
}
test(1, 2, 3) // [1, 2, 3]
test(undefined, 2, undefined) // [undefined, 2, undefined]
test(1, undefined, 3) // [1, undefined, 3]
test(2, ,5) // error
上面代码演示了,非末尾参数,不可以省略,但可以使用undefined占位,这样默认值即生效。
因默认值采用了(===)undefined,所以传入undefined会使用默认值,但是null不可以
function test (x = 1, y = 2) {
return [x, y]
}
test(undefined, null)
// [1, null]
函数的length属性
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b. c = 520)).length // 2
如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
(function (a = 1, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
let x = 1;
function test (x, y = x) {
console.log(x);
}
test(2);
// 2
上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。
再看一个例子
let x = 1;
function test (y = x) {
let x = 2;
console.log(y);
}
test()
// 1
当函数调用时,参数形成单独的作用域,但是这个作用域中,x没有定义,所以获取外部的变量x,函数调用时,函数体内部的x变量不会影响到默认值变量x
如果此时x不存在就会报错
function test (y = x) {
let x = 1;
console.log(y);
}
test();
test函数执行时,形成单独作用域的变量中没有x,此时去找外部的,但是全局中也没有变量x, 此时的x就是undefined,但是函数体内部的变量x又影响不到默认值变量x,当打印y的时候就会报错 x is not defined
还有一种写法
let x = 1;
function test (x = x) {
}
test();
此时也会报错,因为单独作用域中,x = x 就等于let x = x; 此时属于暂时死亡区, 导致报错,因为x还没声明,你就赋值了。
参数的默认值是函数,也会遵循这种原则
let foo = 'bar';
function test (show = () => foo) {
let foo = 'foo';
show();
}
test();
// 'bar'
test执行时,参数即使是函数,也要遵循原则行程单独作用域,它返回的就是外部‘bar’, 内部foo影响不了默认值。
因此foo指向外部foo,输出’bar’
如果写成下面这样,会报错
function bar (func = () => foo) {
let foo = 'sex';
func();
}
bar();
上面的func指向的函数里没有foo这个变量,foo这个变量依赖外部,外部没有,所以报错。
下面是一个复杂的例子
var x = 1;
function foo (x, y = () => { x = 2; }) {
var x = 3;
y();
console.log(x);
}
foo()
// 3
x
// 1
首先foo执行时,参数形成单独作用域,x使用外部x变量,y使用同作用域第一个x值,y内部更改了同作用域里x的值。
紧接着函数内部声明x并且打印,但是影响不到外部,也影响不到内部参数单独作用域
打印的x是内部声明x
x依旧没有改变,因为y改变的只是同作用域的x,而内部声明的x = 3,根本不在一个作用域。
如果将var去掉,它将指向第一个参数的x,输出就是2,但是依旧外部的x不会被改变。
上述不可以使用let ,否则会声明在后,使用在前
应用
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出错误。
function throwIfMissing () {
throw new Error('不可能省略的参数')
}
function test (foo = throwIfMissing()) {
console.log('传入了参数')
return foo;
}
test();
上面代码,如果传入参数就不会报错,如果没有传入就会报错
注意throwIfMissing方法是运行时执行,这表明是在运行时执行,而不是定义时执行,如果参数已经赋值,那么默认值的函数就不会被执行。
另外还可以设置undefined,让这个参数可以不传参
function test (option = undefined) {...}