跳到主要内容

函数一等功臣

TODO

什么是尾调用优化和尾递归?

尾调用指函数的最后一步操作是调用另一个函数。

// 有的引擎可以做到类似以下的优化,简化了函数的调用栈
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();

// 等同于
function f() {
return g(3);
}
f();

// 等同于
g(3);
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

"尾调用优化"对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6也是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署"尾调用优化"。这就是说,在 ES6 中,只要使用尾递归,就不会发生栈溢出,相对节省内存。

ES6箭头函数的特性

ES6 增加了箭头函数,基本语法为 let func = value => value; 相当于

let func = function (value) {
return value;
};

箭头函数与普通函数的区别在于: 1、箭头函数没有 this,所以需要通过查找作用域链来确定 this 的值,这就意味着如 果箭头函数被非箭头函数包含,this 绑定的就是最近一层非箭头函数的 this, 2、箭头函数没有自己的 arguments 对象,但是可以访问外围函数的 arguments 对象 3、不能通过 new 关键字调用,同样也没有 new.target 值和原型

arguments

arguments 是类数组对象,有 length 属性,不能调用数组方法 可用 Array.from()转换

箭头函数获取arguments

可用…rest 参数获取

使用箭头函数(arrow functions)的优点是什么?

说说async函数

  1. 什么是 Async 函数?

Async 函数是一种异步函数,它使用 async 关键字在函数前进行修饰。它可以以一种更简单和直观的方式编写异步代码。Async 函数的返回值是一个 Promise 对象,可以使用 then 和 catch 处理。

  1. Async 函数与 Promise 之间有什么区别?

Async 函数是 Promise 的一种语法糖,它提供了一种更简单和直观的方式来编写异步代码。Promise 是一个对象,它代表一个异步操作的最终结果,可以使用 then 和 catch 处理。

  1. 如何在 Async 函数中使用 await?

在 Async 函数中,可以使用关键字 await 来等待一个 Promise 对象的解决。当遇到 await 表达式时,Async 函数会暂停执行,直到 Promise 对象被解决。

  1. Async 函数的返回值是什么类型?

Async 函数的返回值是一个 Promise 对象。它可以是解决的 Promise 对象,也可以是拒绝的 Promise 对象,具体取决于 Async 函数的执行结果。

  1. 在 Async 函数中可以使用哪些其他异步函数?

在 Async 函数中可以使用所有返回 Promise 对象的异步函数,例如 fetch、setTimeout、setInterval 等等。

  1. Async 函数在什么情况下会抛出异常?

当 Async 函数中的任何一条语句抛出异常时,该函数将立即停止执行并返回一个拒绝的 Promise 对象。这个 Promise 对象将包含抛出的错误对象。

  1. 在什么情况下我们应该使用 Async 函数?

通常情况下,当需要处理异步事件时,我们应该使用 Async 函数,因为 Async 函数提供了一种更直观和更易于理解的方式来编写异步代码。使用 Async 函数可以大大简化异步代码的编写和调试。

如果 new 一个箭头函数的会怎么样

在 JavaScript 中,箭头函数是通过函数字面量定义的,它们没有构造函数。因此,无论如何使用 new 关键字来调用箭头函数,都会导致错误。

当你尝试使用 new 关键字来实例化一个箭头函数时,会抛出一个 TypeError 错误。这是因为箭头函数没有自己的 this 绑定,它们继承了父作用域的 this 值,并且无法被改变。使用 new 关键字来调用函数时,会创建一个新的对象,并将函数的 this 绑定到该对象上,但对于箭头函数来说,无法绑定新的 this,因此会导致错误。

以下是一个例子,展示了使用 new 关键字实例化箭头函数时会发生的错误:

const ArrowFunc = () => {
this.name = 'John'; // 错误:箭头函数没有自己的 this
};

const obj = new ArrowFunc(); // TypeError: ArrowFunc is not a constructor

