前端面试总结3

发布于:2025-04-11 ⋅ 阅读:(58) ⋅ 点赞:(0)

目录

1.尾调用

 尾调用优化的实际应用

 1. **避免栈溢出**

 2. **提高性能**

 3. **支持深层递归**

 4. **函数式编程的支持**

 5. **状态机模拟**

6. **异步任务调度**

2.async/await、Promise 和 setTimeout输出顺序

1. JavaScript 的事件循环机制

2. async/await、Promise 和 setTimeout 的区别

3. 执行顺序示例

1.代码示例(正常的)

输出

解析:

2.示例 1:多个 Promise 和 setTimeout

解析

示例 2:嵌套 async/await 和 Promise

解释

常见概念模糊点解析

3.AJAX、Axios 和 Fetch 的区别概述

1. AJAX 示例

2.Axios 示例

1.安装axios

2.js中代码示例

3.在 Vue 中的应用示例

4. 使用axios拦截器

3. Fetch 示例

js示例

vue代码示例

4.总结 


1.尾调用

尾调用优化(Tail Call Optimization, TCO)是一种编译器或解释器优化技术,用于减少函数调用时的栈空间消耗。当一个函数的最后一步是调用另一个函数时,编译器或解释器可以优化这一调用,使得当前函数的栈帧被复用,从而避免栈空间的持续增长。

 尾调用优化的实际应用

 1. **避免栈溢出**

在递归算法中,函数可能会频繁地自我调用,这会导致调用栈不断增长,最终可能引发栈溢出错误。尾调用优化可以有效避免这一问题。

**示例:阶乘计算**

function factorial(n) {
  if (n === 0) return 1;
  return n * factorial(n - 1); // 非尾调用
}
```

尾调用优化版本:

function factorial(n, acc = 1) {
  if (n === 0) return acc;
  return factorial(n - 1, n * acc); // 尾调用
}

通过引入累加器参数 `acc`,将递归调用改为尾调用,避免了栈溢出。

 2. **提高性能**

尾调用优化通过复用栈帧,减少了函数调用的开销,从而提升了程序的性能。

 3. **支持深层递归**

某些算法(如递归实现的斐波那契数列)在未优化的情况下,递归深度受栈空间限制,无法处理大规模输入。尾调用优化使得递归函数可以处理更深层次的调用。

**示例:斐波那契数列**

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}
```

尾调用优化版本:

function fibonacci(n, a = 0, b = 1) {
  if (n === 0) return a;
  return fibonacci(n - 1, b, a + b); // 尾调用
}


通过引入辅助参数,将递归调用改为尾调用,避免了栈溢出并提升了性能。

 4. **函数式编程的支持**

函数式编程语言(如 Scheme、Haskell 等)通常依赖递归而非循环来实现迭代逻辑。尾调用优化使得函数式编程语言能够高效地处理递归。

 5. **状态机模拟**

尾调用优化可以用于模拟状态机,通过递归调用实现状态转移,而不会导致栈溢出。

**示例:状态机**

function processState(state) {
  switch (state) {
    case 'start':
      console.log('Processing...');
      return processState('processing');
    case 'processing':
      console.log('Done!');
      return processState('done');
    case 'done':
      return state;
  }
}

console.log(processState('start')); // 输出:Processing... Done! done

通过尾调用优化,状态机可以无限递归而不会导致栈溢出。

6. **异步任务调度**

尾调用优化可以用于实现异步任务链式调用,通过递归调用实现任务的顺序执行。

**示例:异步任务链**

function asyncProcess(input, callback) {
  setTimeout(() => {
    callback(`Processed: ${input}`);
  }, Math.random() * 1000);
}

function pipeline(...fs) {
  let result = null;
  const execute = (index) => {
    if (index < fs.length) {
      asyncProcess(result, (r) => {
        result = r;
        execute(index + 1);
      });
    } else {
      console.log('Pipeline completed:', result);
    }
  };
  execute(0);
}

