Skip to content

即刻前端面试

March 12, 2023 | 11:32 AM

即刻前端面试

这是我目前面过的公司里难度感觉最高的一家了,不是常规的那种八股文直接硬背,而是真的结合实际的场景进行出题的,对于一个技术栈的考察的深度很深,是一个很不错的面试官

首先第一点给我感觉不一样的就是,没有自我介绍!上来就直接发了个链接,可能是打算结合实际代码来对我进行考察,可惜这里失败了(学校的网真的差。。。)

ref和reactive的区别

ref是一个函数,它可以将一个普通数据类型(如数字、字符串等)转换为一个响应式对象,从而让这个数据在Vue的响应式系统中被追踪。ref返回一个对象,这个对象有一个.value属性,用来获取和设置这个响应式对象的值

import { ref } from 'vue';

const count = ref(0);

console.log(count.value); // 0

count.value = 1;

console.log(count.value); // 1

而reactive则是一个函数,它可以将一个普通的Javascript对象转换为一个响应式对象。它会递归地将这个对象的所有属性都转换为响应式对象,从而让整个对象在Vue的响应式系统中被追踪。reactive返回一个Proxy对象,用来代理原始对象的访问和修改

import { reactive } from 'vue';

const state = reactive({
  count: 0,
  message: 'hello'
});

console.log(state.count); // 0
console.log(state.message); // 'hello'

state.count = 1;
state.message = 'world';

console.log(state.count); // 1
console.log(state.message); // 'world'

因此,ref主要用于创建一个单一的响应式数据,而reactive则适用于创建一个复杂的、包含多个属性的响应式数据对象

ref可以大量的替换成reactive吗

不能直接把ref替换成reactive。

ref主要用于将基本数据类型(如字符串、数字等)转换为响应式数据,并提供一个.value属性用于访问和修改该数据。而reactive则用于将一个普通的JavaScript对象转换为响应式对象,并使用Proxy来拦截对该对象的访问和修改,以实现响应式更新。

因此,如果你需要使用响应式数据来存储基本数据类型,或者你只需要响应式地跟踪一个值的变化,那么ref仍然是更合适的选择。而如果你需要管理一个对象的多个属性,并希望这些属性可以响应式地更新,那么reactive会更加合适

为什么vue和react都去选择自己实现一个路由,是出于什么目的呢

  1. 更好地集成到框架中:由于路由是前端应用中必不可少的一部分,因此框架集成路由功能可以提供更好的用户体验和开发效率。通过自己实现路由库,Vue和React可以将路由功能无缝集成到框架中,提供更好的开发体验和更高的开发效率。
  2. 更好地控制代码和依赖:Vue和React自己实现的路由库可以更好地控制代码和依赖。如果使用第三方路由库,可能会增加代码的复杂性和依赖关系,而自己实现路由库可以避免这些问题。
  3. 更好地满足框架的需求:Vue和React的路由库可以更好地满足框架的需求。由于框架本身的特性和设计思想,可能需要特定的路由实现方式来满足这些需求。通过自己实现路由库,可以更好地满足框架的需求,提供更好的开发体验和更高的性能。
  4. 更好地控制性能:Vue和React的路由库可以更好地控制性能。由于路由是前端应用中的关键部分,性能往往是一个重要的考虑因素。通过自己实现路由库,Vue和React可以更好地控制路由的性能,从而提供更好的用户体验和更高的性能

总之,Vue和React都实现了自己的路由库,主要是为了更好地集成到框架中,更好地控制代码和依赖,更好地满足框架的需求,以及更好地控制性能

浏览器自己本身就有路由,为什么不直接用a标签进行一个跳转,而是选择用router来进行一个跳转呢

浏览器本身的路由是基于URL的,即每个页面都有一个唯一的URL地址。使用浏览器本身的路由,需要在URL中手动编写参数,处理页面刷新和前进/后退等操作的逻辑,这样会使得代码复杂性增加,并且不太方便维护。

而使用router库可以将路由相关的逻辑抽象出来,让开发者可以更加方便地处理页面跳转和传递参数等操作。使用router可以实现单页应用(SPA),使得用户在应用中的操作更流畅,且无需每次跳转都重新加载整个页面。

此外,使用router还可以提供一些额外的功能,如路由守卫、动态路由等,这些功能可以帮助开发者更好地控制路由的行为,提高应用的性能和安全性

虽然浏览器本身也有路由,但使用router库可以提供更好的开发体验和更丰富的功能,使得应用的开发更加方便、高效和可维护

浏览器为什么支持单页面路由呢?

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/History

浏览器支持单页面路由的一个重要原因是History API。

