2019-6-11
第三章 函数
(一)函数参数
ES5 中模拟参数默认值:
function makeRequest(url, timeout, callback) {
timeout = timeout || 2000;
callback = callback || function() {};
// the rest of the function
}
缺点:当参数为0时,也会取到默认值。
ES6的参数默认值
1. ES6提供了参数默认值
只有当参数完全等于undefined时,才会使用默认值。拥有默认值的参数被认为是可选的。
2. 参数默认值对arguments对象的影响
// ES5非严格模式下,arguments对象会随出具名参数的变化而变化;
function mixArgs(first, second) {
console.log(first === arguments[0]); //true
console.log(second === arguments[1]); //true
first = "c";
second = "d";
console.log(first === arguments[0]); //true
console.log(second === arguments[1]); //true
}
mixArgs("a", "b");
// ES5严格模式下,arguments对象与具名参数分离,始终表示初始调用状态;
function mixArgs(first, second) {
"use strict";
console.log(first === arguments[0]); //true
console.log(second === arguments[1]); //true
first = "c";
second = "d"
console.log(first === arguments[0]); //false
console.log(second === arguments[1]); //false
}
mixArgs("a", "b");
// 使用ES6参数默认值:无论是否在严格模式下,表现总是与ES5严格模式一致
function mixArgs(first, second = "b") {
console.log(arguments.length); //1
console.log(first === arguments[0]); //true
console.log(second === arguments[1]); //false
first = "c";
second = "d"
console.log(first === arguments[0]); //false
console.log(second === arguments[1]); //false
}
mixArgs("a");
3. 参数默认值表达式
参数的默认值不一定是基本数据类型,还可以通过执行函数来产生默认值。注意,在函数声明时,并不会调用参数默认表达式,只有在函数调用未传参时,才会执行参数默认表达式,所以参数的默认值也有可能是可变的。
4. 参数默认值的暂时性死区(TDZ)
//前面的参数,可以被后面的参数引用
function add(first, second = first) {
return first + second;
}
console.log(add(1, 1)); // 2
console.log(add(1)); // 2
//相当于
// JavaScript representation of call to add(1, 1)
let first = 1;
let second = 1;
// JavaScript representation of call to add(1)
let first = 1;
let second = first;
//前面的参数不能引用后面的参数
function add(first = second, second) {
return first + second;
}
console.log(add(1, 1)); // 2
console.log(add(undefined, 1)); // throws error
函数参数拥有各自的作用域和暂时性死区,与函数的作用域相分离,这意味着参数的默认值不允许访问在函数体内部声明的任意变量。
使用剩余参数
…具名参数:包含传递给函数的其余参数的一个数组。
// 函数的length属性值是第一个默认参数之前具名参数的个数
function example(first, second = "whoo", third) {
}
console.log(example.length); //1
// 剩余参数不会算入函数的参数个数
function example2(first, ...others) {
}
console.log(example2.length); //1
剩余参数的限制条件:
- 一个函数只能有一个剩余参数,且必须放在最后面;
2)不能在对象字面量的setter函数中使用,因为对象字面量的setter函数被限定只能有一个参数,而剩余参数是不限制参数数量的。
(二)函数构造器
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
(三)扩展运算符...
扩展运算符将一个数组分割,并将各个项作为分离的参数传给函数,而剩余参数把多个参数合并为一个数组;
用途:作为apply()方法的替代品。
扩展运算符也可以在Function构造器中使用。
(四)函数名称
function doSomething() {
// ...
}
var doAnotherThing = function() {
// ...
};
console.log(doSomething.name); // "doSomething"
console.log(doAnotherThing.name); // "doAnotherThing"
var doSomething = function doSomethingElse() {
// ...
};
console.log(doSomething.name); // "doSomethingElse"
var person = {
get firstName() {
return "Nicholas"
},
sayName: function() {
console.log(this.name);
}
}
console.log(person.sayName.name); // "sayName"
var descriptor = Object.getOwnPropertyDescriptor(person, "firstName");
console.log(descriptor.get.name); // "get firstName"
var doSomething = function() {
// ...
};
console.log(doSomething.bind().name); // "bound doSomething"
console.log((new Function()).name); // "anonymous"
(五)函数的双重用途
双重用途:根据是否用new调用区分。
- Js函数包含两个内部方法:[[Call]] [[Construct]]
不使用new时,执行的是[[Call]],运行的是代码中的函数体
使用new时,执行的是[[Construct]],负责创建一个新对象,且将该对象作为this去执行函数体,最后返回这个新对象
注意:箭头函数没有[[Construct]]方法;
- 判断函数如何被调用
ES5中 通过instanceOf来判断,缺点并不一定可靠
function Person(name) {
if (this instanceof Person) {
this.name = name; // using new
} else {
throw new Error("You must use new with Person.")
}
}
var person = new Person("Nicholas");
var notAPerson = Person.call(person, "Michael"); // works!
ES6 新增new.target元属性判断
function Person(name) {
if (typeof new.target !== "undefined") {
this.name = name; // using new
} else {
throw new Error("You must use new with Person.")
}
}
var person = new Person("Nicholas");
var notAPerson = Person.call(person, "Michael"); // error!
new.target还可以判断是否使用特定构造器进行了调用:
function Person(name) {
if (new.target === Person) {
this.name = name; // using new
} else {
throw new Error("You must use new with Person.")
}
}
function AnotherPerson(name) {
Person.call(this, name);
}
var person = new Person("Nicholas");
var anotherPerson = new AnotherPerson("Nicholas"); // error!
(六)块级函数
ES5,严格模式下不允许在代码块内部声明函数,
ES6,会将代码块内部声明的函数视为块级声明
| 提升位置 | 作用域 | |
|---|---|---|
| 严格模式 | 所在代码块顶部 | 当前代码块 |
| 非严格模式 | 所在函数或者全局的顶部 | 所在函数或者全局 |
块级函数和let函数表达式的区别:块级函数会被提升,而let函数表达式不会。
(七)箭头函数
箭头函数与传统js函数的区别:
- 没有this, super, arguments, new.target, 由外层最接近的非箭头函数来决定;
- 不能使用new调用;
- 没有原型;
- 不能修改this;
- 不允许重复的具名参数
使用箭头函数创建立即执行函数时只有一种写法:
(() => {})()
(八)尾调用优化
ES6严格模式下,当满足以下条件时,尾调用优化不会创建新的栈帧,而是清除当前的栈帧并再次利用它:
- 不能引用当前栈帧中的变量(意味着该函数不能是闭包);
- 返回结果后不能做额外操作;
- 尾调用的结果作为当前函数的返回值。
"use strict";
function doSomething() {
// 可被优化
return doSomethingElse();
}
function doSomething() {
var num = 1,
func = () => num;
// 未被优化:此函数是闭包
return func();
}
function doSomething() {
// 未被优化:在返回以后还执行加法
return 1 + doSomethingElse();
}
function doSomething() {
// 未被优化:没有return
doSomethingElse();
}
function doSomething() {
// 未被优化:调用不在尾部
var result = doSomethingElse();
return result;
}
主要考虑应用在递归函数中,能显著提高性能。
如何控制尾调用优化?以计算阶乘函数为例
function factorial(n) {
if (n <= 1) {
return 1;
} else {
// not optimized - must multiply after returning
return n * factorial(n - 1);
}
}
//尾调用优化
function factorial(n, p = 1) {
if (n <= 1) {
return 1 * p;
} else {
let result = n * p;
// optimized
return factorial(n - 1, result);
}
}