小编第一次面试吓尿了,赶快来写篇文章压压经

引言

前言小编前几个礼拜把项目给做完了,但是关于小编写的智能健身项目,小编预计要花一天时间,预计五篇关于该项目使用的技术栈,以及碰到代码当中的问题,将会通过五篇文章将自己纯手打造出来的项目进行详细的解析,并且对于项目当中的问题进一步修改。自此,小编也是开始了BOSS直聘上的投递,今天也是面了一家公司,发现自身也有很多八股不熟,话不多说来看下面试题吧!!!

一、es6的新特性

面试官:请你说说es6的新特性?

1.var、let、const之间的区别

在es5当中使用var声明的变量存在变量提升的情况

js 复制代码
console.log(a) // undefined
var a = 20

使用var,能够对一个变量进行多次声明,后面声明的变量会覆盖前面的变量声明

js 复制代码
var a = 20
var a = 30
console.log(a) // 30

在函数中使用var声明变量时候,该变量是局部的

js 复制代码
var a = 20
function change(){
    var a = 30
}
change()
console.log(a) // 20

let是ES6新增的命令,用来声明变量,只在let命令所在的代码块内有效

js 复制代码
{
    let a = 10
}
console.log(a) // ReferenceError: a is not defined.

不存在变量提升

js 复制代码
console.log(a) // 报错ReferenceError
let a = 2

使用let声明变量前,该变量都不可用,也就是大家常说的"暂时性死区"

js 复制代码
var a = 123
if(true){
    a = 'abc' // ReferenceError
    let a;
}

const声明一个只读的常量,一旦声明,常量的值就不能改变

js 复制代码
const a = 1
a = 3
// TypeError: Assisgnment to constant variable

如果之前用var或let声明过变量,再用const声明同样会报错

js 复制代码
var a = 20
let b = 20 
const a = 30
const b = 30
// 都会报错

const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动

对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,并不能确保改变量的结构不变

2.ES6新增数组扩展

一、扩展运算符的应用

ES6通过扩展元素符...,好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列

