函数参数的默认值

基本用法

在ES6之前,不能直接为函数的参数指定默认值,为了避免这个问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。

ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。

  1. function log(x, y = 'World') {
  2. console.log(x, y);
  3. }
  4.  
  5. log('Hello') // Hello World
  6. log('Hello', 'China') // Hello China
  7. log('Hello', '') // Hello

可以看到,ES6的写法比ES5简洁许多,而且非常自然。

ES6的写法还有两个好处:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。

参数变量是默认声明的,所以不能用letconst再次声明。

  1. function foo(x = 5) {
  2. let x = 1; // error
  3. const x = 2; // error
  4. }

上面代码中,参数变量x是默认声明的,在函数体中,不能用letconst再次声明,否则会报错。

与解构赋值默认值结合使用

参数默认值可以与解构赋值的默认值,结合起来使用。

  1. function foo({x, y = 5}) {
  2. console.log(x, y);
  3. }
  4.  
  5. foo({}) // undefined, 5
  6. foo({x: 1}) // 1, 5
  7. foo({x: 1, y: 2}) // 1, 2
  8. foo() // TypeError: Cannot read property 'x' of undefined

上面代码使用了对象的解构赋值默认值,而没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量xy才会通过解构赋值而生成。如果函数foo调用时参数不是对象,变量xy就不会生成,从而报错。如果参数对象没有y属性,y的默认值5才会生效。

参数默认值的位置

通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。

  1. // 例一
  2. function f(x = 1, y) {
  3. return [x, y];
  4. }
  5.  
  6. f() // [1, undefined]
  7. f(2) // [2, undefined])
  8. f(, 1) // 报错
  9. f(undefined, 1) // [1, 1]
  10.  
  11. // 例二
  12. function f(x, y = 5, z) {
  13. return [x, y, z];
  14. }
  15.  
  16. f() // [undefined, 5, undefined]
  17. f(1) // [1, 5, undefined]
  18. f(1, ,2) // 报错
  19. f(1, undefined, 2) // [1, 5, 2]

上面代码中,有默认值的参数都不是尾参数。这时,无法只省略该参数,而不省略它后面的参数,除非显式输入undefined

如果传入undefined,将触发该参数等于默认值,null则没有这个效果。

  1. function foo(x = 5, y = 6) {
  2. console.log(x, y);
  3. }
  4.  
  5. foo(undefined, null)
  6. // 5 null

上面代码中,x参数对应undefined,结果触发了默认值,y参数等于null,就没有触发默认值。

函数的length属性

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

  1. (function (a) {}).length //
  2. (function (a = 5) {}).length //
  3. (function (a, b, c = 5) {}).length //

上面代码中,length属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。比如,上面最后一个函数,定义了3个参数,其中有一个参数c指定了默认值,因此length属性等于3减去1,最后得到2

这是因为length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest参数也不会计入length属性。

  1. (function(...args) {}).length //

如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

  1. (function (a = 0, b, c) {}).length //
  2. (function (a, b = 1, c) {}).length //

作用域

一个需要注意的地方是,如果参数默认值是一个变量,则该变量所处的作用域,与其他变量的作用域规则是一样的,即先是当前函数的作用域,然后才是全局作用域。

  1. var x = 1;
  2.  
  3. function f(x, y = x) {
  4. console.log(y);
  5. }
  6.  
  7. f(2) //

上面代码中,参数y的默认值等于x。调用时,由于函数作用域内部的变量x已经生成,所以y等于参数x,而不是全局变量x

如果调用时,函数作用域内部的变量x没有生成,结果就会不一样。

  1. let x = 1;
  2.  
  3. function f(y = x) {
  4. let x = 2;
  5. console.log(y);
  6. }
  7.  
  8. f() //

上面代码中,函数调用时,y的默认值变量x尚未在函数内部生成,所以x指向全局变量,结果又不一样。

利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。

  1. function throwIfMissing() {
  2. throw new Error('Missing parameter');
  3. }
  4.  
  5. function foo(mustBeProvided = throwIfMissing()) {
  6. return mustBeProvided;
  7. }
  8.  
  9. foo()
  10. // Error: Missing parameter

上面代码的foo函数,如果调用的时候没有参数,就会调用默认值throwIfMissing函数,从而抛出一个错误。

从上面代码还可以看到,参数mustBeProvided的默认值等于throwIfMissing函数的运行结果(即函数名之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行),这与python语言不一样。

另外,可以将参数默认值设为undefined,表明这个参数是可以省略的。

  1. function foo(optional = undefined) { ··· }

rest参数

