前端面试篇(1)

228 阅读14分钟

美团外包视频面试
(此文章中我自己的回答并没有写出来,下面是一些ai回答,可以参考一下)

求职者: 小gpt

1.自我介绍(然后根据之前项目问一些)

  • 平时用react多一点还是vue多一点
  • 在职还是离职
  • 上家公司的离职原因
  • 之前有没有大厂驻场经验
  • 上家公司平时主要负责什么业务
  • 在什么时机会去封装一个组件,封装组件的底层逻辑是什么
  • (追问,提到可复用)组件封装还有哪些原则
  • (提到h5项目的组件)平时会怎样去适配

嗨,我是[你的名字]。我是一名前端开发工程师,热爱编程和技术探索。

关于平时使用React和Vue的情况,我更多地使用React。React在我看来更加灵活和可扩展,适合构建大型应用。同时,React的生态系统也相当丰富,有助于提高开发效率。

目前我是在职状态,正在寻找更好的发展机会。

关于上家公司的离职原因,主要是希望能够寻找到更有挑战性的项目和更广阔的发展空间。

在上家公司,我主要负责前端开发工作,涉及到各种业务模块的前端开发和维护。

关于大厂驻场经验,目前我并没有这样的经验,但我对这样的工作环境和挑战非常感兴趣。

在我看来,封装一个组件的时机通常是当我们发现多个地方需要使用相似功能时。封装组件的底层逻辑涉及到对功能的抽象和通用化,需要考虑组件的接口设计、状态管理、数据传递等方面的问题。

关于组件封装的原则,我认为主要有以下几点:可复用性、可定制性、可扩展性和可维护性。我们需要保证封装的组件能够在不同的场景下复用,并且能够通过参数或者插槽等机制进行定制和扩展,同时也需要保证组件的代码结构清晰,方便维护和扩展。

在H5项目中,我会采用响应式设计和媒体查询等技术来进行适配,同时也会考虑使用flexbox和grid等CSS布局方式来实现页面的灵活布局。另外,我也会使用一些移动端适配方案,如rem、vw/vh等,以确保页面在不同设备上的展示效果良好。

  1. react的东西

平时怎样处理组件通信的,讲一下你知道的组件通信方式

组件通信是React开发中的重要话题,主要有以下几种方式:

  1. Props传递:通过将数据作为属性传递给子组件,实现父子组件之间的通信。
  2. Context API:React的Context API允许您将数据传递给组件树中所有组件,而不必手动传递props。
  3. 状态提升:如果两个组件之间共享状态,可以将该状态提升到它们的最近公共祖先中。
  4. 事件总线:使用事件总线库(如EventEmitter)在组件之间发送和接收事件。

平时用函数组件多一点还是类组件多一点

在React中,函数组件和类组件都是用来定义UI的组件。函数组件更加简洁,易于理解,而类组件具有更多的生命周期方法和状态管理能力。随着React Hooks的引入,函数组件可以使用状态和其他React功能,因此使用函数组件的情况越来越多。

平时使用那些hooks,讲一下它的注意事项

常用的Hooks包括useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef等。使用Hooks时需要注意以下几点:

  • 只能在函数组件顶层或其他自定义Hooks中调用Hook。不能在循环、条件判断或嵌套函数中调用。
  • Hook的调用顺序必须保持一致,不要在条件语句中调用Hook。

useMemo和useCallback用过吗,能讲一下吗

  • useMemo:用于优化性能,它会在依赖项发生变化时重新计算值,并将值缓存起来,只在依赖项变化时重新计算。

  • useCallback:用于缓存函数实例,依赖项发生变化时,会返回新的函数实例。适用于当函数作为回调传递给子组件时,避免不必要的重新渲染。

讲一下类组件的生命周期(你能根据它的执行时机依次说一下它的生命周期吗)

  • 挂载阶段:constructor -> static getDerivedStateFromProps -> render -> componentDidMount

  • 更新阶段:static getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate

  • 卸载阶段:componentWillUnmount

