函数形参的默认值

ES6中的默认参数值

function makeRequest(url, timeout = 2000, callback = function() {}) {

}

可以为任意参数指定默认值,在已指定默认值的参数后可以继续声明无默认值参数。

function makeRequest(url, timeout = 2000, callback) {

}

这种情况下,之后当不为第二个参数传入值或者主动为第二个参数传入 undefined 时才会使用 timeout 的默认值

// 使用 timeout 的默认值
makeRequest("/foo", undefined, function(body) {
doSomething(body);
}) // 使用 timeout 的默认值
makeRequest("/foo"); // 不使用 timeout 的默认值
makeRequest("/foo", null, function(body) {
doSomething(body);
})

第三个调用需要注意。对于默认参数值,null是一个合法值。

关于 null 和 undefined 的区别请参照阮一峰的 undefined与null的区别

默认参数值对 arguments 对象的影响

ES5非严格模式下,函数命名参数的变化会体现在 arguments 对象中。

function mixArgs(first, second) {
console.log(arguments.length);
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d";
console.log(first === arguments[0]);
console.log(second === arguments[1]);
} mixArgs("a", "b");
// 2
// true
// true
// true
// true

然而在ES5严格模式下,取消了 arguments 对象这个令人感到困惑的行为。

命名参数与 arguments 对象分离开了。

function mixArgs(first, second) {
"use strict"; // 设置为严格模式
console.log(arguments.length);
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d";
console.log(first === arguments[0]);
console.log(second === arguments[1]);
} mixArgs("a", "b");
// 2
// true
// true
// false
// false

在ES6中,如果一个函数使用了默认参数值,则无论是否显示定义了严格模式,arguments 对象的行为都将是与ES5严格模式下保持一致。

function mixArgs(first, second = "b") {
console.log(arguments.length);
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d";
console.log(first === arguments[0]);
console.log(second === arguments[1]);
} mixArgs("a");
// 1
// true
// false
// false
// false mixArgs("a", "b");
// 2
// true
// true
// false
// false

默认参数表达式

默认参数值可以是非原始值传参。

function getValue() {
return 5;
} function add(first, second = getValue()) {
return first + second;
} console.log(add(1, 1)); // 2
console.log(add(1)); // 6

上例中,second 的默认值为 getValue() 的返回值。

默认参数还可以使用先定义的参数作为后定义参数的默认值。

function add(first, second = first) {
return first + second;
} console.log(add(1, 1)); // 2
console.log(add(1)); // 2

还可以将上面两个例子合起来修改成如下形式:

function getValue(value) {
return value + 5;
} function add(first, second = getValue(first)) {
return first + second;
} console.log(add(1, 1)); // 2
console.log(add(1)); // 7

在引用参数默认值时,只允许引用前面参数的值,即先定义的参数不能访问后定义的参数。

function add(first = second, second) {
return first + second;
} console.log(add(1, 1)); // 2
console.log(add(undefined, 1)); // 抛出错误
// Uncaught ReferenceError: second is not defined

因为second比first定义的晚,所以不能作为fist的默认值。

这里就是所谓默认参数的临时死区(TDZ)。

Note

默认参数有自己的作用域和临时死区,其与函数体的作用域是各自独立的,也就是说参数的默认值不可访问函数体内声明的变量。

处理无命名参数

JS的函数语法规定,无论函数已定义的命名参数有多少,都不限制调用时传入的实际参数的数量,调用时总是可以传入任意数量的参数。

ES5中的无命名参数

JS提供了 arguments 对象来检查函数的所有参数,从而不必定义每一个要用的参数。

function pick(object) {
let result = Object.create(null); // 从第二个参数开始
for (let i = 1, len = arguments.length; i < len; i++) {
result[arguments[i]] = object[arguments[i]];
} return result;
} let book = {
title: "Understanding ECMAScript 6",
author: "Nicholas C. Zakas",
year: 2016
}; let bookData = pick(book, "author", "year"); console.log(bookData.author); // "Nicholas C. Zakas"
console.log(bookData.year); // 2016

上例中,pick() 函数返回一个给定对象的副本,包含原始对象的特定子集。

不定参数

ES6中提供了不定参数(rest parameters)特性来提供更好的实现方案。

function pick(object, ...keys) {
let result = Object.create(null); for (let i = 0, len = keys.length; i < len; i++) {
result[keys[i]] = object[keys[i]];
} return result;
}

