GVKun编程网logo

Java中的尾调用优化(尾调用 js)

11

在这篇文章中,我们将为您详细介绍Java中的尾调用优化的内容,并且讨论关于尾调用js的相关问题。此外,我们还会涉及一些关于6-1java中的面相对象;使用包管理java中的类;java中的访问修饰符;

在这篇文章中,我们将为您详细介绍Java中的尾调用优化的内容,并且讨论关于尾调用 js的相关问题。此外,我们还会涉及一些关于6-1 java中的面相对象;使用包管理java中的类;java中的访问修饰符;java中的this关键字、c# – 没有等待的尾调用异步、ES6被你忽略的尾调用、ES6躬行记(15)——箭头函数和尾调用优化的知识,以帮助您更全面地了解这个主题。

本文目录一览:

Java中的尾调用优化(尾调用 js)

Java中的尾调用优化(尾调用 js)

从Java
8开始,Java不提供尾叫优化(TCO)。经过研究,我知道原因是:

在JDK类中,有许多对安全性敏感的方法,它们依赖于对JDK库代码和调用代码之间的堆栈帧进行计数,以确定谁在调用它们。

但是,基于JVM的Scala支持尾叫优化。Scala在编译时进行尾递归优化。Java为什么不能使用相同的方法?

PS:不确定Java的最新版本(截至现在的Java 11)是否具有TCO。如果知道的人也可以分享这一点,那就太好了。

笔记:

  1. 我知道TCO处于积压状态,并且优先级较低,但是想知道为什么Java无法像Scala那样在编译时进行更改。

  2. Java没有尾调用优化,原因与大多数命令式语言没有的原因相同。命令式循环是该语言的首选样式,程序员可以用命令式循环代替尾部递归。(来源)

6-1 java中的面相对象;使用包管理java中的类;java中的访问修饰符;java中的this关键字

6-1 java中的面相对象;使用包管理java中的类;java中的访问修饰符;java中的this关键字

1.

1.面向对象三大特性:封装,继承,多态

2.封装的实现步骤:

2.使用包管理java中的类

3.java中的访问修饰符

4.java中的this关键字

 

 

 

 

 

c# – 没有等待的尾调用异步

c# – 没有等待的尾调用异步

采用这种异步方法:

public async Task<string> ReadStringFromUrlAsync(string url)
{
    WebRequest request = WebRequest.Create(url);
    WebResponse response = request.GetResponse();
    Stream dataStream = response.GetResponseStream();
    var reader = new StreamReader(dataStream);
    return await reader.ReadToEndAsync();
}

因为它返回另一个任务的结果,我相信我可以取消异步并等待:

public Task<string> ReadStringFromUrlAsync(string url)
{
    WebRequest request = WebRequest.Create(url);
    WebResponse response = request.GetResponse();
    Stream dataStream = response.GetResponseStream();
    var reader = new StreamReader(dataStream);
    return reader.ReadToEndAsync();
}

我注意到在异常事件中调用堆栈不会提到ReadStringFromUrlAsync,以这种方式删除尾部调用上的aysnc / await是否有任何其他缺点.

解决方法

在大多数情况下都很好,但是异常行为有一个重要的区别:返回任务之前的任何异常都将直接抛出而不是放在任务上.在您的示例中,如果WebRequest.Create,GetResponse,GetResponseStream或StreamReader(..)抛出,则在异步方法中将异常放在任务上,而在非异步方法中则异常将直接抛出.

顺便说一句,如果那是你真实代码的代表,我建议改用HttpClient,它的设计考虑到了:

public Task<string> ReadStringFromUrlAsync(string url)
{
  return new HttpClient().GetStringAsync(url);
}

ES6被你忽略的尾调用

ES6被你忽略的尾调用


title: 被你忽略的‘尾调用’
date: 2017-05-02 16:52:22

tags: [ES6,javascript]

尾调用是什么?

在ES6有一个新特性:尾调用
用最简单的一句话描述就是‘某个函数的最后一步再调用另一个函数’,听起来挺简单的,
但是它的功能特别强大,直接给你撸个例子吧。

function a(x) {
  return b(x);
}

这个函数的最后一步再调用另一个函数,这就是尾调用。
以下几点不属于尾调用

  1. 在调用函数b之后还存在赋值的操作

function a(x) {
    let m = b(x);
    return m;
}
  1. 返回的那个函数没有加return

function a(x) {
    b(x);
}
  1. 在调用之后还存在其他的赋值操作

function a(x) {
    return b(x) - 2;
}

尾调用优化