在函数组件中如何模拟componentDidMount、componentDidUpdate,componentWillunMount

  • 模拟componentDidMount:使用useEffect Hook,并将依赖项设置为空数组,确保只在组件挂载时执行一次。

  • 模拟componentDidUpdate:使用useEffect Hook,并在依赖项中传入需要监测的状态,以便在状态更新时执行。

  • 模拟componentWillUnmount:在useEffect Hook中返回一个清理函数,用于在组件卸载时执行清理操作。

讲一下hooks使用规则

  • 只在函数组件或自定义Hook中调用Hook。

  • Hook的调用顺序必须保持一致。

  • 只在React函数组件中调用Hook,不要在普通的JavaScript函数中调用Hook。

  1. js

js有几种数据结构

JavaScript中常见的数据结构包括:

  1. 数组(Array) :用于存储多个值的有序集合。
  2. 对象(Object) :用于存储键值对的集合。
  3. 字符串(String) :用于表示文本数据。
  4. 数字(Number) :用于表示数值。
  5. 布尔值(Boolean) :用于表示真或假。
  6. 函数(Function) :用于封装可执行的代码。
  7. Symbol:唯一的、不可变的数据类型,通常用作对象属性的键。
  8. Map:用于存储键值对的集合,可以使用任何数据类型作为键。
  9. Set:用于存储唯一值的集合。

如何判断js数据类型

可以使用 typeof 操作符来判断 JavaScript 中的数据类型。例如:

javascript
复制代码
typeof 42; // "number"
typeof "hello"; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object" (注意这是个历史遗留问题)
typeof {}; // "object"
typeof []; // "object" (数组也被认为是对象)
typeof function() {}; // "function"

讲一下什么是深拷贝浅拷贝

  • 浅拷贝:只复制一层对象的引用,如果对象内部还有对象,则这些内部对象仍然会被多个变量共享。
  • 深拷贝:复制对象所有层级的值,创建一个全新的对象,原对象和拷贝对象互不影响。

简单写(说)一下深拷贝。如何避免嵌套循环,比如说第一层有属性是引用的我们外层的

function deepCopy(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    
    let copy = Array.isArray(obj) ? [] : {};
    
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            copy[key] = deepCopy(obj[key]);
        }
    }
    
    return copy;
}

讲一下对闭包的理解

闭包是指函数和其相关的引用环境组合的组合体。简单来说,闭包可以访问其声明时所在的词法作用域以外的变量。闭包使得函数内部的变量在函数执行完毕后仍然可以被访问和使用。

讲一下对事件循环的理解

事件循环是JavaScript运行时的核心概念之一,它负责处理异步事件。事件循环持续监听执行栈和任务队列,当执行栈为空时,会从任务队列中取出任务并推入执行栈中执行。这个过程会不断循环执行,直到任务队列为空。

哪些会进入宏任务哪些会进入微任务

  • 宏任务(Macro Task):包括整体代码script、setTimeout、setInterval、I/O、UI Rendering等。

  • 微任务(Micro Task):Promise、process.nextTick等。

然后投屏看实践题,主页是settimeout,promise,process.nextTick几个的执行顺序,以及settimeout嵌套process.nextTick的执行顺序

然后说一下this

在JavaScript中,this 关键字代表当前执行上下文的对象。它的值取决于函数的调用方式。

  • 在函数中,this 的值取决于函数的调用方式:作为对象方法调用时,this 指向该对象;作为普通函数调用时,this 指向全局对象(在浏览器环境中是 window)。
  • 在箭头函数中,this 的值是在定义时确定的,而不是在调用时确定的,它会捕获所在上下文的 this 值。

投屏看实践题,直接调用函数,里面的this,执行对象中的函数,里面的this,然后对象中的函数用函数包裹,里面的this是什么

数组的splice方法,原地修改还是什么修改,参数是什么(第二个参数是什么),怎么用,要在数组中添加删除修改数据应该怎么使用

splice() 方法可用于修改数组内容。它的用法如下:

