函数参数的默认值

    function log(x, y) {
y = y || 'world'
console.log(x + ' ' + y);
}
log('hello') // hello world
log('hello','China') // hello China
log('hello', '') // hello world
/*
* 如果 y 没有赋值,指定默认值为world // log('hello') // hello world
* 如果 y 有赋值,指定赋值 log('hello','China') // hello China
* 如果 y 有赋值,但是为 boolean 值 false,则该赋值就不起作用了 log('hello', '') // hello world
* */

函数的length属性

console.log((function (a) {}).length) //
console.log((function (a = 1) {}).length) // 0
console.log((function (a, b = 1, c) {}).length) //
console.log((function (a, b , c = 2) {}).length) //
// 返回没有赋值形参的个数
// 如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数

作用域

赋值的形参形成单独的作用域

rest参数

ES6 引入rest参数,(形式为“...变量名”),用于获取函数的多余参数,这样就不需要arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入其中
    function add(...value) {
let sum = 0
for(let val of value) {
sum += val
}
return sum
}
const NUM = add(2, 5, 8, 90)
console.log(NUM)

箭头函数

var f = v => v;
// 如果见箭头函数不需要参数 或者 需要对个参数,就是用圆括号代表参数部分
var sum = (num1, num2) => num1 + num2
// 如果箭头函数的代码块部分多余一条语句,就要使用大括号将其阔起来,
// 并使用return语句返回
// 如果箭头函数直接返回一个对象,必须在对象外面加上括号
var getTempItem = id => ({ id: id, name: "Temp"}) // 箭头函数 注意事项
/*
* 函数体内的this对象就是定义时所在的对象,而不是使用时所在的队形
* 不可以当做构造函数
* 不可以使用arguments对象
* 不可以使用yield命令,因此箭头函数態用作Generator函数
* 箭头函数可以让this指向固定化,这种形式非常有利于封装回调函数
* 箭头函数可以绑定this,大大减少了显示绑定this对象的写法(call,apply,bind)*/

《ES6标准入门》 第三版

箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。

箭头函数可以让this指向固定化,这种特性很有利于封装回调函数。

绑定this

箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。

但是,箭头函数并不适用于所有场合,所以现在有一个 提案,提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。

函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对 象),绑定到右边的函数上面。

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

尾调用及优化

就是指某个函数的后一步是调用另一个函数。

函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。

如果在函数A的内部调用函数B,那 么在A的调用帧上方,还会形成一个B的调用帧。

等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一 个C的调用帧,

以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。

尾调用由于是函数的后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用 帧,取代外层函数的调用帧就可以了。

只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有 一项,这将大大节省内存。这就是“尾调用优化”的意义。
注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

尾递归及优化

函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧, 所以永远不会发生“栈溢出”错误。

/* ----------- 普通递归 ------------------ */
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
} let num1 = factorial(5)
console.time(1); //设置时间起点
console.log(num1); //
console.timeEnd(1); // 3.450ms
/* 上面代码是一个阶乘函数,计算n的阶乘,多需要保存n个调用记录,复杂度 O(n) 。 */ /* ----------- 尾递归 ------------------ */
/* 如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。 */
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
} let num2 = factorial(5, 1) //
console.time(1); //设置时间起点
console.log(num2); //
console.timeEnd(1); // 0.275ms

ES6 中只要使用尾递归,就不会发生栈溢出,相对节省内存。

/* 非尾递归的 费氏数列 Fibonacci */
function Fibonacci(n) {
if (n <= 1) {
return 1
};
return Fibonacci(n - 1) + Fibonacci(n - 2);
} // let num21 = Fibonacci(10) // 573147844013817200000
// console.time(1); //设置时间起点
// console.log(num21); // 89
// console.timeEnd(1); // 4.030ms // let num22 = Fibonacci(100) // 573147844013817200000
// console.time(2); //设置时间起点
// console.log(num22); // 堆栈溢出
// console.timeEnd(2); // 4.堆栈溢出 // let num23 = Fibonacci2(1000) // 7.0330367711422765e+208
// console.time(3); //设置时间起点
// console.log(num23); // 堆栈溢出
// console.timeEnd(3); // 堆栈溢出 /* 尾递归优化过的 Fibonacci 数列实现如下 */
function Fibonacci2(n, ac1 = 1, ac2 = 1) {
if (n <= 1) {
return ac2
};
return Fibonacci2(n - 1, ac2, ac1 + ac2);
} // let num21 = Fibonacci2(10) // 573147844013817200000
// console.time(1); //设置时间起点
// console.log(num21); // 89
// console.timeEnd(1); // 3.469ms // let num22 = Fibonacci2(100) // 573147844013817200000
// console.time(2); //设置时间起点
// console.log(num22); // 573147844013817200000
// console.timeEnd(2); // 4.511ms // let num23 = Fibonacci2(1000) // 7.0330367711422765e+208
// console.time(3); //设置时间起点
// console.log(num23); // 7.0330367711422765e+208
// console.timeEnd(3); // 4.053ms /* ES6 是如此,第一次明确规定,所有 ECMAScript 的实 现,都必须部署“尾调用优化”。这就是说,ES6 中只要使用尾递归,就不会发生栈溢出,相对节省内存。 */
/* 尾递归的实现,往往需要改写递归函数,确保后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。 */