函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置
和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用记录上方,还会形成
一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。如果函数B
内部还调用函数C,那就还有一个C的调用记录栈,以此类推。所有的调用记录,就形成一个
"调用栈"

来个例子吧

function a() {
  let p = 2;
  let q = 3;
  return b(p + q)
}
a();

仔细观察上面的代码就会发现啊a函数似乎是多余的吧,因为b函数是尾调用函数,执行到
这里函数a早就结束了,完全可以删除a函数了只保留b的调用帧即可。
尾调用优化:就是只保留内层函数的调用帧,如果所有函数都是尾调用,那么完全可以
做到每次执行时调用帧只能有一项,将大大节省内存,也就是尾调用的意义所在了。
注意的一点就是内层函数运用了外层函数的变量便不能进行尾调用优化了。记住哦!

尾递归

顾名思义,在一个尾调用中,如果函数最后的尾调用位置上是这个函数本身,则被称为尾递
归。递归很常用,但如果没写好的话也会非常消耗内存,导致爆栈。但是对于尾递归来说只
存在一个调用栈,便永远不会发生“栈溢出”错误。
就以求一个给出数的阶乘来探索吧。在传统的做法中利用n的递减乘上原函数,这样复杂度
便会很高,数据量一大便会发生“栈溢出”的错误了。

传统的解决办法

function f(n) {
  if(n === 1) {
    return 1;
  }
  return n * f(n -1);
}

尾调用

function a(n, t) {
  if(n === 1) {
    return t;
  }
  return a(n - 1, n * t)
}

对比两个解决办法的复杂度就知道,尾调用只保留了一个记录。

ES6躬行记(15)——箭头函数和尾调用优化

ES6躬行记(15)——箭头函数和尾调用优化

一、箭头函数

  箭头函数(Arrow Function)是ES6提供的一个很实用的新功能,与普通函数相比,不但在语法上更为简洁,而且在使用时也有更多注意点,下面列出了其中的三点:

(1)由于不能作为构造函数,因此也就没有元属性(new.target)和原型(prototype属性)。

(2)函数体内不存在arguments、super和this,即没有为它们绑定值。

(3)当需要包含多个参数时,它们的名称不可重复。

1)语法

  箭头函数由三部分组成:箭头(=>)、参数和函数体,省略了名称和function关键字,如下代码所示。注意,参数和箭头要在一行中,不能换行,并且没有箭头函数声明,只有箭头函数表达式。

var arrow1 = (name, age) => {
  return name;
};

  如果函数体中只包含一个表达式,那么可以像下面这样省略花括号和return关键字。

var arrow2 = (name, age) => name;

  当返回值是一个对象字面量时,需要为其包裹圆括号(如下代码所示),以免将花括号解析成代码块而引起语法错误。

var arrow3 = (name, age) => ({name: name, age: age});

  如果只有一个参数,那么可以省略包裹参数的圆括号。

var arrow4 = name => name;

  在参数缺失的情况下,还是需要一对空的圆括号占位。

var arrow5 = () => "strick";

  箭头函数也支持ES6新增的参数解构、剩余参数和默认参数,依次如下所示。

var arrow6 = ({name, age}) => name + age;
var arrow7 = (name, ...args) => name + args[0];
var arrow8 = (name = "strick") => name;

  箭头函数很适合那些只需做简短计算的函数,例如数组排序的回调函数。下面代码的第一段采用了普通的匿名函数,第二段采用了箭头函数,可以看出第二段的代码来得清晰简洁,更有吸引力。

var list1 = [1, 2, 3].sort(function(a, b) {
  return a < b;
});
var list2 = [1, 2, 3].sort((a, b) => a < b);

  虽然箭头不是运算符,但是当把它与其它运算符结合的时候,需要将其用圆括号隔离,否则会引起语法错误。

var operator1 = 0 || () => 2;        //语法错误
var operator2 = 0 || (() => 2);     //() => 2
var operator3 = 0 || (() => 2)();   //2

  上面代码的第一句是错误的写法;第二句中的箭头函数默认是不会自动执行的,因此或运算的结果是函数本身;第三句在箭头函数的末尾又加了一对圆括号,从而创建了立即执行函数表达式(IIFE),运算后的结果是2。

  普通函数能创建两种即时函数(如下代码所示),区别就是末尾的那对圆括号的位置,相对于函数是在内还是在外,而箭头函数只支持第一种写法的即时函数。

(function() {})();        //第一种
(function() {}());        //第二种

2)this

  普通函数都会包含一个this对象,而this绑定的对象只取决于函数的调用方式。在下面的代码中,有两个函数,分别是构造函数people()以及定时器的回调函数。