array.splice(start, deleteCount, item1, item2, ...);
  • start:表示开始删除或插入的位置。
  • deleteCount:表示要删除的元素个数,如果为0,则不删除任何元素。
  • item1, item2, ...:可选参数,表示要插入到数组中的元素。

splice() 方法会直接修改原始数组,并返回被删除的元素组成的数组。
4. css

讲一下对层叠上下文和z-index的理解

  • 层叠上下文(Stacking Context) :是HTML元素在垂直层叠顺序上的一种概念,决定了元素在垂直方向上的显示顺序。层叠上下文是由元素的某些属性形成的,如positionz-indexopacity等。

  • z-index:用于控制元素的堆叠顺序,决定了元素在水平方向上的显示顺序。z-index 只能应用于已经定位的元素(即 position 属性值为 absoluterelativefixed 的元素)。

讲一下css的盒子模型

CSS的盒子模型描述了HTML元素在页面布局中所占的空间,主要包括以下几个部分:

  • 内容区域(Content) :包含元素的实际内容,由 widthheight 属性定义。
  • 内边距(Padding) :内容区域与边框之间的空间,由 padding 属性定义。
  • 边框(Border) :包围内容区域的边框线,由 border 属性定义。
  • 外边距(Margin) :元素与其他元素之间的空间,由 margin 属性定义。

css的选择器有哪些,优先级怎样的,子元素选择器会增加优先级吗

常见的CSS选择器包括:

  • 元素选择器:例如 divp
  • 类选择器:例如 .class
  • ID选择器:例如 #id
  • 后代选择器:例如 div p
  • 相邻兄弟选择器:例如 div + p
  • 通用选择器:例如 *
  • 伪类选择器:例如 :hover:nth-child()
  • 伪元素选择器:例如 ::before::after