pipeline(
  (input) => input + ' -> Step 1',
  (input) => input + ' -> Step 2',
  (input) => input + ' -> Step 3'
);

通过尾调用优化,异步任务可以链式调用而不会导致栈溢出。

2.async/await、Promise 和 setTimeout输出顺序

1. JavaScript 的事件循环机制

1.在深入讨论之前,简要了解 JavaScript 的事件循环机制是必要的。JavaScript 是单线程的,但它通过事件循环机制来处理异步操作,主要包括以下几个部分:

​调用栈(Call Stack)​:用于跟踪函数的执行顺序。
​任务队列(Task Queue)​:存放宏任务(如 setTimeout 回调)。
​微任务队列(Microtask Queue)​:存放微任务(如 Promise 的回调、async/await 生成的回调)。
2.事件循环的基本流程是:

执行调用栈中的同步任务。
当调用栈为空时,先执行所有微任务队列中的任务。
然后从任务队列中取出一个宏任务执行。
重复上述过程。

2. async/await、Promise 和 setTimeout 的区别

  1. ​**Promise**:用于创建一个异步操作,它有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。then 和 catch 方法用于处理异步操作的结果,它们会被加入到微任务队列中。
  2. ​**async/await**:是基于 Promise 的语法糖,使得异步代码看起来更像同步代码。async 函数总是返回一个 Promise,而 await 会暂停函数的执行,直到等待的 Promise 被解决。
  3. ​**setTimeout**:用于在指定的时间后执行一个回调函数。无论延迟时间是多少,setTimeout 的回调都会被加入到宏任务队列中,等待当前调用栈和微任务队列清空后执行。

3. 执行顺序示例

1.代码示例(正常的)

console.log('开始');

setTimeout(() => {
    console.log('setTimeout');
}, 0);

Promise.resolve().then(() => {
    console.log('Promise 1');
}).then(() => {
    console.log('Promise 2');
});

async function asyncFunc() {
    console.log('asyncFunc 开始');
    await Promise.resolve();
    console.log('asyncFunc 结束');
}

asyncFunc();

console.log('结束');
输出

解析:

​1.同步代码执行:
console.log('开始'); 输出 开始。
遇到 setTimeout,将其回调函数加入宏任务队列,等待执行。
Promise.resolve().then(...) 创建一个立即解决的 Promise,其第一个 then 回调被加入微任务队2.列。
2.调用 asyncFunc():
输出 asyncFunc 开始。
await Promise.resolve(); 暂停 asyncFunc 的执行,并将后续代码(console.log('asyncFunc 结束');)包装成一个 Promise 的 then 回调,加入微任务队列。
输出 结束。
​3.执行微任务队列:
执行第一个 Promise 的 then 回调,输出 Promise 1。
该 then 回调又添加一个新的 then 回调到微任务队列。
执行 asyncFunc 中暂停后的回调,输出 asyncFunc 结束。
执行第二个 Promise 的 then 回调,输出 Promise 2。
​执行宏任务队列:
执行 setTimeout 的回调,输出 setTimeout。

2.示例 1:多个 Promise 和 setTimeout

代码示例

console.log('开始');

Promise.resolve().then(() => {
    console.log('Promise 1');
    setTimeout(() => {
        console.log('setTimeout 1');
    }, 0);
});

setTimeout(() => {
    console.log('setTimeout 2');
}, 0);

Promise.resolve().then(() => {
    console.log('Promise 2');
});

console.log('结束');

输出:

 

解析
  1. 同步代码输出 开始 和 结束。
  2. 两个 Promise 的 then 回调被加入微任务队列,依次执行,输出 Promise 1 和 Promise 2。
  3. 在 Promise 1 中,setTimeout 的回调被加入宏任务队列。
  4. 另外一个 setTimeout 的回调也被加入宏任务队列。
  5. 微任务队列清空后,执行宏任务队列中的回调,按它们被添加的顺序,输出 setTimeout 2 和 setTimeout 1。