function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
} function factorial(n) {
return tailFactorial(n, 1);
} factorial(5) // // 上面代码通过一个正常形式的阶乘函数factorial,调用尾递归函数tailFactorial,看起来就正常多了。
// 函数式编程有一个概念,叫做柯里化(currying),意思是将多参数的函数转换成单参数的形式。这里也可以使用柯里化。
function currying(fn, n) {
return function (m) {
return fn.call(this, m, n);
};
} function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
} const factorial = currying(tailFactorial, 1); factorial(5) //

ES6 之 函数的扩展 尾调用以及尾递归的更多相关文章

  1. javascript专题系列--尾调用和尾递归

    最近在看<冴羽的博客>,讲真,确实受益匪浅,已经看了javascript 深入系列和专题系列的大部分文章,可是现在才想起来做笔记.所以虽然很多以前面试被问得一脸懵逼的问题都被“一语惊醒梦中 ...

  2. ES6入门——函数的扩展

    1.函数参数的默认值 在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法.现在ES6可以为函数的参数添加默认值,简洁了许多. ES5 function show(a,b){ b = b ...

  3. 【ES6】函数的扩展

    1.函数参数默认值[详情例子参照ESMAScript 6入门 (阮一峰)] 允许为函数的参数设置默认值,即直接写在参数定义的后面.[例子1] 参数变量是默认声明的,所以不能用let或const再次声明 ...

  4. ES6中函数的扩展

    一.设置默认参数 ES6之前,给函数设置默认参数是这样做的: function fn(a) { if(typeof y === undefined){ a = a || 'hello'; } cons ...

  5. es6(三):es6中函数的扩展(参数默认值、rest参数、箭头函数)

    1.函数可以设置参数默认值 function test1(x,y=1){ console.log(x,y) } test1(10)//10 1 2.rest参数:形式为...变量名 function ...

  6. ES6 入门系列 - 函数的扩展

    1函数参数的默认值 基本用法 在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法. function log(x, y) { y = y || 'World'; console.log( ...

  7. ES6的新特性(7)——函数的扩展

    函数的扩展 函数参数的默认值 基本用法 ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法. function log(x, y) { y = y || 'World'; console ...

  8. ES6学习笔记(6)----函数的扩展

    参考书<ECMAScript 6入门>http://es6.ruanyifeng.com/ 函数的扩展 函数的默认值 : ES6可以为函数指定默认值 (1)指定默认值的两种方式 a.函数参 ...

  9. ES6学习笔记(五)函数的扩展

    1.函数参数的默认值 1.1基本用法 ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法. function log(x, y) { y = y || 'World'; console. ...

随机推荐

  1. 利用 vuex 实现一个公用搜索器

    安装 npm i vuex vuex 的使用 先创建好如图所示的文件: 编写 modules 下的 params.js const param = { state: { params: {} }, m ...

  2. 嵊州普及Day1T2

    题意:走迷宫.求走到a[n][n]需要多久. 考场上想的dfs,听老师说最多50分.代码懒得码了,知道是走迷宫就好. 正解:bfs,时间复杂度O(n). 见代码: #include<iostre ...

  3. platform设备驱动框架

    驱动框架 通过使用platform设备驱动框架,实现led驱动与设备操作的分离.     我们关注led_drv里面的 struct platform_driver led_drv里面的.probe函 ...

  4. MQTT 协议学习:005-发布消息 与 对应报文 (PUBLISH、PUBACK、PUBREC、PUBREL)

    背景 当有订阅者订阅了有关的主题以后,通过发布消息的消息的动作,可以让订阅者收到对应主题的消息. 根据不同的QoS 等级,通信的动作也略有不同. PUBLISH – 发布消息 报文 PUBLISH控制 ...

  5. 从0开始自己配置一个vps虚拟服务器(1)

    我前几年买的虚拟机都被我荒废了,我已经配置过很多遍了,但是从来没有真的用过.因为我前几个月之前又新买了一个便宜的服务,准备写新的东西.供应商pacificrack,真的很烂,一直断,控制面板还打不开, ...

  6. Golang的基础数据类型-字符型

    Golang的基础数据类型-字符型 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.字符型概述 Go语言中的字符有两种,即uint8类型和rune类型. uint8类型: 我们也 ...

  7. leetcode(数据结构)—— 镜像二叉树

    镜像二叉树,力扣上面的的题目,这道题很简单,放出来的原因是它要求用两种解法来写这道题——递归和迭代,而且数据结构学到了树,记录自己学习的过程,以免忘了,没地方找. 题目的意图很明显,就是然你写个程序看 ...

  8. Spring Boot2(007):关于Spring beans、依赖注入 和 @SpringBootApplication 注解

    一.关于Spring beans 和 依赖注入(Dependency Injection) spring boot 和 Spring 全家桶无缝衔接,开发过程中可以很轻松地使用 Spring 全家桶的 ...

  9. 在Windows中安装vim

    这篇文章主要教大家如何在Windows系统下安装最好用的编辑器VIM 来自百度百科的介绍: Vim是一个类似于Vi的著名的功能强大.高度可定制的文本编辑器,在Vi的基础上改进和增加了很多特性. VIM ...

  10. RabbitMQ系列文章

    详解 RabbitMQ 管理界面解析框架 (一) RabbitMQ系列(二)深入了解RabbitMQ工作原理及简单使用 RabbitMQ windows安装步骤 RabbitMQ管理页面各种属性详解 ...