ES6引入rest参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

  1. function add(...values) {
  2. let sum = 0;
  3.  
  4. for (var val of values) {
  5. sum += val;
  6. }
  7.  
  8. return sum;
  9. }
  10.  
  11. add(2, 5, 3) //

上面代码的add函数是一个求和函数,利用rest参数,可以向该函数传入任意数目的参数。

下面是一个rest参数代替arguments变量的例子。

  1. // arguments变量的写法
  2. function sortNumbers() {
  3. return Array.prototype.slice.call(arguments).sort();
  4. }
  5.  
  6. // rest参数的写法
  7. const sortNumbers = (...numbers) => numbers.sort();

上面代码的两种写法,比较后可以发现,rest参数的写法更自然也更简洁。

注意,rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

  1. // 报错
  2. function f(a, ...b, c) {
  3. // ...
  4. }

函数的length属性,不包括rest参数。

  1. (function(a) {}).length //
  2. (function(...a) {}).length //
  3. (function(a, ...b) {}).length //

扩展运算符

含义

扩展运算符(spread)是三个点(...)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。

  1. console.log(...[1, 2, 3])
  2. // 1 2 3
  3.  
  4. console.log(1, ...[2, 3, 4], 5)
  5. // 1 2 3 4 5
  6.  
  7. [...document.querySelectorAll('div')]
  8. // [<div>, <div>, <div>]

该运算符主要用于函数调用。

替代数组的apply方法

由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。

  1. // ES5的写法
  2. function f(x, y, z) {
  3. // ...
  4. }
  5. var args = [0, 1, 2];
  6. f.apply(null, args);
  7.  
  8. // ES6的写法
  9. function f(x, y, z) {
  10. // ...
  11. }
  12. var args = [0, 1, 2];
  13. f(...args);

扩展运算符的应用

(1)合并数组

  1. // ES5
  2. [1, 2].concat(more)
  3. // ES6
  4. [1, 2, ...more]

(2)与解构赋值结合

扩展运算符可以与解构赋值结合起来,用于生成数组。

  1. // ES5
  2. a = list[0], rest = list.slice(1)
  3. // ES6
  4. [a, ...rest] = list

如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

(3)函数的返回值

JavaScript的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。

  1. var dateFields = readDateFields(database);
  2. var d = new Date(...dateFields);

上面代码从数据库取出一行数据,通过扩展运算符,直接将其传入构造函数Date

4)字符串

扩展运算符还可以将字符串转为真正的数组。

  1. [...'hello']
  2. // [ "h", "e", "l", "l", "o" ]

上面的写法,有一个重要的好处,那就是能够正确识别32位的Unicode字符。

  1. 'x\uD83D\uDE80y'.length //
  2. [...'x\uD83D\uDE80y'].length //

(5)实现了Iterator接口的对象

任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。

  1. var nodeList = document.querySelectorAll('div');
  2. var array = [...nodeList];

上面代码中,querySelectorAll方法返回的是一个nodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了Iterator接口。

name属性

函数的name属性,返回该函数的函数名。

  1. function foo() {}
  2. foo.name // "foo"

这个属性早就被浏览器广泛支持,但是直到ES6,才将其写入了标准。

需要注意的是,ES6对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5的name属性,会返回空字符串,而ES6的name属性会返回实际的函数名。

  1. var func1 = function () {};
  2.  
  3. // ES5
  4. func1.name // ""
  5.  
  6. // ES6
  7. func1.name // "func1"

上面代码中,变量func1等于一个匿名函数,ES5和ES6的name属性返回的值不一样。

bind返回的函数,name属性值会加上“bound ”前缀。

  1. function foo() {};
  2. foo.bind({}).name // "bound foo"
  3.  
  4. (function(){}).bind({}).name // "bound "

