函数扩展
这篇笔记主要介绍ES6中,对函数的一些改进语法:
- 默认参数
- 可变参数
- Lambda表达式和其需要注意的问题
默认参数
ES6支持为函数参数指定默认值。
function foo(i = 0) {
console.log(i);
}
foo();//输出0
foo(1);//输出1
注意:JavaScript的默认参数每次都会重新初始化,这个和Python不同,Python中这一点确实比较坑,JavaScript表现的比较正常。下面是一个例子:
function add_end_to_list(l = []) {
l.push("END");
return l;
}
console.log(add_end_to_list());
console.log(add_end_to_list());
console.log(add_end_to_list());
输出:
[ 'END' ]
[ 'END' ]
[ 'END' ]
实际上,我们不要搞这么复杂的默认参数,Python建议函数的默认参数是“不可变对象”,JavaScript里也同样适用,最好使用基本类型或是不可变的类型作为默认参数。
使用解构赋值参数结合参数默认值
在其它章节中介绍过解构赋值,这是一个十分好用的语法,解构赋值用于函数传参时,也可以指定默认值,下面是一个简单的例子。
obj = {
'aaa':'aaa'
};
function foo({aaa, bbb = 'bbb', ccc = 'ccc'}) {
console.log(aaa);
console.log(bbb);
console.log(ccc);
}
foo(obj);
默认值要设置在函数尾部
函数默认参数应该设置在参数列表尾部,JavaScript虽然没有这样的语法规定,但是默认参数如果不放在尾部,就必须传入undefined触发参数的默认值,我相信是不会有人这么设计函数的。
Python中则规定,默认参数必须放在参数列表尾部,否则是语法错误:
一个错误的例子(Python):
def myFunc(a=1,b):
print(a)
myFunc(1,2)
可变参数
我们可以用...arg的形式指定函数的可变参数,然后在函数体内以数组的形式读取参数。
function foo(arg1, ...arg2) {
console.log('arg1:' + arg1);
//这行代码仅仅是用来输出arg2的类型,typeof arg2会返回object,无法区分数组和其他对象
console.log(Object.prototype.toString.apply(arg2));
console.log('arg2:' + arg2);
}
foo(1, 2, 3, 4, 5);
输出:
arg1:1
[object Array]
arg2:2,3,4,5
Lambda表达式
写JavaScript的都管Lambda表达式叫箭头函数,因为JavaScript语法里使用=>代表Lambda表达式。实际上各个语言的写法几乎全都不一样,这里我们还是叫Lambda表达式比较通俗易懂。
Lambda表达式可以用于简化回调函数的写法,JavaScript里有很多的回调函数,因此使用Lambda表达式能够极大的简化代码,增强可读性。
let result = [1, 2, 3, 4, 5].map(
(arg) => arg * arg
);
console.log(result);
上面代码中,(arg) => arg * arg是一个参数为arg,返回值为arg*arg的函数。如果Lambda表达式函数体有多行代码,可以使用大括号包裹,但要注意return要指明写出,下面代码和上面等效。
let result = [1, 2, 3, 4, 5].map(
(arg) => {
return arg * arg;
}
);
console.log(result);
此外,这里还要注意箭头函数的this指向问题:
//function嵌套定义的函数,this指向全局对象
let tom = {
name : 'Tom',
sayHello : function () {
let show = function () {
console.log('Hello, I am ' + this.name);
};
show();
}
};
//Lambda表达式不会覆盖this,this是属于sayHello函数的,指向jerry对象
let jerry = {
name : 'Jerry',
sayHello : function () {
let show = () => console.log('Hello, I am ' + this.name);
show();
}
};
tom.sayHello();
jerry.sayHello();
输出结果:
Hello, I am undefined
Hello, I am Jerry
我们可以发现,实际上function定义的函数this指向是比较诡异的,这是历史原因造成的,反而Lambda表达式的this指向更好理解,因此推荐使用Lambda表达式代替function匿名函数。
关于用function定义函数的this指向补充说明
由于JavaScript写法比较灵活,很多人编码的习惯都不同,这里补充说明,对于使用function定义的函数,其this指向分为这样两种情况:
- 调用对象的方法(基于一个对象调用定义在他身上的函数),其this指向该对象
- 其余情况,包括匿名函数,this都指向全局对象(浏览器中就是window对象)
那么,如何解决使用function定义函数时的this指向问题呢?
方法一:再定义一个that保存前文的this指向
let tom = {
name : 'Tom',
sayHello : function () {
let that = this;
let show = function () {
console.log('Hello, I am ' + that.name);
};
show();
}
};
方法二:使用bind手动覆盖函数原本绑定的this
let tom = {
name : 'Tom',
sayHello : function () {
let show = function () {
console.log('Hello, I am ' + this.name);
};
show = show.bind(this);
show();
}
};
关于Lambda表达式的常见误用
我们要记住,Lambda表达式不会覆盖this的指向,下面这种写法,this指向的是全局对象,而不是tom:
错误写法:
let tom = {
name : 'Tom',
sayHello : () => {
console.log('Hello, I am ' + this.name);
}
};
注意:即使上面的代码逻辑上是明显错误的,但是语法上并没有错,JavaScript支持这么写,就会有人利用这个特性实现自己的功能,我们看到时不要懵逼就行了。
注:然而我使用的webstorm IDE(2018.1版本)在Lambda中键入this.还会立刻给出name的自动提示,使用JavaScript时,IDE并不靠谱,这也造成了JavaScript难编写、难调试、可读性差,相反,优点就是JavaScript相当的灵活,对开发人员编码能力要求较高。