文章目录
参数默认值
参数变量默认声明,不能使用let或const二次声明。
function setDefault(x, y="defaultVal"){}
使用解构赋值的方式设置默认值
//函数参数默认值为空对象,但是设置了对象解构赋值的默认值
function fc1({x = 0, y = 1} = {}){
console.log(x,y);
}
//函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值
function fc2({x,y} = {x: 0, y: 1}){
console.log(x,y);
}
两种方法使用时的区别
var val1 = {},
val2 = {x: 3},
val3 = {y: 4},
val4 = {x: 3, y: 4};
fc1();
fc2();
/*Output
0 1
0 1
*/
fc1(val1);
fc2(val1);
/*Output
0 1
undefined undefined
*/
fc1(val2);
fc2(val2);
/*Output
0 1
3 undefined
*/
fc1(val3);
fc2(val3);
/*Output
0 1
undefined 4
*/
fc1(val4);
fc2(val4);
/*Output
0 1
3 4
*/
默认参数的位置
尾参数,可以比较容易看出来可省略的参数。若非尾参数,则需要传入undefined才能触发默认值的赋值。
length属性
该函数预期传入的参数个数。
如果设置的默认值非尾参数,那么默认值的参数之后的参数不被length计入计数。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
name属性
返回该函数的函数名
对于一个匿名函数赋值给变量,ES5的name属性会返回空字符串,而ES6会返回实际的函数名
Function构造函数
其name属性为anonymous
(new Function).name
//result: anonymous
bind返回的函数
function foo() {};
foo.bind({}).name
//result: "bound foo"
(function(){}).bind({}).name
//result: "bound "
(new Function).bind({}).name
//result: "bound anonymous"
作用域
- 先当前函数作用域,后全局作用域。
- 如果参数的默认值是一个函数,该函数的作用域是其声明时所在的作用域。
let foo = 'outer'; function bar(func = x => foo) { let foo = 'inner'; console.log(func()); // outer } bar(); //Output: outer //匿名函数func的foo因为在调用时内部的foo还未生成 //所以调用的是外部的foo
应用
- 抛出一个不能省略的异常错误。
function throwIfMissing() { throw new Error('Missing parameter'); } function foo(mustBeProvided = throwIfMissing()) { return mustBeProvided; } foo() // Error: Missing parameter
- 将参数默认值设置为undefined,表示这个参数可以省略
rest参数
…val,扩展运算符+变量,用于获取函数的多余参数。
rest参数搭配的变量是个数组,多余的参数将放入这个数组中。
rest参数之后不能有其他参数,否则会报错。
length属性不包括rest数组
(function(a) {}).length // 1
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
扩展运算符
- 将一个数组转为用逗号分隔的参数序列
console.log(1,2,...[1,2,3],4) //Output: 1 2 1 2 3 4
- 用于函数调用
function func(a, b, ...c){ console.log(a,b,c); } var t = [1,2,3,4]; func(...t,6,7,7,8,9); //Output: 1 2 [3, 4, 6, 7, 7, 8, 9]
代替数组的apply方法
将数组转化为函数的参数
function arr2para(a,b,c){
console.log(a,b,c);
}
var arr = [1,2,3];
arr2para(...arr);
//Output: 1 2 3
扩展运算符的运用
- 合并数组
- 与解构赋值结合
- 函数参数中解构数组,将数组解构为参数
- 将字符串转为字符数组,可识别码点>0xFFFF的字符
- 将拥有iterator接口的对象转为数组。如果是一个类似数组的对象,可以使用Array.from()将其转为带有iterator接口的数组。
- Map和Set结构,Generator函数
严格模式
只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
原因是函数内部的严格模式,同时适用于函数体代码和函数参数代码。但是在函数执行的时候,先执行参数代码,然后再执行函数体代码。
当在函数体内添加了严格模式,使用了默认值、解构赋值或者扩展运算符的函数已经先执行了参数代码,等到执行函数体代码的时候才知道要使用严格模式执行参数代码,所以报错。
将函数包在一个无参数的立即执行函数里。
const doSomething = (function(){
'use strict';
return function(value = 42){
return value;
}
}())
箭头函数
var f = v => v;
//等价于
var f = function(v){
return v;
}
- 如果箭头函数不需要参数或者需要多个参数,就使用一个圆括号代表参数部分。
- 如果箭头函数的代码块部分多于一条语句,就要使用大括号将他们括起来,并且使用return语句返回。
- 简化回调函数
[1,2,3].map(function(d){return d + 1}); //换成箭头函数 [1,2,3].map(d=>d + 1);
使用注意
- 函数体内的this对象,就是定义所在的对象,而不是使用时所在的对象(即运行时所在的作用域)。(this指向是可变的,但是在这里是固定的。)
箭头函数中不存在的指向外层函数的对应变量有:
this、argument、super、new.target - 不可用作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
- 不可使用argument对象,该对象在函数体内不存在。如果需要,使用rest参数代替
- 不可使用yield命令,因此箭头函数不能用作Generator函数
嵌套
function insert(value) {
return {into: function (array) {
return {after: function (afterValue) {
array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}};
}};
}
//可改写为
let insert2 = (value) => ({
into: (array) => ({
after: (afterValue) => {
array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}
})
});
insert(2).into([1, 3]).after(1); //[1, 2, 3]
insert2(2).into([1, 3]).after(1); //[1, 2, 3]
管道机制
const pipeline = (...func) => val => func.reduce((a,b) => b(a), val);
const plus1 = a => a + 1;
const mult2 = a => a * 2;
const addThenMult = pipeline(plus1, mult2);
addThenMult(5);
//mult2(plus1(5))
改写λ演算1
λ演算:基本定义形式:
λ<变量>.<表达式> 用这种方法定义的函数又叫λ表达式,例如 λx.x^2^+2*x +1
// λ演算的写法
var fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v)))
// ES6的写法
var fix = f => (x => f(v => x(x)(v)))
(x => f(v => x(x)(v)));
尾调用
函数的最后一步调用其他函数。不过尾调用不一定出现在函数尾部,只要是最后一步即可
function f(x){
return g(x);
}
以下情况不属于尾调用
// 情况一:调用函数g之后还有赋值操作
function f(x){
let y = g(x);
return y;
}
// 情况二:调用函数g之后还有操作
function f(x){
return g(x) + 1;
}
// 情况三:调用g函数,但是返回了undefined
function f(x){
g(x);
}
尾调用优化
只保留内层函数的调用帧。
函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同于
function f() {
return g(3);
}
f();
// 等同于
g(3);
上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除 f(x) 的调用帧,只保留 g(3) 的调用帧。
如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。
注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。
尾递归
函数尾调用自身。
function factorial(n) {
if (n === 1)
return 1;
return n * factorial(n - 1);
}
factorial(5) // 120
//改写为尾递归
function factorial(n, total) {
if (n === 1)
return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120
递归函数的改写
- 在尾递归之外,再提供一个正常形式的函数。
function tailFactorial(n, total) { if (n === 1) return total; return tailFactorial(n - 1, n * total); } function factorial(n) { return tailFactorial(n, 1); } factorial(5) // 120
- 采用ES6的函数默认值
function factorial(n, total = 1) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5) // 120
仅在严格模式下开启
在正常模式下,函数内部有两个变量,可跟踪函数的调用栈。而尾调用优化发生时,函数的调用栈会进行改写,这两个变量会被禁用,所以尾调用优化仅在严格模式下生效。
- function.arguments:返回调用时函数的参数
- function.caller:返回调用当前函数的那个函数
function restricted() {
"use strict";
restricted.caller;
restricted.arguments;
}
restricted();
//Error: Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
正常模式下尾递归实现
以1开始加1加100000次为例
//递归实现
function sum(start, num){
//start 开始累加的数
//num 累加次数
if(num > 0){
return sum(start + 1, num -1);
}else{
return start;
}
}
sum(1,100000);
//Output: Uncaught RangeError: Maximum call stack size exceeded
//将递归转为循环执行
function trampoline(f){
while(f && f instanceof Function){
f = f();
}
//返回一个函数,然后执行该函数
return f;
}
// 将原来的递归函数改写为每一步返回另一个函数
function sum(start, num){
if(num > 0){
return sum.bind(null, start + 1, num - 1);
} else {
return start;
}
}
//不会发生调用栈溢出
trampoline(sum(1, 100000));
//尾递归实现——————————————————————————
function tco(f){
var value;
var active = false;
var accumulated = [];
return function accumulator() {
accumulated.push(arguments);
if(!active){
//进入尾递归优化过程,激活active
active = true;
//每次循环以当前的this作为函数的this环境,以数组当前的首个元素为参数的新函数。
while(acuumulated.length){
value = f.apply(this, accumulated.shift());
}
//休眠
active = false;
//返回这个新函数
return value;
}
};//end return
}
var sum = tco(function(x,y) {
if( y> 0){
return sum(x + 1,y - 1);
}
else {
return x;
}
}
sum(1, 100000);
整理参考地址