函数扩展

这篇笔记主要介绍ES6中,对函数的一些改进语法:

  1. 默认参数
  2. 可变参数
  3. 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指向分为这样两种情况:

  1. 调用对象的方法(基于一个对象调用定义在他身上的函数),其this指向该对象
  2. 其余情况,包括匿名函数,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相当的灵活,对开发人员编码能力要求较高。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。