函数的扩展
【函数的扩展】
函数参数的默认值
认识函数参数的默认值
调用函数的时候传参了,就用传递的参数;如果没传参,就用默认值
函数参数默认值的基本用法
1 | // 之前的默认值实现方式 |
1 | // ES6 默认值实现方式 |
默认值的生效条件
不传参数,或者明确的传递 undefined 作为参数,只有这两种情况下,默认值才会生效。
注意:null 就是 null,不会使用默认值。
与解构赋值默认值结合使用
参数默认值可以与解构赋值的默认值,结合起来使用。
1 | function foo({x, y = 5}) { |
上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数foo()
的参数是一个对象时,变量x
和y
才会通过解构赋值生成。如果函数foo()
调用时没提供参数,变量x
和y
就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。
1 | function foo({x, y = 5} = {}) { |
上面代码指定,如果没有提供参数,函数foo
的参数默认为一个空对象。
下面是另一个解构赋值默认值的例子。
1 | function fetch(url, { body = '', method = 'GET', headers = {} }) { |
上面代码中,如果函数fetch()
的第二个参数是一个对象,就可以为它的三个属性设置默认值。这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。
1 | function fetch(url, { body = '', method = 'GET', headers = {} } = {}) { |
上面代码中,函数fetch
没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量method
才会取到默认值GET
。
注意,函数参数的默认值生效以后,参数解构赋值依然会进行。
1 | function f({ a, b = 'world' } = { a: 'hello' }) { |
上面示例中,函数f()
调用时没有参数,所以参数默认值{ a: 'hello' }
生效,然后再对这个默认值进行解构赋值,从而触发参数变量b
的默认值生效。
参数默认值的位置
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
1 | // 例一 |
上面代码中,有默认值的参数都不是尾参数。这时,无法只省略该参数,而不省略它后面的参数,除非显式输入undefined
。
如果传入undefined
,将触发该参数等于默认值,null
则没有这个效果。
1 | function foo(x = 5, y = 6) { |
上面代码中,x
参数对应undefined
,结果触发了默认值,y
参数等于null
,就没有触发默认值。
函数参数默认值的应用
接收很多参数的时候
1 | // 普通时候 |
某一个参数不得省略,如果省略就抛出一个错误
1 | function throwIfMissing() { |
上面代码的foo
函数,如果调用的时候没有参数,就会调用默认值throwIfMissing
函数,从而抛出一个错误。
从上面代码还可以看到,参数mustBeProvided
的默认值等于throwIfMissing
函数的运行结果(注意函数名throwIfMissing
之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会运行。
另外,可以将参数默认值设为undefined
,表明这个参数是可以省略的。
1 | function foo(optional = undefined) { ··· } |
rest 参数
前言
剩余语法(Rest syntax 也可以叫剩余参数)看起来和展开语法完全相同都是使用 ...
的语法糖,不同之处在于剩余参数用于解构数组和对象。从某种意义上说,剩余语法与展开语法是相反的:展开语法将数组展开为其中的各个元素,而剩余语法则是将多个元素收集起来成为一个整体。
函数参数
在讲解剩余参数前,我们先来看看,剩余参数在函数参数中都解决了哪些问题?为什么会引入剩余参数的概念?
在 ES5 中,函数经常会传入不定参数,在传入不定参数时,ES5 的给出的解决方案是通过 arguments
对象来获取函数调用时传递的参数。 arguments
对象不是一个数组,它是一个类数组对象,所谓类数组对象,就是指可以通过索引属性访问元素并且拥有 length 属性的对象。
一个简单的类数组对象是长这样的:
1 | var arrLike = { |
而它所对应的数组应该是这样子的:
1 | var arr = ['name', 'age', 'job']; |
这里我们说类数组对象与数组的性质相似,是因为类数组对象在访问、赋值、获取长度上的操作与数组是一致的,具体内容可查阅相关的类数组使用。
在函数体中定义了 Arguments 对象,其包含函数的参数和其它属性,以 arguments
变量来指代。下面我们看个实例:
1 | function fn() { |
在控制台中打印出上面的代码结果,如下图所示:在定义函数的时候没有给定参数,但是通过 arguments
对象可以拿到传入的参数。可以看到 arguments
中包含了函数传递的参数、length 等属性,length 属性表示的是实参的长度,即调用函数的时候传入的参数个数。这样我们就对 arguments
对象有了一定的了解。
在 ES5 的开发模式下,想要使用传递的参数,则需要按位置把对应的参数取出来。尽管 arguments
是一个类数组且可遍历的变量,但它终究不是数组,它不支持数组方法,因此我们不能调用 arguments.forEeach (…)
等数组的方法。需要使用一些特殊的方法转换成数组使用,如:
1 | function fn() { |
终于借助 call
方法把 arguments
转化成一个真正的数组了。但是这样无疑是一个繁琐的过程,而且不容易理解。这时 ES6 给出了它的完美解决方案 —— 剩余参数,那剩余参数是如何在函数传参中使用的呢?下面我们来看看实例:
语法:const add = (x, y, z, ...args) => {};
1 | function fn(...args) { |
使用方式很简单在函数定义时使用 ...
紧接着跟一个收集的参数,这个收集的参数就是我们所传入不定参数的集合 —— 也就是数组。这样就很简单地摆脱了 arguments 的束缚。另外,还可以指定一个默认的参数,如下示例:
1 | function fn(name, ...args) { |
上面的代码中给函数第一个参数,声明一个变量 name,剩余的参数会被 ...
收集成一个数组,这就是剩余参数。引入剩余参数就是为了能替代函数内部的 arguments
,由于 arguments
对象不具备数组的方法,所以很多时候在使用之前要先转换成一个数组。而剩余参数本来就是一个数组,避免了这多余的一步,使用起来既优雅又自然。
注意事项
箭头函数的剩余参数
箭头函数的参数部分即使只有一个剩余参数,也不能省略圆括号。
const add = (...args) => {};
使用剩余参数替代 arguments 获取实际参数
- 剩余参数是一个 “真数组”,arguments 是一个 “伪数组”
- 剩余参数的名字可以自定义
剩余参数的位置
剩余参数只能是最后一个参数,之后不能再有其他参数,否则会报错。
剩余参数的应用
作为数组的应用:
1 | const add = (...args) => { |
与解构赋值结合使用:
(剩余参数不一定非要作为函数参数使用)
- 与数组解构赋值结合
1 | let array = [1, 2, 3, 4, 5]; |
- 与对象解构赋值结合
1 | const {x, y, ...z} = {a: 3, x: 1, y: 2, b: 4}; |
1 | const func = ({x, y, ...z}) => { |
- 在函数传参的时候也可以是和解构一起使用
1 | function fun(...[a, b, c]) { |
上面的代码中,a、b、c 会去解构传入参数,加上有剩余语法的作用,对应的值从数组中的项解构出来,在函数内部直接使用解构出来的参数即可。剩余语法看起来和展开语法完全相同,不同点在于,剩余参数用于解构数组和对象。
小结
本节结合了 ES5 函数中的 arguments
对象引入了为什么 ES6 会引入剩余参数的概念,可以看到剩余参数所带来的好处。本节内容可以总结以下几点:
- 剩余参数是为了能替代函数内部的 arguments 而引入的;
- 和展开语法相反,剩余参数是将多个单个元素聚集起来形成一个单独的个体的过程。
箭头函数
前言
在编程中使用最多的就是函数,在 ES5 中是用 function
关键字来定义函数的,由于历史原因 function
定义的函数存在一些问题,如 this
的指向、函数参数 arguments
等。
ES6 规定了可以使用 “箭头” =>
来定义一个函数,语法更加简洁。它没有自己的 this
、arguments
、super
或 new.target
,箭头函数表达式更适用于那些本来需要匿名函数的地方,但它不能用作构造函数。
认识箭头函数
普通函数:
function 函数名() {}
const 变量名 = function () {};
箭头函数:
-
参数 => 函数体
-
() => {}
由于箭头函数是匿名函数,所以我们通常把它赋给一个变量
1 | const add = (x, y) => { |
箭头函数注意事项
省略写法
1 | const add = (x) => { |
单行函数体
1 | const add = (x, y) => { |
单行对象
1 | const add = (x, y) => { |
推荐:一般情况最好不要简写!
非箭头函数中的 this 指向
全局作用域中的 this 指向
1 | console.log(this); |
一般函数(非箭头函数)中的 this 指向
只有在函数调用的时候 this 指向才能确定,不调用的时候,不知道指向谁。
this 指向和函数在哪儿没有关系,只和谁在调用有关。
1 | function add() { |
1 | // 严格模式 |
1 | // 严格模式 |
箭头函数没有 this
在 JavaScript 中,要说让人最头疼的知识点中,this 绑定绝对算一个,这是因为 this 的绑定 ‘难以捉摸’,出错的时候还往往不知道为什么,相当反逻辑。下面我们来看一个示例:
1 | var title = "全局标题"; |
通过上面的小例子的打印结果可以看出 this
的问题,说明 this
的指向是不固定的。
这里简单说明一下 this
的指向,this
指向的是调用它的对象。例子中的 this
是在 getTitle 的函数中的,执行 imooc.getTitle()
这个方法时,调用它的对象是 imooc
,所以 this 的指向是 imooc
。
之后把 imooc.getTitle
方法赋给 bar
,这里要注意的是,只是把地址赋值给了 bar
,并没有调用。 而 bar
是全局对象 window
下的方法,所以在执行 bar
方法时,调用它的是 Window 对象,所以这里打印的结果是 window 下的 title——“全局标题”。
TIPS: 上面的示例只是简单的
this
指向问题,还有很多更加复杂的,在面试中经常会被问到,所以还不清楚的同学可以去研究一下this
的问题。
ES6 为了规避这样的问题,提出了箭头函数的解决方案,在箭头函数中没有自己的 this
指向,所有的 this 指向都指向它的上一层 this
,这样规定就比较容易理解了。下面看使用箭头函数下的 this
指向:
1 | var title = "全局标题"; |
上面的打印结果可以看出来,所有的 this
指向都指向了 window 对象下的 title,本身的 imooc 对象下没有了 this
,它的上一层就是 window。
不适用箭头函数的场景
作为构造函数
因为箭头函数没有 this,而构造函数的核心就是 this。
需要 this 指向调用对象的时候
因为箭头函数没有 this,所以如果箭头函数中出现了 this,那么这个 this 就是外层的!
需要使用 arguments 的时候
箭头函数没有 arguments。
(这个问题有替代解决方案:剩余参数)
1 | var fun = function() { |
上面的示例中,对比两种定义函数的方法可以明显的看出,在箭头函数中去取 arguments
时会报引用错误,没有定义的 arguments
。
arguments
的主要作用是获取所有调用函数时所需要传入的参数,在箭头函数中使用剩余参数 ...args
,在函数内可以直接使用。
1 | function foo(...args) { |
其他注意点
不能用作构造器
箭头函数不能用作构造器,和 new 一起用会抛出错误。
1 | var Foo = () => {}; |
没有 prototype 属性
箭头函数没有 prototype 属性。
1 | var Foo = () => {}; |
不能使用 yield 命令
yield 关键字通常不能在箭头函数中使用,因此箭头函数不能用作 Generator 函数。
小结
本节主要讲解了 ES6 的箭头函数,总结了以下几点:
- 更短的函数,优雅简洁;
- 箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this;
- 不能绑定 arguments, 只能使用
...args
展开运算来获取当前参数的数组。
函数参数的尾逗号
ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。
此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。
1 | function clownsEverywhere( |
上面代码中,如果在param2
或bar
后面加一个逗号,就会报错。
如果像上面这样,将参数写成多行(即每个参数占据一行),以后修改代码的时候,想为函数clownsEverywhere
添加第三个参数,或者调整参数的次序,就势必要在原来最后一个参数后面添加一个逗号。这对于版本管理系统来说,就会显示添加逗号的那一行也发生了变动。这看上去有点冗余,因此新的语法允许定义和调用时,尾部直接有一个逗号。
1 | function clownsEverywhere( |
这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。
catch 命令的参数省略
JavaScript 语言的try...catch
结构,以前明确要求catch
命令后面必须跟参数,接受try
代码块抛出的错误对象。
1 | try { |
上面代码中,catch
命令后面带有参数err
。
很多时候,catch
代码块可能用不到这个参数。但是,为了保证语法正确,还是必须写。ES2019做出了改变,允许catch
语句省略参数。
1 | try { |