es6学习笔记9--函数的扩展的更多相关文章

  1. ES6学习笔记(6)----函数的扩展

    参考书<ECMAScript 6入门>http://es6.ruanyifeng.com/ 函数的扩展 函数的默认值 : ES6可以为函数指定默认值 (1)指定默认值的两种方式 a.函数参 ...

  2. ES6 学习笔记之四 对象的扩展

    ES6 为对象字面量添加了几个实用的功能,虽然这几个新功能基本上都是语法糖,但确实方便. 一.属性的简洁表示法 当定义一个对象时,允许直接写入一个变量,作为对象的属性,变量名就是属性名. 例1: , ...

  3. es6学习笔记-async函数

    1 前情摘要 前段时间时间进行项目开发,需求安排不是很合理,导致一直高强度的加班工作,这一个月不是常说的996,简直是936,还好熬过来了.在此期间不是刚学会了es6的promise,在项目有用到pr ...

  4. ES6学习笔记(一)——扩展运算符和解构赋值

    前言 随着前端工程化的快速推进,在项目中使用ES6甚至更高的ES7等最近特性早已不是什么新鲜事.之前还觉得既然浏览器支持有限,那了解一下能看懂就好,然而仅仅了解还是不够的,现在放眼望去,那些成熟框架的 ...

  5. ES6学习笔记(三)——数值的扩展

    看到这条条目录有没有感觉很枯燥,觉得自己的工作中还用不到它所以实在没有耐心看下去,我也是最近得闲,逼自己静下心来去学习去总结,只有在别人浮躁的时候你能静下心来去学去看去总结,你才能进步.毕竟作为前端不 ...

  6. ES6学习笔记二:各种扩展

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/7242967.html 一:字符串扩展 1:字符串遍历器 for (let char of str) { // ...

  7. ES6学习笔记(8)----对象的扩展

    参考书<ECMAScript 6入门>http://es6.ruanyifeng.com/ 对象的扩展 1.属性名的简洁表示法 : ES6允许在代码中直接写变量,变量名是属性名,变量值是属 ...

  8. ES6学习笔记(5)----数值的扩展

    参考书<ECMAScript 6入门>http://es6.ruanyifeng.com/ 数值的扩展 1.Number对象的扩展(1)javascript的全局函数isNaN,isFin ...

  9. ES6学习笔记之数组的扩展

    ✏️1. 扩展运算符 扩展运算符(spread)是三个点(...),将一个数组转为用逗号分隔的参数序列. 普通用法 console.log(...[1,2,3]);//1 2 3 数组拷贝(普通类型深 ...

随机推荐

  1. Latex 表格(跨行、跨列、背景加灰)new

    一. 效果如图 二.代码如下 1. 首部增加宏包: \usepackage{multirow} 2. 正文部分增加: \begin{table} \centering \caption{Suspici ...

  2. 使用PerfView监测.NET程序性能(二):Perfview的使用

    在上一篇博客中,我们了解了对Windows及应用程序进行性能分析的基础:Event Trace for Windows (ETW).现在来看看基于ETW的性能分析工具——Perfview.exe Pe ...

  3. C# 动态创建SQL数据库(二)

    使用Entity Framework  创建数据库与表 前面文章有说到使用SQL语句动态创建数据库与数据表,这次直接使用Entriy Framwork 的ORM对象关系映射来创建数据库与表 一 新建项 ...

  4. 自定义WPF窗体形状

    介绍 你好WPF爱好者. 随着WPF等统一API语言的发明,丰富用户界面变得非常容易. 创建丰富的用户界面只是一个想法. 您需要拥有的是创造性思维和最新技术融合. WPF和Expression Ble ...

  5. [Ynoi2016]这是我自己的发明(莫队)

    话说这道题数据是不是都是链啊,我不手动扩栈就全 \(RE\)... 不过 \(A\) 了这题还是很爽的,通过昨晚到今天早上的奋斗,终于肝出了这题 其实楼上说的也差不多了,就是把区间拆掉然后莫队瞎搞 弱 ...

  6. iOS开发手记-iOS8中使用定位服务解决方案

    问题描述: 在iOS8之前,app第一次开始定位服务时,系统会弹出一个提示框来让用户选择是否允许使用定位信息.但iOS8后,app将不会出现这个弹窗.第一次运行之后,在设置->隐私->定位 ...

  7. Centos7安装python3并与python2共存

    1.查看是否已经安装Python CentOS 7.2 默认安装了python2.7.5 因为一些命令要用它比如yum 它使用的是python2.7.5. 使用 python -V 命令查看一下是否安 ...

  8. 线性表java实现

    顺序表 public class SequenceList { /* * content,节点内容 * location,节点在表中的位置(序号) * */ private String conten ...

  9. C、C++打包成.dll .so .a 给Unity使用

    C.C++打包成.dll .so .a 给Unity使用 打包.dll库 工具:VS 使用VS新建项目 选择不大于.NET3.5的版本 选择Visual C++ -> Win32 控制台应用程序 ...

  10. 【xsy2506】 bipartite 并查集+线段树

    题目大意:有$n$个点,你需要操作$m$次.每次操作为加入/删除一条边. 问你每次操作后,这$n$个点构成的图是否是二分图. 数据范围:$n,m≤10^5$. 此题并没有强制在线,考虑离线做法. 一条 ...