js 复制代码
console.log(...[1,2,3])
// 1 2 3
console.log(1, ...[2,3,4],5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [<div>,<div>,<div>]

主要用于函数调用的时候,将一个数组变为参数序列

js 复制代码
function push(array, ...items){
    array.push(...items)
}
function push(array,...items){
  array.push(...items)
}

const arr = [1,2,3]
push(arr,4,5,6)
console.log(arr);
// [1, 2, 3, 4, 5, 6]

数组的合并也更为简洁了

js 复制代码
const arr1 = ['a','b']
const arr2 = ['c']
const arr3 = ['d','e']
[...arr1, ...arr2, ...arr3]
// ['a','b','c','d','e']

注意:通过扩展运算符实现的是浅拷贝,修改了引用指向的值,会同步反映到新数组

二、构造函数新增的方法
  • Array.from() 将对象转为真正的数组
  • Array.of() 用于将一组值,转换为数组
三、实例对象新增的方法
  • copyWithin() 将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组
js 复制代码
[1, 2, 3, 4, 5].copyWithin(0, 3) // 将从 3 号位直到数组结束的成员(4 和 5),复制到从 0 号位开始的位置,结果覆盖了原来的 1 和 2
// [4,5,3,4,5]
  • find() 用于找出第一个符合条件的数组成员 参数是一个回调函数,接受三个参数依次为当前的值、当前的位置和原数组
javascript 复制代码
[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10
  • findIndex 返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
javascript 复制代码
[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2
  • fill() 使用给定值,填充一个数组
js 复制代码
['a','b','c'].fill(7)
// [7,7,7]
new Array(3).fill(7)
// [7,7,7]
  • entires().keys(),values() keys是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历
js 复制代码
for(let index of ['a','b'].keys()){
    console.log(index);
}
// 0 1
for(let elem of ['a','b'].values()){
    console.log(elem)
}
// 'a' 'b'
for(let [index,elem] of ['a','b'].entries()){
    console.log(index,elem);
}
// 0 "a"

3.es6对象上新增

-super关键字 this关键字总是指向函数所在的当前对象,ES6又新增了另一个类似的关键字super,指向当前对象的原型对象

js 复制代码
const name = {
    foo: 'hello world'
}
const obj = {
    foo: 'world',
    find(){
        return super.foo;
    }
};
Object.setPrototypeOf(obj,proto); // 为obj设置原型对象
obj.find() // 'hello'

4.es6函数新增扩展

一、参数
  • ES6允许为函数的参数设置默认值
js 复制代码
function log(x, y = 'World') {
  console.log(x, y);
}

console.log('Hello') // Hello World
console.log('Hello', 'China') // Hello China
console.log('Hello', '') // Hello
  • 函数的形参是默认声明的,不能用let或const再次声明
二、属性
  • 函数的length属性,length将返回没有指定默认值的参数个数
js 复制代码
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
  • 如果设置了默认参数不是尾参数,那么length属性也不再计入后面的参数了
js 复制代码
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
  • 返回该函数的函数名
js 复制代码
var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"
  • 如果将一个具名函数赋值给一个变量,则name属性都返回这个具名函数原本的名字
js 复制代码
const bar = function baz(){}
bar.name // "baz"
三、作用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。

下面例子中,y=x会形成一个单独作用域,x没有被定义,所以指向全局变量x

js 复制代码
let x = 1;

function f(y = x) { 
  // 等同于 let y = x  
  let x = 2; 
  console.log(y);
  console.log(x);
}

f() // 1 2
四、严格模式

只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错

javascript 复制代码
// 报错
function doSomething(a, b = a) {
  'use strict';
  // code
}

// 报错
const doSomething = function ({a, b}) {
  'use strict';
  // code
};

// 报错
const doSomething = (...a) => {
  'use strict';
  // code
};

const obj = {
  // 报错
  doSomething({a, b}) {
    'use strict';
    // code
  }
};
五、箭头函数
  • 使用"箭头"(=>)定义函数
js 复制代码
var foo = a => a
// 等同于
var f = function(v){
    return v;
}
  • 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
js 复制代码
var f = () => 5;
// 等同于
var f = function () {return 5};
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1,num2){
    return num1 + num2;
}

注意:函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象

二、react的虚拟dom

面试官:请你讲一下react的虚拟dom

简单来说,虚拟DOM是一个用javaScript对象来描述真实 DOM 结构的轻量级、内存中的表示。

  • 状态驱动视图:在React中,UI被看作是应用状态(state)的函数。当组件的状态或属性(props)发生变化时,React需要更新视图。
  • 生成新的虚拟DOM树:当状态变化发生时,React 会重新调用组件的 render 方法,生成一个新的虚拟 DOM 树。
  • Diff算法:它会将新生成的虚拟 DOM 树与上一次渲染后的旧虚拟 DOM 树进行比较来找出两棵树之间的差异。通过 Diff 算法,React 能够精确地计算出需要对真实 DOM 进行的最小修改集合(操作真实DOM是相对损耗也米娜性能的)。
  • 批量更新真实DOM:React会将计算出的最小更新操作批量应用到真实DOM上,从而完成界面的更新。这个过程通常是异步的,并且会进行优化。

三、useEffect

面试官:讲下react当中的useEffect

"useEffect 是 React Hooks 中最核心、最强大的 Hook 之一,它用于在函数式组件中处理副作用

jsx 复制代码
useEffect(() => {
  // 执行副作用操作
  const subscription = subscribe();
  const timer = setInterval(fetchData, 1000);

  // 可选的清理函数(Cleanup)
  return () => {
    // 清理副作用,例如取消订阅、清除定时器
    unsubscribe(subscription);
    clearInterval(timer);
  };
}, [/* 依赖数组 */]);

useEffect 的行为由它的依赖数组(Dependency Array) 控制:

  1. 没有依赖数组(或依赖数组为空):这个 effect 只在组件首次挂载(mount)时执行一次 。常用于初始化操作,如设置定时器、订阅事件、发送首次数据请求等。务必记得在清理函数中清除资源,防止内存泄漏。
  2. 有依赖数组:这个 effect 会在组件首次挂载时执行 ,并且每当依赖数组中的任意一个值发生变化时重新执行。这非常适合根据 props 或 state 的变化来同步执行操作,比如根据用户 ID 获取用户信息。
  3. 没有依赖数组(不推荐,除非明确需要):会在每次组件重新渲染后都执行。这通常会导致性能问题或无限循环(比如在 effect 中更新 state 导致再次渲染),应谨慎使用。
  4. 异步处理:useEffect 的回调函数不能是 async 函数(因为需要返回清理函数)。处理异步操作的正确方式是在内部定义一个 async 函数并立即调用它。
  5. 执行机制:在浏览器完成渲染之后异步执行的,返回的清理函数会在组件卸载时执行,并且在下一次effect执行前也会执行。

四、如何封装一个自定义hooks

面试官:看你项目当中封装了hooks,请你讲下如何封装的

在项目当中在实现页面的瀑布流实现当中,首先在展示组件当中封装了一个WaterFall组件。并且在WaterFall组件当中封装使用了ImageCard组件实现两列式布局,在ImageCard当中为了实现图片的懒加载和滚动懒加载,为了减少页面当中的重排和重绘,使用了useIntersectionObserverhooks函数组件。使用观察者模式对用户在使用该组件当中对页面进行滑动时,对图片的进入高度和离开页面高度进行监听,只有当用户不断下拉屏幕到下一张图片时,图片才会开始加载,实现对页面当中图片的懒加载。

五、Memo、useMemo、useCallback

面试官:请你讲讲项目中如何实现项目优化

Memo (用于优化函数式组件的渲染)

如果 props 没有变化,React 就会跳过该子组件的渲染,直接复用上一次的渲染结果。在项目当中,ImageCard当中使用了memo进行性能优化,因为在用户不断滑动屏幕当中,卡片组件当中的内容不会更改,因此在该组件当中使用Memo实现性能优化。

useMemo (缓存计算结果)

只有当依赖项发生变化时才会重新计算,避免每次渲染时进行昂贵的重复计算。

jsx 复制代码
  // 使用 useMemo 固定随机选择的标题和描述,避免重新渲染时刷新
  const { title, desc } = useMemo(() => {
    if (img.title && img.desc) {
      return { title: img.title, desc: img.desc }
    }
    const randomItem = detailFallback[Math.floor(Math.random() * detailFallback.length)]
    return {
      title: img.title || randomItem.title,
      desc: img.desc || randomItem.desc
    }
  }, [img.title, img.desc])

useCallback(缓存函数的引用)

useCallback用于缓存函数引用,只有当依赖项变化时才创建新函数。这对于防止因函数引用变化导致的子组件不必要重新渲染特别有用。

jsx 复制代码
import React, { useState, useEffect, useCallback } from 'react';

function ExampleComponent({ fetchData }) {
  const [data, setData] = useState(null);

  // 使用 useCallback 来确保 fetchData 回调函数的引用稳定性
  const fetchHandler = useCallback(async () => {
    const result = await fetchData();
    setData(result);
  }, [fetchData]); // 注意这里添加了 fetchData 作为依赖

  useEffect(() => {
    fetchHandler();
  }, [fetchHandler]); // 只有当 fetchHandler 发生变化时才会触发 effect

  return (
    <div>
      {data ? <p>Data: {data}</p> : <p>Loading...</p>}
    </div>
  );
}

六、diff算法

面试官:请你讲讲react当中的diff算法

React 的核心思想是"数据驱动视图",当组件的 state 或 props 发生变化时,React 会重新渲染组件,生成新的虚拟 DOM 树。如果每次都直接操作真实 DOM 进行全量更新,性能开销会非常大。React的diff算法基于三个核心假设,将O(n^3)的复杂度降低到O(n),从而实现高效更新。策略一:同层比较,策略二:类型比较,策略三:列表对比-key的作用。

总结 Diff 算法的三大要点

策略 作用 关键点
同层比较 避免跨层级移动 层级变化 = 重建
类型比较 复用相同类型节点 类型不同 = 重建
列表对比 高效处理列表更新 key 必须稳定、唯一

七、SSE

面试官:请你讲讲流式输出

流式输出是一种数据传输方式,它允许服务器在数据生成的过程中,将其分块、连续地发送给客户端,而不是等待所有数据都处理完毕后再一次性发送。持续不断地从源头(服务器)流向目的地(客户端)。一旦生成了第一个数据块就立即通过网络发送给客户端。客户端接收到第一个块后,就可以开始处理,而无需等待后续数据。而在前端实现流式输出,显著降低了首字节时间(TTFB),减少了服务器内存压力,并实现了真正的实时性。

八、jwt鉴权登录

面试官:讲讲你是如何实现注册登录的

好的,面试官,作为一名前端开发工程师,很乐意为您讲解在现代Web应用中实现注册登录功能的完整思路和关键技术点。其核心目标是安全、可靠、用户体验良好地验证用户身份并管理会话。 而我在项目当中是通过使用JWT鉴权登录的方式实现页面的登录和注册,因为它在现代SPA和API架构中非常流行。

  • 状态管理:使用了轻量型的状态管理库Zustand管理用户登录状态,确保应用在任何组件都能感知用户是否已经登录。'
  • 路由守卫:对于需要登录才能访问的页面,在路由层面进行拦截,在路由跳转前检查本地token和用户状态。
  • API请求拦截器:使用axios拦截器,在每次发送请求前,检查是否存在Token,如果存在,自动将其添加到请求头中。拦截401状态码,清除本地存储的Token和用户状态,重定向用户到登录页,并提示"登录已过期,请重新登录"。
  • HTTPS: 必须使用,防止中间人窃取密码和Token
  • 前端响应:收到成功响应,解析出 JWT Token,安全存储Token,- localStorage : 最常用。优点是持久化,刷新页面不失效。缺点是易受 XSS 攻击 (恶意脚本可读取)。必须确保网站自身没有 XSS 漏洞

总结

总的来说第一次参加面试还是很紧张的,特别是面试官在问第一道题目的时候,因为第一次面试很多内容都记不起来。在面试的过程中当中,跟面试官交流的情况还是很不错的,对于项目当中实现的功能自己也是能很自信的讲出来,对于面试官问的很多八股这块不会的,自己也是即时记录下来,写下文章,加深对其的理解。其实大家对于面试不用太害怕,面试官就算是又秃又强的也不要紧,大家正常回答既可。

相关推荐
vaelcy1 分钟前
css3实现登录框动画特效效果
前端·css
嘟嘟MD2 分钟前
程序员副业 | 2025年7月复盘
后端·程序员·创业
码出极致12 分钟前
MySQL 与 MongoDB 深度对比:从数据模型到实战场景的核心差异解析
后端·面试
码出极致15 分钟前
Elasticsearch 与 Solr 核心差异深度解析:从架构到场景的实战记忆指南
后端·面试
一枚前端小能手20 分钟前
🚀 Webpack构建等到怀疑人生?试试这几个优化
前端·webpack
入秋34 分钟前
2025年项目中是怎么初始化Three.js三维场景的
前端·three.js
码出极致35 分钟前
RocketMQ 和 Kafka有什么区别
后端·面试
托尼_Captain40 分钟前
uniapp封装全局request请求
前端
ze_juejin1 小时前
Fetch API 详解
前端
R-G-B1 小时前
【19】万集科技——万集科技嵌入式,校招 一面,二面,面试问答记录
面试·万集科技·万集科技嵌入式面试问答记录·万集科技嵌入式面试记录·万集科技嵌入式面试·万集科技嵌入式一面·万集科技嵌入式二面