选择器的优先级顺序是:

  1. 重要性(!important
  2. 内联样式(Inline Styles)
  3. ID选择器
  4. 类选择器、属性选择器、伪类选择器
  5. 元素选择器、伪元素选择器

子元素选择器并不会增加选择器的优先级。

你知道伪类和伪元素吗,能讲一下吗

  • 伪类(Pseudo-class) :用于指定元素的特殊状态,如 :hover:focus:active 等。

  • 伪元素(Pseudo-element) :用于在元素的指定位置插入虚拟的元素,如 ::before::after

能讲一下你平时怎么清除浮动吗

清除浮动是指解决浮动元素造成的父元素高度塌陷的问题。常见的清除浮动的方法包括:

  1. 在父元素末尾添加一个空的块级元素,并设置 clear: both;
  2. 使用伪元素清除浮动:.clearfix::after { content: ""; display: table; clear: both; }

position这个属性能讲一下吗

position 属性用于指定元素的定位方式,常用的取值有:

  • static:默认定位方式,元素遵循正常文档流。
  • relative:相对定位,相对于元素自身在正常文档流中的位置进行定位。
  • absolute:绝对定位,相对于最近的已定位的父元素进行定位。
  • fixed:固定定位,相对于浏览器窗口进行定位。
  • sticky:粘性定位,相对于滚动容器(即离它最近的具有滚动条的祖先元素)和 viewport(如果滚动容器不存在)进行定位。

css的解析顺序是怎样的(从左到右还是从右向左)

CSS解析顺序是从右向左的。也就是说,当浏览器解析CSS时,会从右边的选择器开始匹配元素,然后向左依次匹配其祖先元素。这种解析顺序可以提高性能,因为浏览器可以先确定哪些元素符合右边的条件,然后再检查它们的祖先元素。

  1. 其它

你知道哪些是同源吗,然后非同源会收到哪些限制(同源策略)

同源指的是协议、域名、端口都相同的两个URL。同源策略是一种安全机制,限制了来自不同源的页面对当前页面的访问。

哪些不同会导致它不是同源

不同源的条件包括:协议、域名、端口。

跨域的话哪些行为会收到限制

非同源情况下会受到以下限制:

  • JavaScript无法访问非同源的页面的DOM。
  • XMLHttpRequest、Fetch等网络请求受到限制。
  • Cookie、LocalStorage和IndexedDB无法读取非同源的数据。

了解哪些跨域技术,讲一下

常见的跨域解决方案包括:

  1. JSONP:通过动态创建<script>标签来进行跨域请求。
  2. CORS:服务器端设置响应头中的Access-Control-Allow-Origin来允许跨域请求。
  3. 代理:通过服务器转发请求,实现跨域访问。
  4. iframe跨域通信:利用iframe的跨域特性进行通信。
  5. WebSocket:使用WebSocket协议进行跨域通信。

平时工作中用到过web存储吗,简单讲一下,以及它们的存储大小

Web存储是浏览器提供的一种数据存储机制,包括LocalStorage和SessionStorage。它们的区别在于生命周期和作用域不同。

  • LocalStorage:数据在浏览器关闭后仍然保存,因此可以持久化保存数据。
  • SessionStorage:数据在浏览器关闭后会被清除,只能在当前会话中保存数据。

它们的存储大小一般为5MB左右,但具体大小可能因浏览器和设置而异。

知道防抖和节流吗,简单讲一下

  • 防抖(Debounce) :在短时间内多次触发同一事件,只执行最后一次,或者只在开始时执行。常用于输入框输入验证等场景。

  • 节流(Throttle) :在一段时间内只执行一次事件处理函数。常用于滚动事件、resize事件等频繁触发的事件。

手写一下防抖函数

下面是一个简单的防抖函数的实现:

function debounce(func, delay) {
    let timer;
    
    return function() {
        const context = this;
        const args = arguments;
        
        clearTimeout(timer);
        timer = setTimeout(() => {
            func.apply(context, args);
        }, delay);
    };
}

这个防抖函数接收一个函数和延迟时间作为参数,并返回一个新的函数。当调用这个新函数时,它会在延迟时间内等待,如果在这个时间内再次调用该函数,则会清除之前的定时器并重新设置定时器。这样就实现了防抖的效果。
6. 算法题
a.合并有序数组输入nums1=[1,2,3,0,0,0],a=3,b=4,nums2=[2,5,8] 输出:[1,2,2,3,5,8] 要实现合并有序数组,可以使用双指针法,从两个数组的末尾开始比较,依次将较大的元素放入nums1数组的末尾。

function mergeSortedArray(nums1, m, nums2, n) {
    let i = m - 1; // nums1的末尾索引
    let j = n - 1; // nums2的末尾索引
    let k = m + n - 1; // nums1的当前插入位置索引

    while (i >= 0 && j >= 0) {
        if (nums1[i] > nums2[j]) {
            nums1[k--] = nums1[i--];
        } else {
            nums1[k--] = nums2[j--];
        }
    }

    // 将nums2中剩余的元素插入到nums1中
    while (j >= 0) {
        nums1[k--] = nums2[j--];
    }
    
    return nums1;
}

// 示例输入
const nums1 = [1, 2, 3, 0, 0, 0];
const a = 3;
const nums2 = [2, 5, 8];
const b = 3;

console.log(mergeSortedArray(nums1, a, nums2, b)); // 输出 [1, 2, 2, 3, 5, 8]

b.计算和,不能使用内置方法进行类型转换,输入是字符串的输出,输出结果字符串,输入a='9',b=‘1’ 输出‘10’ 要实现字符串加法,可以模拟手动进行十进制加法的过程,逐位相加并考虑进位。

function addStrings(a, b) {
    let i = a.length - 1;
    let j = b.length - 1;
    let carry = 0; // 进位标志
    let result = ''; // 结果字符串

    while (i >= 0 || j >= 0 || carry > 0) {
        const digitA = i >= 0 ? parseInt(a[i--]) : 0;
        const digitB = j >= 0 ? parseInt(b[j--]) : 0;
        const sum = digitA + digitB + carry;
        carry = Math.floor(sum / 10);
        result = (sum % 10) + result;
    }

    return result;
}

// 示例输入
const a = '9';
const b = '1';

console.log(addStrings(a, b)); // 输出 '10'

这段代码模拟了手动进行十进制加法的过程,逐位相加并考虑进位,最后得到结果字符串。