总结来说,箭头函数不适用于被实例化,它们更适合作为匿名函数或用于简化函数语法。如果你需要创建可实例化的函数,应该使用常规的函数声明或函数表达式。

new 操作符的实现步骤

在 JavaScript 中,使用 new 操作符来创建一个对象的实例时,会经历以下步骤:

  1. 创建一个空对象:首先,一个新的空对象会被创建。

  2. 设置原型链连接:新创建的对象会被设置其原型链([[Prototype]])指向构造函数的原型对象(Constructor.prototype)。

  3. 将构造函数的作用域赋给新对象:this 关键字会被绑定到新对象上,使构造函数内部的代码可以访问和操作新对象。

  4. 执行构造函数代码:构造函数会被执行,通过 this 关键字来对新对象进行属性赋值、方法定义等操作。

  5. 返回新对象:如果构造函数中没有显式返回其他对象,则 new 表达式会自动返回新创建的对象实例;否则,返回构造函数中显式返回的对象。

下面是一个简单的示例,演示了 new 操作符的实现步骤:

function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
};

const john = new Person('John', 25);
john.sayHello(); // 输出: Hello, my name is John and I'm 25 years old.

在上述示例中,new Person('John', 25) 执行过程如下:

  1. 创建一个空对象 john

  2. Person.prototype 设置为 john 的原型链。

  3. 将构造函数 Person 的作用域赋给 john,使 this 关键字绑定到 john

  4. 执行构造函数内部的代码,将属性 nameage 赋值给 john

  5. 返回 john 对象。

这样,我们通过 new 操作符创建了一个 Person 的实例,并可以使用该实例调用构造函数原型对象上的方法。

什么是尾调用,使用尾调用有什么好处?

尾调用(Tail Call)是指函数在执行的最后一步调用另一个函数。具体来说,尾调用发生在函数的最后一个操作中,并且没有其他操作需要执行。尾调用可以出现在函数的返回语句中,或者是函数调用表达式的最后一部分。

尾调用优化(Tail Call Optimization)是一种优化技术,旨在在函数调用时减少内存的使用和栈的增长。当一个函数调用发生在另一个函数的尾调用位置时,可以将当前函数的调用帧替换为被调用函数的调用帧,而不会增加栈的深度。这样可以避免栈溢出错误,同时减少了内存的使用。

尾调用优化的好处包括:

  1. 减少内存占用:由于不需要保存每个函数调用的上下文,尾调用优化可以减少内存的使用。这对于递归函数特别有用,因为递归往往会导致栈溢出错误。

  2. 提高性能:尾调用优化可以减少函数调用的开销,使得程序的执行更加高效。通过消除不必要的调用帧的创建和销毁过程,可以节省时间和资源。

  3. 简化调试和代码阅读:尾调用优化可以使调用栈更加简洁和清晰,使调试和代码阅读更加容易。因为在尾调用优化的情况下,调用栈只会保留最后一次函数调用的信息,而不会堆积大量的调用帧。

需要注意的是,并非所有的 JavaScript 引擎都对尾调用进行优化。一些引擎对尾调用进行了优化,而另一些引擎可能没有进行优化。因此,在编写代码时,不能依赖于尾调用优化,而应该使用其他方法来避免栈溢出问题,例如使用迭代替代递归。

总结起来,尾调用是指函数在执行的最后一步调用另一个函数。尾调用优化可以减少内存的使用和栈的增长,提高性能,并简化调试和代码阅读。尽管不是所有的 JavaScript 引擎都支持尾调用优化,但在递归函数等情况下,尽量避免栈溢出错误仍然是一个好的编程实践。

如何确保你的构造函数只能被new调用,而不能被普通调用?

function MyClass() {
// 确保通过 new 调用, 在ES6后建议只用 new.target来判断
if (!(this instanceof MyClass)) {
throw new Error('MyClass must be instantiated with new');
}

// 构造函数的其他代码...
}

// 正确的用法
const obj = new MyClass();

// 错误的用法
const obj2 = MyClass(); // 这里会抛出一个错误