前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >再看JavaScript,那些遗漏或易混淆的知识点(3)

再看JavaScript,那些遗漏或易混淆的知识点(3)

作者头像
踏浪
发布2021-06-09 20:03:57
7550
发布2021-06-09 20:03:57
举报
文章被收录于专栏:踏浪的文章

递归和堆栈

递归

递归,简单来说就是函数在 return 的时候调用了函数自身。比如这个例子:

代码语言:javascript
复制
function pow(x, n) {
	return n === 1 ? x : x * pow(x, n - 1);
}

功效与 Math.pow() 相同。

比如计算 pow(2, 4) ,递归经过下面几个步骤:

  1. pow(2, 4) = 2 * pow(2, 3)
  2. pow(2, 3) = 2 * pow(2, 2)
  3. pow(2, 2) = 2 * pow(2, 1)
  4. pow(2, 1) = 2

最大的嵌套调用次数(包括首次)被称为 递归深度。在上面的那个例子中,正好是 n

在 JavaScript 引擎中,最大递归深度会被受限。引擎在最大迭代深度是 10000 及以下时是可靠的,有些引擎可能允许更大的最大深度,但是对于大多数引擎来说, 100000 可能就超出限制了。所以,有一种尾递归的调用方式诞生了,但是目前还没有被完全支持,只能用于简单场景。

那什么是尾递归呢?

尾递归

尾递归中也包含递归这个词语,所以还是离不开递归。那么尾递归与普通递归有什么不同呢?

尾递归函数在 return 的时候除了常量,只会 return 自身函数,而不会 return 涉及到自身函数与其他函数或者常量的计算。

同样是上面的例子,如果 pow 函数 return 的只是 pow(x, n - 1) 而不包含 x * ,那么这就是一个尾递归函数。

那么与普通递归有什么不同呢?

普通递归函数因为涉及到了计算,所以会等最后一个深度的函数执行完成在回过来执行上一个函数,然后依次释放执行过的函数的内存空间,所以会存在最大深度的问题。

但是如果是尾递归,因为每只都是调用函数本事,不存在计算,所以,之前的函数不会占用内存空间,因而没有最大递归深度的概念。

代码语言:javascript
复制
// 递归
function pow1(x, n) {
  return n === 1 ? x : x * pow1(x, n - 1);
}

console.log(pow1(2, 4));

// 尾递归
function pow2(x, n, total = 1) {
  return n === 0 ? total : pow2(x, n - 1, x * total)
}

console.log(pow2(2, 4));

如果 n 的值足够大,你会发现还是会报出栈溢出错误,并没有进行尾调用优化。因为上面说了,目前浏览器还没有完全支持这个方法。不过虽然没有支持,但是这种方法的调用也比普通递归好上一点。因为尾递归把时间复杂度从 o(n) 降低到了 o(1)

如果想要了解哪些语言支持尾递归,可以自行上 Google Search。

执行上下文和堆栈

递归函数在调用的时候为什么会存在 栈溢出 的情况?就是因为递归函数在执行的时候都是先执行的都是没有被计算的,仅仅只是保留在执行上面文中,等待后面的计算完成在返回来计算之前的。例如上面的例子

代码语言:javascript
复制
function pow1(x, n) {
  return n === 1 ? x : x * pow1(x, n - 1);
}
pow1(2, 4)

在执行的时候,这样堆栈:

  • Context: {x: 2, n: 4, at: line 1} 调用 pow(2, 4)
  • Context: {x: 2, n: 3, at: line 2} 调用 pow(2, 3)
  • Context: {x: 2, n: 2, at: line 2} 调用 pow(2, 2)
  • Context: {x: 2, n: 1, at: line 2} 调用 pow(2, 1) 接着是 n === 1 的时候,找到了出口 return x 。开始出栈

new Function ???

这种语法很少用到,估计在有限的程序员生涯也不会用到这种语法吧。但是很有趣,所以这里写写。

创建函数的语法:

代码语言:javascript
复制
let func = new Function ([arg1, arg2, ...argN], functionBody);

最后一个函数是函数体,如果只有一个参数,那他就是函数体。

代码语言:javascript
复制
let sum = new Function('a', 'b', 'return a + b');
// 或者 let sum = new Function('a, b', 'return a + b');

console.log( sum(1, 2) ); // 3

只有一个参数(即函数体):

代码语言:javascript
复制
let sayHi = new Function('alert("Hello")');

sayHi(); // Hello

还以一个问题,使用这种当时创建的函数,只能访问全局变量

代码语言:javascript
复制
function getFunc() {
  let value = "test";
  let value2 = "test2";

  let func = new Function('console.log(value)');
  let func2 = function() {console.log(value2)};

  return {
    func,
    func2
  };
}

getFunc().func2(); // test2
getFunc().func(); // error: value is not defined

调度:setTimeout 与 setInterval

这两个函数都不陌生,平时也都用的挺多的。尤其是在 requestAnimationFrame 不是特别友好的时候,常常用来写动画效果。

按照我之前的写法

代码语言:javascript
复制
function say() {
  alert('hello');
}

setTimeout(say, 1000);

上面的代码在一秒中以后执行,没有丝毫问题。上面的函数是没有参数的情况,其实还有很多的时候所需要执行的函数是带有参数的。如果带有参数,我一般是这样做的:

代码语言:javascript
复制
function sayHi(phrase, who) {
  alert( phrase + ', ' + who );
}

setTimeout(() => {
  sayHi("Hello", "John")
}, 1000); // Hello, John

其实还有另一种写法,来看看 setTimeout 的语法吧:

代码语言:javascript
复制
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)

// setInterval 一样的

第一个参数可以是一段代码,但是不建议这样做了。第二哥参数是延时时间。以前我就以为只有两个参数。从上面的语法中可以看出,其实还有很多参数的。这些参数就是所需执行的函数需要的参数,所以上面的代码还可以这样:

代码语言:javascript
复制
function sayHi(phrase, who) {
  alert( phrase + ', ' + who );
}

setTimeout(sayHi, 1000, "Hello", "John"); // Hello, John

这样看起来就好看多了。

第二点就是这两个函数的返回值,返回值是一个 timerID ,是一个 number 类型的值。

接下来就是对比以下两者的使用,用 setTimerout 模仿一个 setInterval

代码语言:javascript
复制
function func(s) {
  console.log(s)
}

setInterval(func, 100, 'setInterval'); // line1

setTimeout(function run() { // line2
  func('setTimeout')
  setTimeout(run, 100);
}, 100);

同样都是一秒的延迟,但是每一次都是 setInterval 先打印出来。如果将 line1line2 调换顺序,除了第一次是 setInterval 后执行,后面都是先执行了。为什么呢?因为 func 的执行所花费的时间“消耗”了一部分间隔时间。用图来解释:

诶。这就很明显了嘛。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-03-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 递归和堆栈
    • 递归
      • 尾递归
      • 执行上下文和堆栈
      • new Function ???
      • 调度:setTimeout 与 setInterval
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档