Note

函数的 length 属性统计的是函数命名参数的数量,不定参数的加入不会影响 length 属性的值。上述 pick 方法的 length 值为1.

不定参数的使用限制

  1. 每个函数最多只能声明一个不定参数,而且一定要放在所有参数的末尾;
  2. 不定参数不能用于对象字面量 setter 之中。

不定参数对 arguments 对象的影响

无论是否使用不定参数,arguments 对象总是包含所有传入函数的参数。

增强的 Function 构造函数

使用 Function 构造函数创建函数。

var add = new Function("first", "second", "return first + second");
console.log(add(1, 1)); // 2

创建的 add 方法是如下样子的:

ƒ anonymous(first,second
/*``*/) {
return first + second
}

ES6中增强了该构造函数,使其可以支持默认参数和不定参数。

var add = new Function("first", "second = first", "return first + second");
console.log(add(1, 1)); // 2
console.log(add(1)); // 2
var pickFirst = new Function("...args", "return args[0]");
console.log(pickFirst(1, 2)); // 1

展开运算符

在所有新功能中,与不定参数最相似的是展开运算符。

以Math.max()方法为例,该方法可以接受任意数量的参数并返回最大的那一个。

let value1 = 25,
value2 = 50; Math.max(value1, value2); // 50

但该方法不支持数组,如果需要从数组中挑出一个最大的时该怎么做呢?

ES5中可以使用apply()方法实现该功能。

let values = [25, 50, 75, 100];
console.log(Math.max.apply(Math, values)); // 100

虽然可以实现该功能,但是难以理解。

ES6中可以使用展开运算符(...)简化上述示例。

let values = [25, 50, 75, 100];
console.log(Math.max(...values)); // 100

也可以将展开运算符与其它正常传入的参数混合使用。

let values = [-25, -50, -75, -100];
console.log(Math.max(...values, 0)); // 0

name 属性

ES6中所有的函数的name属性都有一个合适的值。可以帮助开发更好的追踪问题。

function doSomething() {

}

var doAnotherThing = function() {

};

console.log(doSomething.name); // 函数名称 "doSomething"
console.log(doAnotherThing.name); // 匿名函数的变量的名称 "doAnotherThing"

name 属性的特殊情况

var doSomething = function doSomethingElse() {

}

var person = {
get firstName() {
return "Nicholas";
},
sayName: function() {
console.log(this.name);
}
} console.log(doSomething.name); // "doSomethingElse"
console.log(person.sayName.name); // "sayName"
console.log(person.firstName.name); // "get firstName"
  • doSomething.name

    函数本身的名字权重更高
  • person.sayName.name

    其值取自对象字面量
  • person.firstName.name

    person.firstName实际上是个getter函数,自动加上了前缀 “get”。

    setter函数也有其前缀“set”。

    另外通过 bind() 函数创建的函数,其名称带有“bound”前缀;

    通过Function构造函数创建的函数,其名称带有“anonymous”前缀。
var doSomething = function() {

};

console.log(doSomething.bind().name); // "bound doSomething"
console.log((new Function()).name); // "anonymous"

明确函数的多重用途

ES5及早期版本中的函数具有多重功能,可以结合new使用,函数内的this值将指向一个新对象,函数最终返回这个新对象。

function Person(name) {
this.name = name;
} var person = new Person("JiaJia");
var notAPerson = Person("JiaJia"); console.log(person); // "Person {name: "JiaJia"}"
console.log(notAPerson); // "undefined"
console.log(window.name); // "JiaJia"

如果不同new关键字调用Person方法,不仅得不到想要的结果,还会在全局作用创建一个name属性。

在ES5中判断函数被调用的方法

使用 instanceof 操作符判断是会否是通过new关键字调用。

function Person(name) {
if (this instanceof Person) {
this.name = name;
} else {
throw new Error("必须通过new关键字来调用Person。");
}
} var person = new Person("JiaJia");
var notAPerson = Person("JiaJia"); // 抛出错误

一般来说上述写法是有效的,但是也有例外情况。

因为有一种不依赖new关键字的方法也可以将this绑定到person的实例上。

function Person(name) {
if (this instanceof Person) {
this.name = name;
} else {
throw new Error("必须通过new关键字来调用Person。");
}
} var person = new Person("JiaJia");
var notAPerson = Person.call(person, "XKA");
// 没有抛出错误,但也没有得到想要的对象
// 实际修改的是person实例的值

元属性(Metaproperty) new.target

为了解决判断函数是否通过new关键字调用的问题,ES6引入了 new.target 这个元属性。

元属性是指非对象的属性,其可以提供非对象目标的补充信息。

当调用函数的[[Construct]]方法时,new.target 被赋值为new操作符的目标,通常是新创建对象实例,也就是函数体内this的构造函数;

如果调用[[call]]方法,则new.target的值为undefined。

function Person(name) {
if (typeof new.target !== "undefined") {
this.name = name;
} else {
throw new Error("必须通过new关键字来调用Person。");
}
} var person = new Person("JiaJia");
var notAPerson = Person.call(person, "XKA"); // 抛出错误

也可以检查 new.target 是否被某个特定构造函数所调用

function Person(name) {
if (typeof new.target === Person) {
this.name = name;
} else {
throw new Error("必须通过new关键字来调用Person。");
}
} function AnotherPerson(name) {
Person.call(this, name);
} var person = new Person("JiaJia");
var anotherPerson = new AnotherPerson("DLPH"); // 抛出错误

Note

在函数外使用 new.target 是一个语法错误。

块级函数

在代码块中声明的函数。

"use strict";

if (true) {
console.log(typeof doSomething); // "function" function doSomething() { } doSomething();
} console.log(typeof doSomething); // "undefined"

ES6严格模式下,在定义函数的代码块内,块级函数会被提升至顶部。

块级函数与let函数表达式类似,一旦执行过程流出了代码块,函数定义立即被移除。

两者的区别是let定义的函数不会被提升。

"use strict";

if (true) {
console.log(typeof doSomething); // 抛出错误
// Uncaught ReferenceError: doSomething is not defined let doSomething = function () { } doSomething();
} console.log(typeof doSomething);

非严格模式下的块级函数

在ES6的非严格模式下,块级函数会被提升至外围函数或全局作用域的顶部。

if (true) {
console.log(typeof doSomething); // "function" function doSomething() { } doSomething();
} console.log(typeof doSomething); // "function"

箭头函数

箭头函数是一种使用箭头(=>)定义函数的新语法,但是它与传统的JS函数有些不同。

  • 没有 this、super、arguments和new.target绑定
  • 不能通过new关键字调用
  • 没有原型
  • 不可以改变 this 的绑定
  • 不支持 arguments 对象
  • 不支持重复的命名参数

箭头函数语法

let reflect = value => value;

// 实际相当于
let reflect = function(value) {
return value;
};

如果要传入两个或以上参数,要在参数的两侧添加一对小括号。

let sum = (num1, num2) => num1 + num2;

// 实际上相当于
let sum = function(num1, nume) {
return num1 + num2;
};

如果没有参数,也要在声明的时候写一组没有内容的小括号。

let getName = () => "JiaJia";

// 实际上相当于
let getName = function() {
return "JiaJia";
};

如果函数体是多行,则需要用花括号包裹函数体。

let sum = (num1, num2) => {
return num1 + num2;
} // 实际上相当于
let sum = function(num1, nume) {
return num1 + num2;
};

除了 arguments 对象不能使用外,某种程度上你都可以将花括号里的代码视作传统的函数体定义。

如果想创建一个空函数,需要写一对没有内容的花括号。

let doNothing = () => {};

// 实际上相当于
let doNothing = function() {};

如果想在箭头函数外返回一个对象字面量,则需要将该字面量包裹在小括号里。

let getTempItem = id => ({ id: id, name: "Temp" });

// 实际上相当于
let getTempItem = function(id) {
return {
id: id,
name: "Temp"
};
};

创建立即执行函数表达式

let person = ((name) => {
return {
getName: function() {
return name;
}
};
})("JiaJia"); console.log(person.getName()); // "JiaJia"

箭头函数没有this绑定

箭头函数中没有this绑定,必须通过查找作用域链来决定其值。

如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this;

否则this的值会被设置为undefined。

let PageHandler = {
id: "123456", init: function() {
document.addEventListener("click", event => this.doSomething(event.type), false);
}, doSomething: function(type) {
console.log("Handling " + type + " fro " + this.id);
}
};

这里addEventListener的第二个参数如果使用匿名函数的形式,则this是当前click事件目标对象(这里是document)的引用。