function people() {
  var self = this;
  this.age = 28;
  setTimeout(function() {
    this.age;         //undefined
    self.age;         //28
  }, 100);
}
new people();

  people()中的this通过构造函数绑定,指向了新创建的目标对象。而回调函数中的this通过默认绑定,指向了全局对象(window)。由于age属性只存在于构造函数中,因此在全局对象中是访问不到的。如果要在定时器的回调函数中访问age属性,那么需要为this赋予一个别名(例如代码中的self)。

  前面曾提到过,箭头函数中不包含this,不过在其内部还是能使用this的,这个this是从上一层的作用域中继承而来的。下面将构造函数people()改造成animal(),定时器采用箭头函数。

function animal() {
  this.age = 28;
  setTimeout(() => {
    this.age;         //28
  }, 100);
}
new animal();

  此时,箭头函数中的this指向的是新创建的目标对象,因此能成功访问到age属性。

  普通函数可以通过bind()、apply()或call()方法来更改this的指向,但这不适用于箭头函数,如下代码所示。还是以animal()为例,在箭头函数上调用call(),虽然第一个参数传入了全局对象,但依然无法改变this的指向。

function animal() {
  this.age = 28;
  setTimeout((() => {
    this.age;         //28
  }).call(window), 100);
}
new animal();

二、尾调用优化

  尾调用(Tail Call)是指函数中的最后一步会调用一个函数,并将其结果作为返回值,就像下面这两个函数。

function tail1() {
  return digit(28);
}
function tail2(name) {
  if (name == "strick")
    return digit(28);
  return digit(30);
}

  如果在最后一步调用的是自身,那么这种情况可称为尾递归(Tail Recursion)。下面是个会无限递归的函数,只是为了做演示用。

function recursion() {
  return recursion();
}

  尾调用不是一个新语法,而是一条组织代码的约束。下面代码里的三种情况就都不符合尾调用的要求。

function error1() {
  digit(28);
}
function error2() {
  return digit(28) + 1;
}
function error3() {
  var number = digit(28);
  return number;
}

  error1()函数缺少return关键字;error2()函数在调用digit(28)之后还额外多做了一次加法运算;error3()函数没有直接返回digit(28)的结果,而是先赋给一个变量,再返回。

  接下来为了能更好的理解尾调用优化,会先讲解调用栈的基础知识。

1)调用栈

  简单地说,调用栈(Call Stack)是一种记录函数地址的栈,当调用一个函数时,会在调用栈的顶端添加一个新的栈帧(Stack Frame),即入栈;而当函数执行完毕后,就会将其移出调用栈,即出栈。

  正常情况下,调用栈能够保持有进有出的平衡,但当函数的调用次数激增时(例如过多的递归),调用栈就会消耗大量内存,甚至占满整个内存空间,从而导致栈溢出(Stack Overflow)。不过值得庆幸的是,现代浏览器都不会因为栈溢出而奔溃,其内部已做了限制,例如Chrome仅仅会给出“RangeError: Maximum call stack size exceeded”的错误警告。

2)尾调用优化

  为了控制栈帧的数量,减少内存空间的使用,引入了尾调用优化(Tail Call Optimization,TCO)。这也不是一个新语法,只是一种空间上的优化,并且所有的工作都由JavaScript引擎(例如V8、SpiderMonkey等)代劳了。

  ES6中的TCO只有在严格模式下才能开启,它的原理就是不需要记住尾调用所处函数的地址。下面是一个检测字符串回文的函数,当开启TCO时,由于函数内需要的值已通过参数的形式传递进来(即不依赖函数外部的变量),因此每次递归都会覆盖前一次函数调用的栈帧,即调用栈的数量会保持恒定。

"use strict";
function palindrome(str) {
  if (str.length <= 1)
    return true;
  if (str[0] != str[str.length - 1])
    return false;
  return palindrome(str.substr(1, str.length - 2));
}

  虽然TCO的表现很抢眼,但很遗憾,目前除了Safrai之外,大部分浏览器暂不支持(可参考ES6浏览器兼容性),因此在这些浏览器中还享受不到TCO带来的性能提升。

原文出处:https://www.cnblogs.com/strick/p/10290638.html

我们今天的关于Java中的尾调用优化尾调用 js的分享就到这里,谢谢您的阅读,如果想了解更多关于6-1 java中的面相对象;使用包管理java中的类;java中的访问修饰符;java中的this关键字、c# – 没有等待的尾调用异步、ES6被你忽略的尾调用、ES6躬行记(15)——箭头函数和尾调用优化的相关信息,可以在本站进行搜索。

本文标签: