整理学习——ES6函数的扩展

参数默认值

参数变量默认声明,不能使用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"

作用域

  1. 先当前函数作用域,后全局作用域。
  2. 如果参数的默认值是一个函数,该函数的作用域是其声明时所在的作用域。
    let foo = 'outer';
    function bar(func = x => foo) {
    	let foo = 'inner';
    	console.log(func()); // outer
    }
    bar();
    //Output: outer
    //匿名函数func的foo因为在调用时内部的foo还未生成
    //所以调用的是外部的foo
    

应用

  1. 抛出一个不能省略的异常错误。
    function throwIfMissing() {
    	throw new Error('Missing parameter');
    }
    
    function foo(mustBeProvided = throwIfMissing()) {
    	return mustBeProvided;
    }
    
    foo()
    // Error: Missing parameter
    
  2. 将参数默认值设置为undefined,表示这个参数可以省略

rest参数

…val,扩展运算符+变量,用于获取函数的多余参数。
rest参数搭配的变量是个数组,多余的参数将放入这个数组中。
rest参数之后不能有其他参数,否则会报错。
length属性不包括rest数组

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

扩展运算符

  1. 将一个数组转为用逗号分隔的参数序列
    console.log(1,2,...[1,2,3],4)
    //Output: 1 2 1 2 3 4
    
  2. 用于函数调用
    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

扩展运算符的运用

  1. 合并数组
  2. 与解构赋值结合
  3. 函数参数中解构数组,将数组解构为参数
  4. 将字符串转为字符数组,可识别码点>0xFFFF的字符
  5. 将拥有iterator接口的对象转为数组。如果是一个类似数组的对象,可以使用Array.from()将其转为带有iterator接口的数组。
  6. Map和Set结构,Generator函数

严格模式

只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
原因是函数内部的严格模式,同时适用于函数体代码和函数参数代码。但是在函数执行的时候,先执行参数代码,然后再执行函数体代码。
当在函数体内添加了严格模式,使用了默认值、解构赋值或者扩展运算符的函数已经先执行了参数代码,等到执行函数体代码的时候才知道要使用严格模式执行参数代码,所以报错。

将函数包在一个无参数的立即执行函数里。

const doSomething = (function(){
	'use strict';
	return function(value = 42){
		return value;
	}
}())

箭头函数

var f = v => v;
//等价于
var f = function(v){
	return v;
}
  1. 如果箭头函数不需要参数或者需要多个参数,就使用一个圆括号代表参数部分。
  2. 如果箭头函数的代码块部分多于一条语句,就要使用大括号将他们括起来,并且使用return语句返回。
  3. 简化回调函数
    [1,2,3].map(function(d){return d + 1});
    //换成箭头函数
    [1,2,3].map(d=>d + 1);
    

使用注意

  1. 函数体内的this对象,就是定义所在的对象,而不是使用时所在的对象(即运行时所在的作用域)。(this指向是可变的,但是在这里是固定的。)

    箭头函数中不存在的指向外层函数的对应变量有:
    this、argument、super、new.target

  2. 不可用作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
  3. 不可使用argument对象,该对象在函数体内不存在。如果需要,使用rest参数代替
  4. 不可使用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);

整理参考地址


  1. λ演算:
    https://mp.weixin.qq.com/s?__biz=MzIwMDgyNzUzNw==&mid=2247483658&idx=1&sn=2dde89033d2fef0cba307db9b8b5489e&scene=0#wechat_redirect ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值