前端面试题详解整理88|async/defer区别,get和post区别吗,v-if,v-for,省略文字,promise.all,token.cookie,时

52 阅读19分钟

B站前端实习一面

发个面经积攒人品
时长:40min
上来拷打项目,问的挺细的,好久没看了说的都不怎么全,甚至还有的说不上来。
然后是非常简单的几个八股

async/defer区别

asyncdefer 是用来控制脚本的加载和执行顺序的两个 HTML 属性。它们通常用于 <script> 标签中。

  1. async
    • 当浏览器遇到带有 async 属性的脚本时,它会异步地下载该脚本,同时不阻止 HTML 解析。
    • 下载完成后,脚本会立即执行,不管 HTML 是否解析完成。
    • 多个带有 async 属性的脚本,执行顺序不确定,谁先下载完成谁先执行。
    • 适用于不需要按顺序执行的脚本,且不依赖页面的其他内容。
<script src="script.js" async></script>
  1. defer
    • 当浏览器遇到带有 defer 属性的脚本时,它会异步地下载该脚本,同时不阻止 HTML 解析。
    • 脚本会在 HTML 解析完成后、DOMContentLoaded 事件触发之前执行,按照在页面中出现的顺序依次执行。
    • 多个带有 defer 属性的脚本,执行顺序与它们在页面中的顺序相同。
    • 适用于需要按照顺序执行,但又不需要等待 HTML 解析完成的脚本。
<script src="script.js" defer></script>

综上所述,asyncdefer 都用于异步加载脚本,但它们的执行时机和顺序略有不同,具体使用取决于脚本的依赖关系和执行顺序要求。

get/post区别

GET 和 POST 是 HTTP 协议中最常见的两种请求方法,它们在数据传输、安全性和语义上有一些区别。

  1. 传输方式

    • GET 请求通过 URL 参数传输数据,数据会附加在 URL 后面,可以在浏览器地址栏中看到。
    • POST 请求通过请求体(Request Body)传输数据,数据不会暴露在 URL 中,而是在请求体中发送。
  2. 请求大小限制

    • GET 请求对 URL 的长度有限制,因为 URL 的长度有浏览器和服务器的限制,通常是 2KB 到 8KB 之间。
    • POST 请求没有限制,可以发送大量数据,但服务器可能会设置请求体的大小限制。
  3. 安全性

    • GET 请求的数据会暴露在 URL 中,不适合发送敏感信息,例如密码等。
    • POST 请求的数据在请求体中,不会暴露在 URL 中,更适合发送敏感信息。
  4. 幂等性

    • GET 请求是幂等的,即多次重复调用不会产生副作用。
    • POST 请求不是幂等的,即多次重复调用可能会产生不同的结果。
  5. 缓存

    • GET 请求可以被缓存,适合获取静态资源等不会产生副作用的请求。
    • POST 请求默认不会被缓存,因为可能会产生副作用,但可以通过设置 Cache-Control 头字段来实现缓存。

综上所述,GET 和 POST 请求在数据传输方式、大小限制、安全性、幂等性和缓存等方面有所不同,应根据实际需求选择合适的请求方法。通常情况下,GET 适合用于获取数据,POST 适合用于提交数据。

vue v-if和v-for

v-ifv-for 是 Vue.js 中两个常用的指令,用于在模板中控制元素的显示和循环渲染。

  1. v-if
    • v-if 是 Vue.js 提供的条件渲染指令,用于根据表达式的真假来决定是否渲染某个元素。
    • 当表达式为真时,元素被渲染到 DOM 中;当表达式为假时,元素从 DOM 中移除。
    • v-if 可以单独使用或与 v-elsev-else-if 指令配合使用,实现复杂的条件判断。
<div v-if="isShow">显示内容</div>
  1. v-for
    • v-for 是 Vue.js 提供的列表渲染指令,用于根据数据源的内容重复渲染元素。
    • 可以使用 v-for 遍历数组、对象或数字范围。
    • 可以通过 v-for 的第二个参数获取索引值。
<ul>
  <li v-for="(item, index) in items" :key="index">{{ item }}</li>
</ul>
  1. v-if 和 v-for 的组合使用
    • 在同一个元素上,不推荐同时使用 v-ifv-for
    • 原因是 v-for 的优先级比 v-if 高,每次都会先遍历数据,然后再根据条件进行渲染,可能会导致不必要的性能损耗。
    • 如果需要根据条件过滤数据并循环渲染,可以使用计算属性或方法先处理数据,然后再在模板中使用 v-for
<ul>
  <li v-for="item in filteredItems" :key="item.id">{{ item }}</li>
</ul>
computed: {
  filteredItems() {
    return this.items.filter(item => item.condition);
  }
}

综上所述,v-if 用于条件渲染,根据表达式的真假来控制元素的显示与隐藏;v-for 用于列表渲染,根据数据源的内容重复渲染元素;在使用时应注意避免在同一元素上同时使用 v-ifv-for,并根据实际需求选择合适的指令。

css实现多行文本省略文字

要实现多行文本的文字溢出省略,可以使用 CSS 的 text-overflow-webkit-line-clamp 属性。具体步骤如下:

  1. 设置容器的高度和样式,确保文本溢出时会产生省略号。
  2. 使用 -webkit-line-clamp 属性限制文本显示的行数。
  3. 设置 overflow: hidden;text-overflow: ellipsis; 属性来隐藏溢出的文本并显示省略号。

下面是一个示例:

<style>
  .text-container {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    overflow: hidden;
    -webkit-line-clamp: 3; /* 显示行数 */
    text-overflow: ellipsis;
    max-height: 3em; /* 控制显示行数 */
  }
</style>

<div class="text-container">
  <!-- 这里放置要显示的文本 -->
  Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec dui eu velit lacinia bibendum.
</div>

在上面的示例中,.text-container 类将文本限制为显示三行,超出的部分会被省略号代替。请注意,-webkit-line-clamp 属性在某些浏览器中可能不被支持,因此使用时应注意浏览器兼容性。

问我会ts吗,我说了解,要实现一个类型,他的值类型为枚举类型的key,说了思路,应该是对了。
要实现一个类型,其值的类型为枚举类型的 key,可以使用 TypeScript 的 keyof 关键字。keyof 可以用于获取指定类型的所有键名,然后将其用作值的类型。

以下是一个示例:

enum MyEnum {
  A = 'ValueA',
  B = 'ValueB',
  C = 'ValueC'
}

type MyType = keyof typeof MyEnum;

// MyType 的值只能是 MyEnum 的键名,即 'A' | 'B' | 'C'
let value: MyType = 'A'; // 正确
value = 'D'; // 错误,'D' 不是 MyEnum 的键名

在上面的示例中,MyType 类型的值只能是 MyEnum 枚举类型的键名,即 'A''B''C'。这样可以确保类型的值限制在枚举类型的键名范围内,从而提高代码的类型安全性。

最后手写promise.all

下面是一个简单的手写 Promise.all 的实现:

function customPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Promises must be an array'));
    }

    const results = [];
    let completedCount = 0;

    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(result => {
          results[index] = result;
          completedCount++;
          if (completedCount === promises.length) {
            resolve(results);
          }
        })
        .catch(reject);
    });
  });
}

该实现的核心思想是:

  1. 创建一个新的 Promise 对象,用于包装所有传入的 Promise。
  2. 遍历传入的 Promise 数组,对每个 Promise 调用 Promise.resolve 方法,确保将非 Promise 对象转换为 Promise 对象。
  3. 对每个 Promise 调用 then 方法,将返回的结果存储到结果数组中,直到所有 Promise 都完成。
  4. 当所有 Promise 都完成时,将结果数组传递给外部 Promise 的 resolve 方法。

这样就实现了一个简单的 Promise.all 方法,它能够等待所有的 Promise 都完成,并将所有 Promise 的结果组成一个数组返回。

反问:自认答的很烂,问了问学习建议,不过面试官说懂得还挺多的,可能需要深入某个点。一些问题忘了很正常,解决问题的时候知道咋解决就行。
总结:一些基础的八股没咋看,脑子卡壳答的像答辩,应该是挂了,寄。

作者:俺家猪有形
链接:www.nowcoder.com/feed/main/d…
来源:牛客网 腾讯前端实习一面_牛客网 (nowcoder.com)

2. token和 cookie

Token和Cookie都是用于在Web应用程序中进行身份验证和授权的常见机制,但它们有一些不同之处。

  1. Token

    • Token是一种无状态的认证方式,通常被用于API的认证授权。
    • 在Web应用中,常见的Token有JWT(JSON Web Token)和OAuth2 Token。
    • Token通常存储在客户端,比如LocalStorage或SessionStorage中,或者通过HTTP请求的Header中进行传递。
    • 服务器在接收到Token后,会解析Token中的信息,验证用户的身份和权限,并生成对应的响应。
  2. Cookie

    • Cookie是一种用于在客户端和服务器之间传递信息的小型文本文件。
    • Cookie通常用于存储会话信息、用户首选项、跟踪用户行为等。
    • Cookie存储在客户端的浏览器中,每次向服务器发送请求时,都会自动将对应的Cookie信息包含在请求头中。
    • Cookie有一些特性,比如过期时间、域名限制、路径限制等,可以通过设置这些特性来控制Cookie的行为。

总的来说,Token通常用于API的认证授权,适用于无状态的分布式系统,而Cookie则是传统的Web应用程序中常用的身份验证和会话管理机制。在实际开发中,可以根据具体需求和场景选择合适的认证授权方式。

4.如何使用原生JS绑定点击事件:addEventListener的第三个参数

addEventListener 方法的第三个参数是一个布尔值,用于指定事件是否在捕获阶段进行处理。它的作用是确定事件是在捕获阶段还是冒泡阶段触发。

如果该参数为 true,则表示事件在捕获阶段处理;如果为 false,则表示事件在冒泡阶段处理。通常情况下,我们使用冒泡阶段来处理事件,因为这样可以更灵活地控制事件的触发顺序和行为。

以下是一个示例:

// 获取元素
const myButton = document.getElementById('myButton');

// 添加点击事件监听器,事件在冒泡阶段进行处理
myButton.addEventListener('click', handleClick, false);

// 点击事件处理函数
function handleClick(event) {
  console.log('Button clicked!');
}

在上面的示例中,addEventListener 方法的第三个参数设置为 false,表示事件在冒泡阶段进行处理。这是 addEventListener 方法的默认行为,因此在大多数情况下,我们可以省略第三个参数或直接传入 false

事件捕获和事件冒泡

事件捕获和事件冒泡是事件传播的两种不同方式,它们描述了事件在DOM树中传播时的顺序和方向。

  1. 事件捕获

    • 在事件捕获阶段,事件从DOM树的根节点向目标元素传播。
    • 事件捕获阶段的顺序是从外到内,即从根节点逐级向下直到目标元素。
    • 在捕获阶段,事件处理程序是按照父元素到子元素的顺序触发的。
  2. 事件冒泡

    • 在事件冒泡阶段,事件从目标元素向DOM树的根节点传播。
    • 事件冒泡阶段的顺序是从内到外,即从目标元素逐级向上直到根节点。
    • 在冒泡阶段,事件处理程序是按照子元素到父元素的顺序触发的。

综上所述,事件捕获和事件冒泡描述了事件在DOM树中的不同传播方向,但在实际开发中,大多数情况下都使用事件冒泡阶段来处理事件,因为它更符合直觉,而且兼容性更好。事件捕获阶段在某些特定场景下可能有用,但使用相对较少。

防抖和节流

防抖(Debouncing)和节流(Throttling)是用于控制事件触发频率的两种常见技术,它们在实际开发中经常用于优化性能或避免不必要的资源浪费。

  1. 防抖(Debouncing)

    • 防抖的基本思想是在事件触发后等待一段时间再执行回调函数,如果在等待时间内又有事件触发,则重新计时。
    • 典型应用场景包括搜索框输入、滚动事件等,避免频繁触发事件而导致不必要的资源消耗。
    • 实现方式是使用定时器,在事件触发后设定一个延迟时间,在延迟时间内如果再次触发事件,则取消之前的定时器并重新设定新的定时器。
  2. 节流(Throttling)

    • 节流的基本思想是在一段时间内只允许触发一次事件回调函数,即无论事件触发多频繁,都在固定的时间间隔内执行一次回调函数。
    • 典型应用场景包括滚动事件、resize事件等,控制事件触发频率,减少资源消耗。
    • 实现方式是使用时间戳或定时器,在每次事件触发时判断当前时间与上次触发时间的间隔,如果间隔超过设定的阈值,则执行回调函数并更新上次触发时间。

综上所述,防抖和节流都是常用的性能优化技术,但它们适用于不同的场景,选择合适的技术取决于具体需求和事件特性。

js事件循环

JavaScript事件循环(Event Loop)是JavaScript引擎用于处理异步任务的一种机制,它负责维护一个事件队列(Event Queue)和一个调用栈(Call Stack),并按照一定规则从事件队列中取出任务执行。

事件循环的基本流程如下:

  1. 同步任务:JavaScript代码在执行过程中,同步任务会直接进入调用栈中执行。

  2. 异步任务:异步任务包括定时器回调、事件监听、Ajax请求等,它们不会立即执行,而是会被放入事件队列中等待执行。

  3. 事件循环:事件循环是一个持续的过程,不断地从事件队列中取出任务执行,直到事件队列为空。

具体的执行过程如下:

  • 当调用栈为空时,事件循环会检查事件队列是否为空。
  • 如果事件队列为空,则等待新的任务加入。
  • 如果事件队列不为空,则取出一个任务并放入调用栈中执行。
  • 执行完当前任务后,检查调用栈是否为空,如果不为空则继续取出下一个任务执行,否则回到第一步。

JavaScript事件循环的机制保证了异步任务的执行顺序和可靠性,同时确保了单线程的执行模型。在事件循环中,微任务(Microtask)会在每次执行栈清空后立即执行,而宏任务(Macrotask)则会等待当前执行栈清空后才执行。这样可以保证微任务先于宏任务执行,从而确保了一些关键的异步操作(比如Promise的then方法、DOM的更新等)在宏任务之前执行,提高了响应速度和用户体验。

7.http状态码。如果用户输入错误,后端

应该返回什么状态码 HTTP状态码是HTTP协议用于表示客户端与服务器之间请求-响应过程中的状态的数字代码。对于用户输入错误的情况,通常应返回以下几种状态码之一:

  1. 400 Bad Request

    • 表示客户端发送的请求存在语法错误,服务器无法理解。
  2. 401 Unauthorized

    • 表示客户端未经身份验证,需要进行身份验证后才能访问资源。
  3. 403 Forbidden

    • 表示服务器理解客户端的请求,但拒绝执行该请求,通常是因为客户端没有访问权限。
  4. 404 Not Found

    • 表示服务器未找到客户端请求的资源,通常用于表示客户端请求的URL地址不存在。
  5. 422 Unprocessable Entity

    • 表示服务器理解客户端发送的请求,但是请求中包含的数据格式正确,但无法处理,通常用于表示输入数据校验失败等情况。

具体选择哪种状态码取决于具体的业务逻辑和错误类型。通常情况下,应该选择与错误情况最匹配的状态码,以便客户端能够根据状态码更好地理解发生了什么错误,从而采取适当的处理措施。

vue和 react在循环渲染时绑定key有什么作用

在Vue和React中,循环渲染(例如使用v-formap()函数)时绑定key的作用是帮助框架识别列表中的每个元素,从而在更新DOM时进行有效的重用、更新和删除。

具体作用包括:

  1. 提高性能:使用key可以帮助Vue和React更准确地识别出列表中的每个元素,从而在更新DOM时减少不必要的操作,提高渲染性能。

  2. 保持状态:通过key的唯一性,Vue和React可以更好地跟踪列表中各个元素的状态,确保在重新渲染时不会丢失用户输入、组件状态等重要信息。

  3. 避免重复渲染:有了唯一的key,Vue和React可以更容易地识别出哪些元素是新增的、哪些是更新的,避免重复渲染已存在的元素,提高渲染效率。

总的来说,绑定key可以帮助Vue和React更高效地管理列表中的元素,提高渲染性能和用户体验。因此,在进行循环渲染时,应该始终为列表中的每个元素提供一个唯一的key值。

讲讲redux在项目中的使用

Redux 是一种用于 JavaScript 应用程序的状态管理库,它可以帮助你管理应用程序的状态并使状态变化可预测。Redux 的核心概念是单一数据源和状态不可变性,它的使用通常包括以下几个方面:

  1. 单一数据源(Single Source of Truth)

    • Redux 使用单一的 JavaScript 对象来存储整个应用程序的状态,称为状态树或存储(Store)。
    • 这个状态树对整个应用程序是唯一的,任何一个组件都可以访问到整个应用程序的状态。
  2. 状态不可变性(Immutable State)

    • Redux 要求状态是不可变的,即不能直接修改原始状态,而是通过创建新的状态对象来更新状态。
    • 这样做的好处是可以追踪状态的变化,并且可以方便地实现时间旅行、状态回滚等功能。
  3. Action

    • Action 是一个包含描述事件的普通 JavaScript 对象,它必须包含一个表示事件类型的 type 属性。
    • 通过派发(dispatch)Action,可以通知 Redux 应用程序状态发生了变化。
  4. Reducer

    • Reducer 是一个纯函数,它接收当前状态和一个 Action,并返回一个新的状态。
    • Reducer 的作用是根据接收到的 Action 更新应用程序的状态。
  5. Store

    • Store 是 Redux 应用程序的核心,它包含了应用程序的状态树和一些方法用于状态的管理。
    • 通过创建 Store,可以让应用程序中的各个组件共享状态,并且可以订阅状态的变化。
  6. Middleware

    • Middleware 是一个函数,它可以在派发 Action 和 Reducer 之间执行自定义的逻辑。
    • Middleware 可以用于实现日志记录、异步操作、路由等功能。

在项目中使用 Redux 通常需要进行以下步骤:

  1. 定义 Action 类型和 Action 创建函数。
  2. 定义 Reducer 函数来处理各种 Action 类型。
  3. 创建 Store,并将 Reducer 和 Middleware 应用到 Store 中。
  4. 在组件中使用 connect 方法连接 Redux Store,并通过 mapStateToPropsmapDispatchToProps 方法将组件的状态和操作与 Redux Store 关联起来。

总的来说,Redux 可以帮助你管理应用程序的状态并实现状态的可预测性,但在使用时需要考虑到其复杂性和适用性,避免过度使用。

10.useContext的使用 11.useEffect的使用 12.自定义hook 13.三道不太难的编程题目

10. useContext 的使用

useContext 是 React 提供的一个 Hook,用于在函数组件中获取上下文(Context)的值。它接收一个 Context 对象(通过 React.createContext 创建),并返回该 Context 的当前值。

import React, { useContext } from 'react';

// 创建一个 Context
const ThemeContext = React.createContext('light');

// 在父组件中提供 Context 的值
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// 在子组件中使用 useContext 获取 Context 的值
function Toolbar() {
  const theme = useContext(ThemeContext);
  return <div>当前主题:{theme}</div>;
}

11. useEffect 的使用

useEffect 是 React 提供的一个 Hook,用于在函数组件中执行副作用操作(如数据获取、订阅、手动操作 DOM 等)。它在组件渲染完成后执行,默认情况下在每次渲染后都会执行,可以通过第二个参数来控制执行的条件。

import React, { useEffect, useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // useEffect 接收一个回调函数作为参数
  useEffect(() => {
    // 在组件渲染完成后执行的副作用操作
    document.title = `你点击了 ${count} 次`;
  }, [count]); // 依赖数组,只有当 count 改变时才执行副作用

  return (
    <div>
      <p>你点击了 {count} 次</p>
      <button onClick={() => setCount(count + 1)}>点击增加</button>
    </div>
  );
}

12. 自定义 Hook

自定义 Hook 是一个函数,其名称以 "use" 开头,可以在函数内部使用其他 Hook。它可以用来复用状态逻辑、副作用等。自定义 Hook 通常用来解决组件之间共享状态逻辑复用的问题。

import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
        setLoading(false);
      } catch (error) {
        console.error('Error fetching data:', error);
        setLoading(false);
      }
    }

    fetchData();

    // 清除副作用
    return () => {
      setData(null);
      setLoading(true);
    };
  }, [url]); // 依赖数组,只有当 url 改变时才重新请求数据

  return { data, loading };
}

export default useFetch;

使用自定义 Hook:

import React from 'react';
import useFetch from './useFetch';

function MyComponent() {
  const { data, loading } = useFetch('https://api.example.com/data');

  if (loading) {
    return <div>Loading...</div>;
  }

  return <div>{JSON.stringify(data)}</div>;
}

export default MyComponent;

通过自定义 Hook,可以将逻辑与 UI 分离,提高代码的复用性和可维护性。