在传统的多页面应用中,页面之间的跳转通过超链接或表单提交等方式实现,每个页面都有一个唯一的URL地址。而在单页面应用中,页面的跳转是通过JavaScript代码控制,使用history API可以更加方便地实现这种页面切换逻辑。

history API是HTML5规范中新增的一组API,可以让开发者更加方便地操作浏览器的历史记录。通过history API,开发者可以在不重新加载整个页面的情况下,改变浏览器的URL地址,添加或修改历史记录,以及监听历史记录的变化等操作。

在单页面应用中,开发者可以使用history API来实现前端路由,即在不重新加载整个页面的情况下,通过改变URL地址,实现不同页面之间的切换。这样可以提高应用程序的性能,并且使得应用程序更具交互性和动态性

当我们在使用history进行导航的时候,我们的页面真的进行了一个切换吗?是怎么做到的呢

当我们使用history进行导航时,实际上并没有进行页面的刷新或重新加载。相反,浏览器仅仅是通过修改URL地址和浏览器历史记录,模拟了一个页面的切换效果。

具体来说,使用history进行导航时,我们通常会调用history.pushState或history.replaceState方法,这些方法可以向浏览器历史记录中添加或修改一个记录,并且同时修改当前URL地址。然后,浏览器会根据新的URL地址重新渲染页面,并且在浏览器的历史记录中添加或修改一个记录。

当我们使用history进行导航时,虽然页面并没有进行刷新或重新加载,但是浏览器会触发一些相关的事件,如popstate事件,用来处理导航过程中的一些逻辑。开发者可以在这些事件中添加相关的处理逻辑,从而实现前端路由的功能

vue如何监听路由的变化呢?

在Vue中,可以使用Vue Router提供的导航守卫(Navigation Guards)来监听路由的变化。

导航守卫是Vue Router提供的一组钩子函数,可以在路由发生变化时被触发。通过使用导航守卫,开发者可以实现一些常见的路由控制逻辑,如路由权限控制、路由拦截、路由跳转前的确认等等。

Vue Router提供了三种类型的导航守卫:

  1. 全局守卫:在整个应用程序中,所有的路由变化都会触发这些守卫。可以通过Vue Router实例的beforeEach、beforeResolve和afterEach方法来注册全局守卫。
  2. 路由独享守卫:在某个路由上,只有该路由变化时才会触发这些守卫。可以在路由配置对象中通过beforeEnter属性来注册路由独享守卫。
  3. 组件内守卫:在某个路由对应的组件中,可以通过Vue组件生命周期钩子函数来监听路由的变化,实现一些组件内部的路由控制逻辑。

以下是一个使用全局守卫来监听路由变化的示例:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const router = new VueRouter({
  routes: [
    { path: '/', component: Home },
    { path: '/about', component: About }
  ]
})

router.beforeEach((to, from, next) => {
  console.log('路由变化:', from.path, ' => ', to.path)
  next()
})

在上面的示例中,我们通过Vue Router实例的beforeEach方法注册了一个全局守卫,在每次路由变化时都会触发这个守卫,并且打印出路由变化的信息。可以根据实际需求,编写自己的路由守卫逻辑

原生js如何进行监听路由的变化

在原生 JavaScript 中,可以使用 window.onpopstate 事件来监听路由的变化。

当用户在浏览器中进行前进或后退操作时,或者通过 JavaScript 调用 history.pushState()history.replaceState() 方法时,都会触发 window.onpopstate 事件。

可以通过如下代码来监听 window.onpopstate 事件:

window.onpopstate = function(event) {
  console.log("当前路由:", window.location.pathname);
};

在上面的代码中,当用户在浏览器中进行前进或后退操作时,或者通过 JavaScript 调用 history.pushState()history.replaceState() 方法时,会触发 window.onpopstate 事件,并输出当前路由路径到控制台。

需要注意的是,这种方式只能监听浏览器历史记录中的路由变化,对于通过 AJAX 或其他方式进行的路由变化是无法监听的。如果需要监听所有的路由变化,可以考虑使用一些现有的路由库,如 React Router、Vue Router 等

没有hash的路由如何进行监听

如果你使用的是 HTML5 History API 来管理路由,而不是 hash 路由,那么可以通过监听 popstate 事件来实现路由变化的监听。

HTML5 History API 可以让我们使用 pushState()replaceState() 方法来操作浏览器的历史记录,并可以修改当前页面的 URL,而不会导致页面的刷新。

当通过 pushState()replaceState() 方法修改浏览器历史记录时,会触发 popstate 事件。我们可以通过监听这个事件来获取路由的变化。

例如,我们可以使用如下代码来监听路由的变化:

window.addEventListener('popstate', function(event) {
  console.log("当前路由:", window.location.pathname);
});