示例 2:嵌套 async/await 和 Promise

代码示例

console.log('开始');

async function func1() {
    console.log('func1 开始');
    await Promise.resolve();
    console.log('func1 结束');
}

async function func2() {
    console.log('func2 开始');
    func1();
    await new Promise(resolve => setTimeout(resolve, 0));
    console.log('func2 结束');
}

func2();

Promise.resolve().then(() => {
    console.log('Promise 1');
});

setTimeout(() => {
    console.log('setTimeout');
}, 0);

console.log('结束');

输出

解释
  • 1.​同步代码执行:

console.log('开始'); 首先执行,输出 ​**"开始"**。
调用 func2(),进入 func2 函数:
输出 ​**"func2 开始"**。
调用 func1(),进入 func1 函数:
输出 ​**"func1 开始"**。
await Promise.resolve(); 将 func1 的剩余部分(console.log('func1 结束');)加入 ​微任务队列,并暂停 func1 的执行。
func1() 调用后,继续执行 func2:
await new Promise(resolve => setTimeout(resolve, 0)); 创建一个 ​宏任务​(setTimeout),并将其回调加入 ​宏任务队列,然后暂停 func2 的执行。

  • 2.继续执行同步代码:

Promise.resolve().then(() => { console.log('Promise 1'); }); 将回调加入 ​微任务队列。
setTimeout(() => { console.log('setTimeout'); }, 0); 将回调加入 ​宏任务队列​(注意,这个 setTimeout 的回调会在前面的 setTimeout 之后执行,因为它们都在宏任务队列中按顺序排列)。
输出 ​**"结束"**。

  • 3.​处理微任务队列:

当前调用栈已清空,事件循环开始处理 ​微任务队列:
执行 Promise 1 的回调,输出 ​**"Promise 1"**。
接下来执行 func1 中暂停的部分,输出 ​**"func1 结束"**。
微任务队列现已清空。

常见概念模糊点解析

  • 1.**Promise 构造函数 vs. Promise.resolve()**:

​**new Promise**:构造函数中的执行器函数是同步执行的,.then 回调会被加入微任务队列。
​**Promise.resolve()**:立即创建一个已解决的 Promise,其 .then 回调被加入微任务队列。
​**async/await 与 Promise 的关系**:

  • 2.async 函数总是返回一个 Promise。

await 会暂停函数的执行,并将后续代码作为微任务加入队列,等待 Promise 解决。

  • 3.​微任务队列的执行顺序:

微任务按照它们被添加到队列的顺序依次执行。
多个 Promise 的 .then 回调和 async/await 生成的回调都遵循这一规则。

  • 4.​**setTimeout 与 Promise 的交互**:

setTimeout 的回调是宏任务,会在当前调用栈和微任务队列清空后执行。
在 setTimeout 回调中添加的 Promise 回调会被加入新的微任务队列,等待当前宏任务完成后再执行。

  • 5.​嵌套 Promise 和 async/await:

嵌套的 Promise 和 async/await 会增加微任务队列的复杂性,需逐层跟踪每个异步操作的挂起点和恢复点。

3.AJAX、Axios 和 Fetch 的区别概述

1. AJAX 示例

function fetchJsonData(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);

        xhr.onload = function () {
            if (xhr.status >= 200 && xhr.status < 300) {
                try {
                    const data = JSON.parse(xhr.responseText);
                    resolve(data);
                } catch (error) {
                    reject(error);
                }
            } else {
                reject(new Error('AJAX 请求失败,状态码: ' + xhr.status));
            }
        };

        xhr.onerror = function () {
            reject(new Error('AJAX 请求发生错误'));
        };

        xhr.send();
    });
}

// 使用封装后的函数
const url = 'https://api.example.com/data';
fetchJsonData(url)
    .then(data => {
        console.log('AJAX 数据:', data);
    })
    .catch(error => {
        console.error('请求失败:', error);
    });

2.Axios 示例


Axios 是一个基于 Promise 的 HTTP 客户端,可以在浏览器和 Node.js 中使用。它提供了更简洁的 API,并内置了许多实用功能,如自动转换 JSON 数据、拦截请求和响应、取消请求等。

1.安装axios

npm install axios

2.js中代码示例


import axios from 'axios';

// 发起 GET 请求
axios.get('https://api.example.com/data')
  .then(response => {
    console.log('Axios 数据:', response.data);
  })
  .catch(error => {
    console.error('Axios 请求失败:', error);
  });

3.在 Vue 中的应用示例

<template>
  <div>
    <button @click="fetchData">获取数据</button>
    <div v-if="loading">加载中...</div>
    <div v-else>
      <pre>{{ data }}</pre>
    </div>
    <div v-if="error" class="error">{{ error }}</div>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      loading: false,
      data: null,
      error: null
    };
  },
  methods: {
    fetchData() {
      this.loading = true;
      this.error = null;
      axios.get('https://api.example.com/data')
        .then(response => {
          this.data = response.data;
        })
        .catch(error => {
          this.error = `请求失败: ${error.message}`;
        })
        .finally(() => {
          this.loading = false;
        });
    }
  }
};
</script>

<style>
.error {
  color: red;
}
</style>

4. 使用axios拦截器

// 创建 Axios 实例
const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 1000,
  headers: { 'X-Custom-Header': 'foobar' }
});

// 添加请求拦截器
apiClient.interceptors.request.use(config => {
  // 在发送请求之前做些什么
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
}, error => {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
apiClient.interceptors.response.use(response => {
  // 对响应数据做点什么
  return response;
}, error => {
  // 对响应错误做点什么
  return Promise.reject(error);
});

// 使用实例发起请求
apiClient.get('/data')
  .then(response => {
    console.log('API 数据:', response.data);
  })
  .catch(error => {
    console.error('API 请求失败:', error);
  });

3. Fetch 示例

Fetch 是现代浏览器原生提供的网络请求 API,基于 Promise,提供了比 AJAX 更简洁的语法。然而,Fetch 不会自动处理 JSON 数据,也不支持一些高级功能(如取消请求需要借助 AbortController),需要开发者手动处理。 

js示例
// 发起 GET 请求
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP 错误! 状态: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log('Fetch 数据:', data);
  })
  .catch(error => {
    console.error('Fetch 请求失败:', error);
  });
vue代码示例
<template>
  <div>
    <button @click="fetchData">获取数据</button>
    <div v-if="loading">加载中...</div>
    <div v-else>
      <pre>{{ data }}</pre>
    </div>
    <div v-if="error" class="error">{{ error }}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      loading: false,
      data: null,
      error: null
    };
  },
  methods: {
    fetchData() {
      this.loading = true;
      this.error = null;
      fetch('https://api.example.com/data')
        .then(response => {
          if (!response.ok) {
            throw new Error(`HTTP 错误! 状态: ${response.status}`);
          }
          return response.json();
        })
        .then(data => {
          this.data = data;
        })
        .catch(error => {
          this.error = `请求失败: ${error.message}`;
        })
        .finally(() => {
          this.loading = false;
        });
    }
  }
};
</script>

<style>
.error {
  color: red;
}
</style>

4.总结 

  1.  ​AJAX 是早期的网络请求技术,虽然兼容性好,但在现代开发中逐渐被更先进的技术取代。
  2. ​Axios 提供了更简洁和强大的 API,内置了许多实用功能,是当前最流行的网络请求库之一。
  3. ​Fetch 是现代浏览器原生提供的 API,语法简洁,但需要手动处理一些功能,适合轻量级应用或对包体积有要求的项目。


网站公告

今日签到

点亮在社区的每一天
去签到