而在本例中,this就是PageHandler对象。

箭头函数缺少正常函数所拥有的property属性,所以不能用它来定义新的类型。

如果尝试用new关键字调用一个箭头函数,会导致程序抛出错误。

var MyType = () => {};
var object = new MyType(); // 抛出错误
// Uncaught TypeError: MyType is not a constructor

箭头函数中的this值取决于该函数外部非箭头函数的this值,且不能通过call()、apply()或bind()方法来改变this的值。

箭头函数和数组

箭头函数的语法简洁,非常适用于数组处理。

var result = values.sort(function(a, b) {
return a - b;
});

可以简化为如下形式

var result = values.sort((a, b) => a - b);

箭头函数没有arguments绑定

箭头函数没有自己的arguments绑定,且无论函数在哪个上下文中执行,箭头函数始终可以访问外围函数的arguments对象。

function createArrowFunctionReturningFirstArg() {
return () => arguments[0];
} var arrowFunction = createArrowFunctionReturningFirstArg(5); console.log(arrowFunction); // 5

尾调用优化

尾调用指的是函数作为另一个函数的最后一条语句被调用。

function doSomething() {
return doSomethingElse(); // 尾调用
}

在ES5中,尾调用的实现与其它函数调用的实现类似:创建一个新的栈帧(stack frame),将其推入调用栈来表示函数调用。

也就是说,在循环调用中,每一个未用完的栈帧都会保存在内存中,当调用栈变得过大时会造成程序问题。

ES6中的尾调用优化

ES6缩减了严格模式下尾调用栈的大小(非严格模式下不受影响),如果满足以下条件,尾调用不再创建新的栈帧,而是清除并重用当前栈帧。

  • 尾调用不访问当前栈帧的变量(也就是说函数不是一个闭包)
  • 在函数内部,尾调用是最后一条语句
  • 尾调用的结果作为函数返回
"use strict";

function doSomething() {
// 可优化
return doSomethingElse(); // 尾调用
}

以下形式均无法优化

"use strict";

function doSomething() {
// 不可优化
doSomethingElse();
}
"use strict";

function doSomething() {
// 不可优化
return 1 + doSomethingElse();
}
"use strict";

function doSomething() {
// 不可优化
var result = doSomethingElse();
return result;
}
"use strict";

function doSomething() {
var num = 1,
func = () => num;
// 不可优化,该函数是一个闭包
return func();
}

如何利用尾优化

递归函数是主要的应用场景,此时尾调用优化的效果最显著。

优化前:

function factorial(n) {
if (n <= 1) {
return 1;
} else {
// 无法优化,必须在返回之后执行乘法操作
return n * factorial(n - 1);
}
}

优化后:

function factorial(n, p = 1) {
if (n <= 1) {
return 1 * p;
} else {
return factorial(n - 1, n * p);
}
}

Warning

通过在谷歌浏览器上测试,好像没有起作用,优化后的代码依然会栈溢出。

这本书作者在写时,这个特性仍在审核中。估计是该优化没有通过ES6的审核。

【读书笔记】【深入理解ES6】#3-函数的更多相关文章

  1. MDX Step by Step 读书笔记(七) - Performing Aggregation 聚合函数之 Max, Min, Count , DistinctCount 以及其它 TopCount, Generate

    MDX 中最大值和最小值 MDX 中最大值和最小值函数的语法和之前看到的 Sum 以及 Aggregate 等聚合函数基本上是一样的: Max( {Set} [, Expression]) Min( ...

  2. 20150206读书笔记<深入理解计算机系统>

    ●第一章 C是系统级编程的首选.C++显示支持抽象,属于应用级程序设计语言. 简单例子: 一个典型系统的硬件组成: 存储器的层次结构: 注:存储器层次结构的设计思想是,该层存储器作为下一层存储器的高速 ...

  3. 深入理解ES6之函数

    一:关于函数的参数: 可以接受任意数量的参数而无视函数声明的参数数量是js函数的独特之处. 1:参数默认值 ES6之前做法: function makeRequest(url,timeout,call ...

  4. 深入理解ES6箭头函数中的this

    简要介绍:箭头函数中的this,指向与一般function定义的函数不同,比较容易绕晕,箭头函数this的定义:箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定. 1.何为定义时 ...

  5. MDX Step by Step 读书笔记(七) - Performing Aggregation 聚合函数之 Sum, Aggregate, Avg

    开篇介绍 SSAS 分析服务中记录了大量的聚合值,这些聚合值在 Cube 中实际上指的就是度量值.一个给定的度量值可能聚合了来自事实表中上千上万甚至百万条数据,因此在设计阶段我们所能看到的度量实际上就 ...

  6. 【读书笔记::深入理解linux内核】内存寻址【转】

    转自:http://www.cnblogs.com/likeyiyy/p/3837272.html 我对linux高端内存的错误理解都是从这篇文章得来的,这篇文章里讲的 物理地址 = 逻辑地址 – 0 ...

  7. 【读书笔记::深入理解linux内核】内存寻址

    我对linux高端内存的错误理解都是从这篇文章得来的,这篇文章里讲的 物理地址 = 逻辑地址 – 0xC0000000:这是内核地址空间的地址转换关系. 这句话瞬间让我惊呆了,根据我的CPU的知识,开 ...

  8. 深入理解ES6箭头函数的this以及各类this面试题总结

    ES6中新增了箭头函数这种语法,箭头函数以其简洁性和方便获取this的特性,俘获了大批粉丝儿 它也可能是面试中的宠儿, 我们关键要搞清楚 箭头函数和普通函数中的this 一针见血式总结: 普通函数中的 ...

  9. (转载) 深入理解ES6箭头函数的this以及各类this面试题总结

    声明:本文转载自 https://blog.csdn.net/yangbingbinga/article/details/61424363 ES6中新增了箭头函数这种语法,箭头函数以其简洁性和方便获取 ...

  10. 理解es6箭头函数

    箭头函数知识点很少,但是要理解清楚,不然看代码会很不适应的. 1. 最简单的写法 x => x*x 可以理解为 我的x要被转化为x*x,所以实际相当于下边的这个 function (x){ re ...

随机推荐

  1. OS X Yosemite升级提示升级OS10.11或更高版本问题解决方法

    如图,楼主的pro久未升级,版本号已经很低.某天一时兴起,想体验最新版本的OS X.就很开心的进行软件更新: 依据iOS上的APP.系统升级经验,这是一个非常自然.毫无难度的过程,哪知道,今天一直卡在 ...

  2. springMVC学习总结(三)数据绑定

    springMVC学习总结(三)数据绑定 一.springMVC的数据绑定,常用绑定类型有: 1.servlet三大域对象: HttpServletRequest HttpServletRespons ...

  3. oracle如何连接别人的数据库,需要在本地添加一些配置

    2.oracle如何连接别人的数据库,需要在本地添加一些配置 1.找到 listener.ora 文件,打开(一般在 C 文件夹) ORCL = (DESCRIPTION = (ADDRESS = ( ...

  4. TCP/IP的那些事--子网掩码

    当前互联网使用的主要是IPv4协议,它是第一个被广泛使用,构成现今互联网的基础的协议.但是,随着用户数量的增多,IPv4包含的IP资源在不断减少.或许你会想,不是还有IPv6吗?IPv6的容量足以应付 ...

  5. 首次在C#程序中用log4net

    众所周知log4net是一个很强大的日志管理库,我自己也用了下,这里作下记录: 首先新建一个项目Log4NetTest,然后将log4net.dll程序集添加引用至Log4NetTest. 然后在Lo ...

  6. 谈谈CommonsChunkPlugin抽取公共模块

    引言 webpack插件CommonsChunkPlugin的主要作用是抽取webpack项目入口chunk的公共部分,具体的用法就不做过多介绍,不太了解可以参考webpack官网介绍: 该插件是we ...

  7. 程序员的自我救赎---11.1:RPC接口使用规范

    <前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...

  8. lesson - 13 Linux系统日常管理2

    内容概要: 1. Linux抓包工具 tcpdump 系统自带抓包工具tcpdump -nn -i eth0 tcp and host 192.168.0.1 and port 80tcpdump - ...

  9. lesson - 10 shell 基础知识

    课程大纲: 1. shell特性 命令历史 history !!  !$  !n  !字符 Tab 键可以补全文件路径或者命令 alias  a=“b”  unalias a 通配符 *匹配零个或多个 ...

  10. Android:Error:Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'.

    今天开发Android项目时,导入了http有关架包后,程序编译报错如下: Error:Execution failed for task ':app:transformResourcesWithMe ...