目录
文章目录
函数
带默认参数的函数
function makeRequest(url, timeout = 2000, callback = function() {}) {
// the rest of the function
}
arguments的变化
- 在ES5的非严格模式下,arguments是会实时更新的,反映了函数参数的变化。
- 在ES5的严格模式下,arguments则不会更新
example:
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中,arguments都不会更新,表现得如同es5的严格模式一样。而如果arguments里采用了默认参数,则会以实际传进去的实参为准
function mixArgs(first, second = "b",third,fourth = 1) {
console.log(arguments);
console.log([first,second,third,fourth])
}
mixArgs("a",undefined,2);
/*
["a", undefined, 2]
["a","b",2, 1]
*/
mixArgs("a",null,2);
/*
["a", null, 2]
["a", null, 2, 1]
*/
默认参数表达式
注意如果默认参数是一个函数的返回结果,那么它可能是会变化的
let value = 5;
function getValue() {
return value++;
}
function add(first, second = getValue()) {
return first + second;
}
console.log(add(1, 1)); // 2
console.log(add(1)); // 6
console.log(add(1)); // 7
//注意,由于仅当没有传入第二个参数时,getValue才被调用,因此值的改变可能发生在任意时刻,另外一定要确保second 是 函数的返回结果而没有遗漏括号,从而误写成函数本身
//还可以在第二个参数中引用第一个参数,但不能反过来获取后面的参数
function add(first, second = first) {
return first + second;
}
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
默认实参的暂时死区(temporal dead zone)
调用add(1,1) 和 add(1)实际是在运行以下代码
let first = 1
let second = 1 // let second = getValue(1)
当function add() 被首次执行时,first 和 second 的绑定被加在了针对参数的 TDZ 中,所以虽然second可以基于first初始化,但反过来就不可以了。实际上,是这么执行的:
let first = second // 此刻,second处于TDZ中,因此对它的任何引用都会报错。
let second = 1
函数参数有他们自己的作用域(scope)和 TDZ,是与函数主体(body)的作用域分开的。函数参数的默认值不能取得函数内部中定义的变量。
函数中的匿名参数
-
rest parameters 剩余参数
rest parameters 是指三个点之后跟着一个命名的参数。这个命名的参数为一个数组,包含着传给这个函数的剩余所有参数,这也就是"rest"一词的含义。比起arguments来,它有两个优势:
- 遍历参数不用从1开始,而是从头到尾
- 一眼就可以看出这个函数可以传入多个参数
另外,rest parameters 不会影响函数的length属性,length代表的是被命名了的函数参数。
它的限制在于:
- 只能有一个rest parameters
- rest parameters必须位于最后
- 不能用于对象的literal setter中,比如:
let object = { // syntax error: can't use rest param in setter set name(...value){ } } 这个限制存在是因为object literal setters 被限制为仅有一个参数。而剩余参数从定义上说,就是一系列数值。
the spread operator
等于把rest parameters反过来。并且可以将它和别的参数合在一起用。
let values = [-25, -50, -75, -100]
console.log(Math.max(...values, 0)); // 0
函数的 name 属性
貌似唯一的用处是在调试时定位问题出在哪里,非常智能地提供了一切能提供的信息
还可能会在前面加描述符 如set get bound 等
匿名函数的name就是anonymous
函数的name只是一个值而已(stirng),不能通过它来获得一个函数的reference
函数的双重用处
当 new 一个 function 的时候,函数内部的 this 值是一个新的对象,而且会被返回。
javascript 有两个不同的函数内置方法,call 和 construct,且这两个方法只供内部使用。当一个函数没有用 new 调用的时候,call 方法被执行,而 new 一个 function 的时候是 construct 被调用。拥有 construct 的函数被称为 constructors。注意不是所有的函数都有construct,比如 arrow functions, 就没有 construct 方法。
在ES5中,通常是用 instanceOf 来判断是否正确地 new 了一个函数
function Person(name) {
if (this instanceof Person) {
this.name = name; // using new
} else {
throw new Error("You must use new with Person.")
}
}
ES6中则引入了元属性new.target。当函数的 construct 方法被调用时,new.target 会是 调用 new 的函数的 body。而当函数的 call 方法被调用时,new.target 是Undefined。
function Person(name) {
// or typeof new.target !== 'undefined'
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 notAPerson = Person.call(person, "Michael"); // error!
var anotherPerson = new AnotherPerson("Nicholas"); // error!
这里用 new AnotherPerson 也引发了报错,这是因为 new.target 必须是 Person (new.target === Person) , 或必须用 new 的方式调用Person (typeof new.target !== ‘undefined’)。 当 AnotherPerson 被调用的时候,接下来是用 Person.call(this, name)
block-level functions 定义在块级作用域内的函数
es5的严格模式下,不能在block 内部声明函数,会引发报错,最佳实践是函数表达式。
es6的严格模式下,则可以在block内声明函数,并且该声明会被提升到block的顶部,当block执行完毕后,该函数就不复存在了,这一点与let式的函数表达式很像。
es6的非严格模式下,block-level function 会被提升到全局、
箭头函数
- 没有 this, super, arguments, new.target 绑定, 箭头函数中的这些值绑定的是包裹箭头函数的最内层非箭头函数。
- 不能用new来调用,箭头函数没有construct内置功能
- 没有prototype
- 不能改变内部的 this 值。在函数的生命周期内this值是恒定的。
- 没有 arguments 对象,只能用命名的形参或者 rest parameters 来获得函数的参数。
- 不管在严格还是非严格模式下,都不能有重复命名的参数
this 问题得到了很好的解决
var PageHandler = {
id: "123456",
init: function() {
document.addEventListener("click",
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log("Handling " + type + " for " + this.id);
}
};
箭头函数的意图是 用过即扔,所以不能作 constructor,在数组中应用特别方便
function createArrowFunctionReturningFirstArg() {
return () => arguments[0];
}
var arrowFunction = createArrowFunctionReturningFirstArg(5);
console.log(arrowFunction()); // 5
使用箭头函数,则只有下面的写法是有效的: (() => {/*函数体*/})();
尾递归优化
es6在严格模式下做了尾递归优化,但需要满足以下条件:
- 尾部调用不需要用到当前栈的那一帧中的变量,即函数不是闭包
- 进行 尾调用 的函数在尾调用后不做任何事(尾调用尾调用,就是在最后的调用!)
- 尾调用的结果被作为函数的值返回
"use strict"
//这是满足尾递归优化的例子
function doSomething() {
// optimized
return doSomethingElse();
}
//以下都是不满足尾递归优化的例子
function doSomething() {
// not optimized - no return
doSomethingElse();
//-----//
// not optimized - must add after returning
return 1 + doSomethingElse();
//-----//
// not optimized - call isn't in tail position
var result = doSomethingElse();
return result;
//-----//
var num = 1,
func = () => num;
// not optimized - function is a closure
return func();
}
尾调用的优化最重要的应用是在函数的递归上。注意要满足优化条件,把用到的变量到放到函数的参数里面传回去。
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);
}
}
Summary
- 默认函数参数值
- Rest Parameters 用数组的形式拿到传入的参数。而且不耽误前面的命名参数,比arguements更灵活
- spread parameters 可以将数组解构成一个一个的参数。之前只能用apply来完成这一工作。
- name属性 可以帮助 识别一个函数,用于debug中的定位,block-level的函数被正式定义,不会在严格模式下报错。
- 函数内部的new.target属性可以用来识别该函数是用内置的call方式调用的还是用construct调用的
- 引入箭头函数,本义是代替匿名函数。语法更简洁,this是用的外层的非箭头函数,没有arguments对象。不能用作constructor
- 尾调用优化让函数在一个较小的堆栈空间内执行,内存更少,防止了栈溢出。优化在满足优化条件时自动完成,主要用于递归。