在上面的代码中,当用户通过浏览器前进或后退按钮,或者通过 pushState()replaceState() 方法修改浏览器历史记录时,会触发 popstate 事件,并输出当前路由路径到控制台

onpopstate可以监听到一个pushstate的事件吗

这里回答错了,我说的是可以进行监听的,面试官让我回去再好好看看

onpopstate 事件只能监听到由浏览器触发的历史记录变化,例如点击浏览器的前进或后退按钮,或者调用 history.back()history.forward() 方法。

如果你在 JavaScript 中调用 history.pushState()history.replaceState() 方法来修改浏览器的历史记录,那么不会触发 onpopstate 事件。但是,调用这两个方法会添加新的历史记录,并且可以在历史记录中回退和前进,这些历史记录变化会触发 onpopstate 事件。

因此,如果你想要在调用 pushState()replaceState() 方法后立即获取路由变化,可以在调用这两个方法后手动触发 popstate 事件,例如

history.pushState({}, '', '/new-path');
window.dispatchEvent(new PopStateEvent('popstate'));

在上面的代码中,我们先调用 pushState() 方法来修改浏览器的历史记录,并修改当前页面的 URL 为 /new-path。然后,手动触发 popstate 事件,这会立即触发绑定在 window.onpopstate 上的事件处理函数,并获取到新的路由信息

ts泛型的作用,在开发当中最常用在哪里

TypeScript 中的泛型(generics)是一种用于在编译时期处理类型的工具。泛型可以让我们编写更通用、更可重用的代码。

泛型最常用的场景之一是在函数和类中使用。通过使用泛型,我们可以编写可重用的函数或类,可以支持多种不同类型的参数或属性。例如,下面是一个使用泛型的函数示例

function reverse<T>(list: T[]): T[] {
  return list.reverse();
}

let numbers = [1, 2, 3, 4];
let reversedNumbers = reverse(numbers);

let letters = ['a', 'b', 'c'];
let reversedLetters = reverse(letters);

在上面的代码中,我们定义了一个名为 reverse 的函数,它使用了一个类型参数 T。我们可以将 reverse 函数应用于任何具有 reverse 方法的数组类型。在调用 reverse 函数时,我们将一个类型为 T[] 的数组作为参数传递,并返回一个类型为 T[] 的数组。

除了函数和类,泛型还可以应用于 TypeScript 中的其它特性,例如接口、类型别名等

在开发中,我们最常使用泛型的场景是编写通用的数据结构、算法和函数,例如列表、树、排序算法、搜索算法等等。泛型可以让我们编写更通用、更可重用的代码,并且可以提高代码的灵活性和可扩展性。同时,使用泛型还可以让我们在编译时期发现类型错误,避免一些潜在的运行时错误

axios二次封装的好处

通过对 axios 进行二次封装,我们可以实现以下功能:

  1. 统一处理请求参数和响应数据格式:我们可以对请求参数和响应数据进行预处理或格式化,以便在多个请求中使用相同的格式。
  2. 统一处理错误信息:我们可以对错误信息进行统一处理或格式化,以便在多个请求中使用相同的错误信息处理逻辑。
  3. 添加请求头、设置超时时间等功能:我们可以在二次封装中添加一些公共的请求头、超时时间等参数,以便在多个请求中使用相同的参数。
  4. 支持自定义拦截器:我们可以通过自定义拦截器来对请求或响应进行处理,例如添加 token、在请求头中添加认证信息等

如何标识用户已经登录

在 Web 应用程序中,标识用户是否已经登录通常使用 Session 或 Token 的方式。

Session 是一种服务器端的技术,用于跟踪用户的状态。当用户登录成功后,服务器会创建一个 Session,并为该用户分配一个唯一的 Session ID,将该 Session ID 存储在 Cookie 中或者通过 URL 传递给客户端。在用户访问其他页面时,客户端会将 Session ID 发送回服务器,并使用该 ID 来查找服务器端的 Session 数据。如果 Session 数据存在,说明用户已经登录,否则用户未登录。使用 Session 的优点是可以在服务器端存储敏感的用户信息,不会在客户端暴露。

Token 是一种基于客户端的技术,通常使用 JSON Web Token (JWT) 或类似的技术。当用户登录成功后,服务器会生成一个 Token,并将该 Token 发送给客户端。客户端在后续的请求中携带该 Token,服务器可以通过解析 Token 来验证用户的身份。使用 Token 的优点是可以让客户端缓存用户的登录状态,减轻服务器的负担,同时可以在不同的服务之间共享用户的登录状态。

在实现登录功能时,通常需要将用户的登录信息存储在服务器端,例如数据库、缓存等等。当用户登录成功后,服务器会创建 Session 或生成 Token,并将其返回给客户端。客户端可以将 Session ID 存储在 Cookie 中,或将 Token 存储在本地存储或会话存储中。在后续的请求中,客户端会将 Session ID 或 Token 发送回服务器,服务器可以根据 Session ID 或解析 Token 来验证用户的身份

token已经过期的话,我想要刷新token如何实现

在实现 Token 刷新功能时,通常需要注意以下几个步骤:

  1. 在服务器端,需要定义一个用于刷新 Token 的 API 接口,该接口需要验证当前 Token 的有效性,并根据需要生成一个新的 Token,并返回给客户端。
  2. 在客户端,当发现当前 Token 已经过期时,需要向服务器端发送一个刷新 Token 的请求,并将当前 Token 和刷新 Token 的回调函数传递给服务器端。
  3. 在服务器端,当收到客户端发送的刷新 Token 的请求时,需要验证当前 Token 的有效性。如果当前 Token 有效,生成一个新的 Token,并将其返回给客户端。如果当前 Token 无效,需要向客户端返回一个错误码或提示信息。
  4. 在客户端,当收到服务器端返回的新 Token 时,需要将新 Token 存储到本地存储或会话存储中,并调用刷新 Token 的回调函数

无感刷新token

在前后端分离的应用中,为了保证安全性和用户体验,通常会使用token来实现用户身份认证。当token过期时,需要重新获取新的token,以保持用户的登录状态。在这种情况下,无感刷新token可以提高用户体验,使用户无需手动重新登录即可继续访问应用程序。

以下是一种基本的无感刷新token的实现思路:

  1. 定义token的过期时间,例如30分钟。
  2. 在用户每次发送请求时,检查token是否快要过期,例如在token过期时间前5分钟进行检查。
  3. 如果token即将过期,发送一个请求给后端,请求新的token。
  4. 如果后端返回新的token,将新的token保存在本地,同时更新所有请求中的token值。
  5. 如果后端返回错误或者新的token无效,清除本地token,跳转到登录页面。

通过这种方式,即使token过期,用户也不需要手动重新登录即可继续使用应用程序,从而提高用户体验和应用程序的安全性。当然,具体实现细节可能因具体应用场景而异,需要根据实际情况进行调整。

将上面的操作写在哪里呢?

在实现无感刷新token的过程中,主要涉及到两个方面的实现:前端和后端。

前端方面,可以在请求拦截器中实现token的检查和更新。可以通过在请求头中设置Authorization字段,将token发送给后端。当token即将过期时,可以在请求拦截器中发送一个刷新token的请求,并将新的token保存在本地存储中,同时更新所有请求的Authorization字段。这样,即使token过期,用户也无需手动刷新token即可继续使用应用程序。

后端方面,需要实现一个token的刷新接口,接收旧的token并返回新的token。在处理刷新请求时,需要对旧的token进行验证,以确保该请求是合法的。如果验证通过,则生成新的token并返回给前端,否则返回错误信息。

总之,在实现无感刷新token的过程中,前端和后端都需要进行一定的实现。前端需要在请求拦截器中进行token的检查和更新,后端需要实现一个token的刷新接口,并对旧的token进行验证。同时,还需要定义token的过期时间,并根据实际情况进行调整

响应拦截器的功能

响应拦截器是前端网络请求中一个非常重要的概念,它的主要功能是在从服务器接收到响应数据之后,对响应数据进行处理,然后再将其返回到调用处。

响应拦截器的主要功能包括以下几个方面:

  1. 错误处理:响应拦截器可以检查响应数据中是否存在错误信息,例如请求失败、权限不足等。如果存在错误信息,则可以根据实际情况进行处理,例如跳转到错误页面、显示错误信息等。
  2. 数据处理:响应拦截器可以对响应数据进行处理,例如对数据进行格式化、过滤、排序等操作。这样可以提高应用程序的可读性和可维护性。
  3. 统一处理:响应拦截器可以对所有的响应数据进行统一处理,例如添加一些公共的响应头、对返回的数据进行加密等操作。这样可以提高应用程序的安全性和可扩展性。
  4. token刷新:响应拦截器可以在响应数据中检查token的过期时间,如果即将过期,则可以自动进行token的刷新,从而实现无感刷新token的功能。
  5. 缓存处理:响应拦截器可以对响应数据进行缓存处理,例如将响应数据存储在本地存储中,以提高应用程序的性能和用户体验

反问

看我自己的兴趣,看我对之后的哪些技术比较感兴趣,然后说了好几分钟的一些我都没听过的前端技术/(ㄒoㄒ)/~~

一下子就感觉自己的前端之路才刚刚开始起步,后面的前端技术好多呀

楠哥会给我消息,最迟是明天

你知道你投递是小宇宙吧,我们公司小宇宙全是react,然后很详细的给我介绍了react里的很多东西