前端面试题详解整理92| 大文件上传,路由守卫,状态码,302,304,200,自定义拉伸节点尺寸,如何做到关闭页面,再打开时自动渲染上,

103 阅读20分钟

滴滴前端日常实习一面(3.8)

3.6号投的简历,3.8号就给我约面了(没想到这么快...我八股还没怎么背,算法题也没刷。。

项目一

  • 说一下思维导图的节点形状的逻辑处理

在前端技术开发中,思维导图的形状处理逻辑通常与所表达的内容和关系密切相关。以下是一些常见的形状处理逻辑:

  1. 矩形节点

    • 用于表示主要的技术概念、功能或模块。
    • 主要用于展示核心的前端技术、框架或库,如Vue.js、React、Angular等。
  2. 圆形节点

    • 通常用于表示与主要技术相关的子概念、工具或附属模块。
    • 可以表示与主要技术相关的辅助工具、插件、库等。
  3. 菱形节点

    • 用于表示决策点或者选择点。
    • 在技术选型或架构设计中,可以表示不同技术方案之间的选择或决策点。
  4. 椭圆形节点

    • 用于表示过程或者操作。
    • 可以表示开发流程中的不同阶段、步骤或者操作指南。
  5. 箭头节点

    • 用于表示不同节点之间的关系、依赖或流向。
    • 可以表示技术之间的引用关系、依赖关系、数据流向等。

在前端技术开发的思维导图中,节点的形状处理应该符合技术体系的逻辑结构,突出重点、清晰表达。合理选择节点形状有助于组织和展示前端技术的相关知识,帮助开发人员更好地理解和掌握技术体系,提高开发效率和质量。在前端技术开发中,思维导图的形状处理逻辑通常与所表达的内容和关系密切相关。以下是一些常见的形状处理逻辑:

  1. 矩形节点

    • 用于表示主要的技术概念、功能或模块。
    • 主要用于展示核心的前端技术、框架或库,如Vue.js、React、Angular等。
  2. 圆形节点

    • 通常用于表示与主要技术相关的子概念、工具或附属模块。
    • 可以表示与主要技术相关的辅助工具、插件、库等。
  3. 菱形节点

    • 用于表示决策点或者选择点。
    • 在技术选型或架构设计中,可以表示不同技术方案之间的选择或决策点。
  4. 椭圆形节点

    • 用于表示过程或者操作。
    • 可以表示开发流程中的不同阶段、步骤或者操作指南。
  5. 箭头节点

    • 用于表示不同节点之间的关系、依赖或流向。
    • 可以表示技术之间的引用关系、依赖关系、数据流向等。

在前端技术开发的思维导图中,节点的形状处理应该符合技术体系的逻辑结构,突出重点、清晰表达。合理选择节点形状有助于组织和展示前端技术的相关知识,帮助开发人员更好地理解和掌握技术体系,提高开发效率和质量。

* 如何做到关闭页面,再打开时自动渲染上一次的内容?

要实现关闭页面后再次打开时自动渲染上一次的内容,可以借助浏览器提供的本地存储功能,如LocalStorage或SessionStorage。以下是实现的基本步骤:

  1. 保存数据到本地存储: 当用户对页面内容进行更改时,将数据保存到本地存储中。可以使用LocalStorage或SessionStorage来存储数据,具体选择取决于需求。

    // 保存数据到LocalStorage
    localStorage.setItem('content', JSON.stringify(data));
    
  2. 页面加载时检索数据: 在页面加载时,检查本地存储中是否存在先前保存的数据。如果存在,则将其检索出来并渲染到页面上。

    // 检查本地存储中是否存在数据
    const savedData = localStorage.getItem('content');
    if (savedData) {
        const data = JSON.parse(savedData);
        // 将数据渲染到页面上
    }
    
  3. 实时更新本地存储: 如果用户在页面上进行了任何更改,确保及时更新本地存储中的数据。

    // 监听页面内容的变化,并实时更新本地存储
    window.addEventListener('change', () => {
        // 更新数据
        localStorage.setItem('content', JSON.stringify(data));
    });
    

通过这种方式,用户在关闭页面后再次打开时,可以自动加载之前保存的内容并渲染到页面上,实现了持久化存储和自动渲染的效果。需要注意的是,LocalStorage和SessionStorage都是同源的,即只能在相同的源(Origin)下访问和存储数据。

自定义拉伸节点尺寸的功能怎么做的?

要实现自定义拉伸节点尺寸的功能,你可以通过以下步骤进行操作:

  1. 确定用户交互方式

    • 用户可能会使用鼠标或触摸屏进行节点的拉伸。
    • 确定用户在节点上进行拉伸时应采取的操作,例如按住鼠标左键并拖动。
  2. 添加事件监听器

    • 监听用户在节点上按下鼠标左键(或触摸)的事件。
    • 监听用户移动鼠标的事件,以便在用户拖动时更新节点的尺寸。
    • 监听用户释放鼠标左键(或触摸结束)的事件,以便停止拖动并保存节点的新尺寸。
  3. 更新节点尺寸

    • 在鼠标移动事件中,计算鼠标移动的距离,并根据需要更新节点的宽度和高度。
    • 确保更新后的尺寸在合理的范围内,并避免节点尺寸变为负值。
    • 可以将节点的尺寸保存在组件的状态中,以便在组件重新渲染时保持节点的尺寸。
  4. 样式调整

    • 根据节点的新尺寸更新样式,以便在页面上正确显示拉伸后的节点。
  5. 可选 - 边界约束

    • 如果需要,可以添加边界约束,以防止节点被拉伸到超出容器或其他限制区域之外。

下面是一个简单的示例,演示了如何使用JavaScript和CSS实现可拉伸节点的功能:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Resizable Node</title>
<style>
  .resizable-node {
    width: 200px;
    height: 150px;
    background-color: lightblue;
    border: 1px solid #ccc;
    resize: both; /* 允许节点被拉伸 */
    overflow: auto; /* 当内容溢出时显示滚动条 */
  }
</style>
</head>
<body>

<div class="resizable-node" id="resizableNode">
  <!-- 可拉伸的内容区域 -->
</div>

<script>
  const resizableNode = document.getElementById('resizableNode');

  let startX, startY, startWidth, startHeight;

  resizableNode.addEventListener('mousedown', startResize);
  window.addEventListener('mouseup', stopResize);

  function startResize(e) {
    e.preventDefault();
    startX = e.clientX;
    startY = e.clientY;
    startWidth = parseInt(document.defaultView.getComputedStyle(resizableNode).width, 10);
    startHeight = parseInt(document.defaultView.getComputedStyle(resizableNode).height, 10);
    window.addEventListener('mousemove', resize);
  }

  function resize(e) {
    e.preventDefault();
    const width = startWidth + e.clientX - startX;
    const height = startHeight + e.clientY - startY;
    resizableNode.style.width = width + 'px';
    resizableNode.style.height = height + 'px';
  }

  function stopResize() {
    window.removeEventListener('mousemove', resize);
  }
</script>

</body>
</html>

这是一个简单的示例,当你在节点上按住鼠标左键并拖动时,可以调整节点的大小。你可以根据需要进行调整和扩展,以满足特定的设计要求和功能需求。

  • 怎么处理节点位置的?缩放、平移

处理节点位置的缩放和平移通常涉及以下步骤:

  1. 确定用户交互方式

    • 用户可能使用鼠标滚轮进行缩放。
    • 用户可能使用鼠标拖动来平移节点。
    • 确定用户应如何触发这些操作。
  2. 添加事件监听器

    • 监听鼠标滚轮事件以进行缩放。
    • 监听鼠标按下并拖动事件以进行平移。
  3. 缩放节点

    • 在鼠标滚轮事件中,根据滚轮的滚动方向和速度,调整节点的缩放比例。
    • 确保缩放比例在合理的范围内,并避免过度缩放。
  4. 平移节点

    • 在鼠标拖动事件中,计算鼠标的移动距离,并相应地调整节点的位置。
    • 可以在节点的样式中使用transform属性来移动节点,例如translateXtranslateY
  5. 可选 - 边界约束

    • 如果需要,可以添加边界约束,以防止节点被平移到超出容器或其他限制区域之外。

下面是一个简单的示例,演示了如何使用JavaScript和CSS实现节点的缩放和平移功能:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node Zoom and Pan</title>
<style>
  .zoomable-node {
    width: 200px;
    height: 150px;
    background-color: lightblue;
    border: 1px solid #ccc;
    overflow: hidden; /* 防止内容溢出 */
    cursor: grab; /* 设置光标样式为可拖动 */
  }

  .zoomable-node.zooming {
    cursor: zoom-in; /* 设置光标样式为缩放 */
  }
</style>
</head>
<body>

<div class="zoomable-node" id="zoomableNode">
  <div id="zoomableContent">
    <!-- 缩放和平移的内容区域 -->
  </div>
</div>

<script>
  const zoomableNode = document.getElementById('zoomableNode');
  const zoomableContent = document.getElementById('zoomableContent');

  let isDragging = false;
  let startX, startY, startScrollLeft, startScrollTop;

  zoomableNode.addEventListener('mousedown', startDrag);
  window.addEventListener('mouseup', stopDrag);
  zoomableNode.addEventListener('mousemove', drag);
  zoomableNode.addEventListener('wheel', zoom);

  function startDrag(e) {
    isDragging = true;
    startX = e.clientX;
    startY = e.clientY;
    startScrollLeft = zoomableNode.scrollLeft;
    startScrollTop = zoomableNode.scrollTop;
    zoomableNode.classList.add('dragging');
  }

  function stopDrag() {
    isDragging = false;
    zoomableNode.classList.remove('dragging');
  }

  function drag(e) {
    if (!isDragging) return;
    const deltaX = e.clientX - startX;
    const deltaY = e.clientY - startY;
    zoomableNode.scrollLeft = startScrollLeft - deltaX;
    zoomableNode.scrollTop = startScrollTop - deltaY;
  }

  function zoom(e) {
    e.preventDefault();
    const scale = e.deltaY > 0 ? 0.9 : 1.1; // 根据滚轮方向调整缩放比例
    zoomableContent.style.transform = `scale(${scale})`;
  }
</script>

</body>
</html>

这是一个简单的示例,允许用户使用鼠标拖动来平移节点,使用鼠标滚轮来缩放节点。你可以根据需要进行调整和扩展,以满足特定的设计要求和功能需求。

网络

  • 302 和 304 的区别?

  • 206有遇见过吗?416呢?

* 302 和 304 的区别:

  • 302 Found(临时重定向): 表示所请求的资源已被暂时移动到了其他位置,但未来可能会再次返回原始位置。客户端应该使用新的 URL 重新发起请求,并且所有的搜索引擎在更新它们的链接时应该使用新的 URL。

  • 304 Not Modified(未修改): 表示所请求的资源未被修改,可以直接使用客户端缓存的版本。通常在客户端发送带有 If-Modified-Since 或 If-None-Match 头部的条件 GET 请求时返回。服务器收到这样的请求后会检查资源的修改日期或者 ETag,如果资源没有修改,则返回 304 状态码,告诉客户端直接使用缓存的版本,无需重新下载。

  • 206、416:

    • 206 Partial Content(部分内容): 表示服务器已经成功处理了部分 GET 请求。在服务器响应中包含了 Accept-Ranges 和 Content-Range 头部,告诉客户端服务器接受范围请求,并且返回的内容是请求范围内的一部分。通常在客户端请求下载大文件的一部分或者断点续传时使用。
    • 416 Range Not Satisfiable(范围不符合要求): 表示客户端请求的范围无法满足。通常在客户端请求的范围超出了服务器所能提供的范围时返回。这可能是因为客户端请求的起始位置超过了文件的大小,或者范围请求格式错误导致服务器无法理解。

* 拉取服务器的音视频这种大文件时是怎么样的流程?

拉取服务器的音视频文件通常涉及以下流程:

  1. 建立连接: 客户端向服务器发起请求,请求获取特定音视频文件。
  2. 服务器响应: 服务器接收到请求后,会验证权限、文件合法性等,并根据请求的文件类型和大小进行处理。
  3. 分片传输: 如果音视频文件较大,服务器通常会支持分片传输,即将文件分成多个片段,客户端可以根据需要逐步获取这些片段。这可以通过 HTTP 分块传输、Range 请求等技术来实现。
  4. 传输数据: 服务器会按照客户端请求的范围或者分块的大小,逐个传输文件的片段。客户端收到每个片段后,可以进行缓存或者直接播放,也可以将这些片段合并成完整的文件。
  5. 播放处理: 客户端通常会将获取到的音视频数据进行解码和处理,然后通过相应的播放器进行播放。在播放过程中,客户端可能会根据网络情况自动调整请求策略,例如进行自适应码率调整等,以保证流畅的播放体验。
  6. 断点续传: 如果下载过程中出现网络中断或者其他问题,客户端通常支持断点续传功能,可以在恢复连接后继续下载未完成的部分,而无需重新下载整个文件。

整个流程中,客户端和服务器之间会通过 HTTP 或者其他协议进行通信,同时会根据实际需要和网络环境进行数据传输的优化,以提供稳定、高效的音视频文件传输服务。

手撕

打开共享屏幕,在自己的编译器上写

  • 二分查找
  • 深拷贝
  • 判断值类型的方法? 这里是关于这些主题的简要解释:
  1. 二分查找: 二分查找是一种在有序数组中查找特定元素的算法。它的基本思想是通过比较数组的中间元素与目标值的大小关系来确定目标值在数组的左半部分还是右半部分,从而缩小查找范围。如果中间元素等于目标值,则查找成功;如果中间元素大于目标值,则在左半部分继续查找;如果中间元素小于目标值,则在右半部分继续查找。这个过程不断重复,直到找到目标值或者确定目标值不存在于数组中。

  2. 深拷贝: 深拷贝是指在复制对象时将对象的所有嵌套对象都复制一份,而不是只复制对象的引用。这意味着深拷贝会递归地复制对象的所有属性和子属性,确保新对象和原对象完全独立。深拷贝通常用于避免原对象和拷贝对象之间的相互影响,特别是在处理嵌套对象或者多层对象结构时。

  3. 判断值类型的方法: 判断值类型的方法通常使用 JavaScript 的 typeof 操作符或者 Object.prototype.toString.call() 方法。typeof 操作符可以返回一个字符串,表示操作数的数据类型,包括 "undefined""boolean""number""string""object""function"。然而,typeof null 返回 "object",这是一个 JavaScript 中的历史遗留问题。为了更准确地判断值的类型,通常使用 Object.prototype.toString.call() 方法,该方法返回一个表示对象的内部属性 [[Class]] 的字符串。对于大多数内置对象,可以通过这种方式获取准确的类型信息,例如 [object Object] 表示普通的对象,[object Array] 表示数组,[object Function] 表示函数等。 以下是使用 JavaScript 实现二分查找、深拷贝和判断值类型的代码示例,带有详细注释:

// 二分查找函数
function binarySearch(arr, target) {
    let left = 0;
    let right = arr.length - 1;

    while (left <= right) {
        let mid = Math.floor((left + right) / 2);

        if (arr[mid] === target) {
            return mid; // 如果找到目标值,返回索引
        } else if (arr[mid] < target) {
            left = mid + 1; // 如果中间值小于目标值,更新左边界
        } else {
            right = mid - 1; // 如果中间值大于目标值,更新右边界
        }
    }

    return -1; // 如果未找到目标值,返回 -1
}

// 深拷贝函数
function deepCopy(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj; // 如果是基本类型或者 null,直接返回
    }

    let newObj = Array.isArray(obj) ? [] : {}; // 创建新对象,数组则为 []

    for (let key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            newObj[key] = deepCopy(obj[key]); // 递归复制属性值
        }
    }

    return newObj; // 返回新对象
}

// 判断值类型的函数
function getType(value) {
    return Object.prototype.toString.call(value).slice(8, -1);
}

// 测试示例
const arr = [1, 2, 3, 4, 5];
console.log(binarySearch(arr, 3)); // 输出:2

const obj = { a: 1, b: { c: 2 } };
const newObj = deepCopy(obj);
console.log(newObj); // 输出:{ a: 1, b: { c: 2 } }

console.log(getType([])); // 输出:Array
console.log(getType(null)); // 输出:Null

这段代码演示了如何实现二分查找、深拷贝和判断值类型的功能,并提供了详细的注释以便理解每个函数的作用。

看代码输出

一共9道题,考察一些比较细致的js特性

  • 涉及原始值类型、类型转换、装箱类型、拆箱类型
  • 考察对象的属性类型,及隐式类型转换,Symbol有使用场景吗?说几个
  • 考察闭包、作用域链

有两题讲错了,面试官还给我补充了一些知识

一些八股

  • 怎么做的自适应布局?讲一下flex?发来一张效果图,用flex实现,讲一下思路?用过媒体查询吗?
  • 自适应布局通常使用 CSS 的 flexbox 进行实现,下面是一个简单的示例以及对思路的解释:

首先,这是一个示例的 HTML 结构:

<div class="container">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
  <div class="item">Item 3</div>
</div>

接着,我们使用 CSS 的 flexbox 布局来实现自适应布局:

.container {
  display: flex; /* 使用 flex 布局 */
  justify-content: space-around; /* 主轴上均匀分布 */
  align-items: center; /* 交叉轴上居中对齐 */
}

.item {
  flex: 1; /* 设置项目在容器中的拉伸比例,即自适应布局 */
  padding: 10px;
  border: 1px solid #ccc;
  margin: 5px;
}

以上代码中,.container 使用了 display: flex; 来启用 flex 布局,justify-content: space-around; 设置了项目在主轴上均匀分布,align-items: center; 则使项目在交叉轴上居中对齐。.item 类的元素使用了 flex: 1;,这意味着它们将根据可用空间进行自适应布局。

关于媒体查询,它是一种 CSS 技术,用于在不同的屏幕尺寸下应用不同的样式。通过使用媒体查询,可以根据设备的特性,如宽度、高度、方向等,来为不同的设备提供不同的布局和样式。在实际项目中,通常会结合 flex 布局和媒体查询来实现响应式布局,以适应不同大小和分辨率的设备。

下面是一个示例的效果图,展示了 flex 布局的自适应效果:

在这张效果图中,三个项目均匀地分布在容器中,并且在不同屏幕尺寸下自动适应布局。

  • 用的css单位有哪些?rem的原理?给width设置100vh行不行?
  • 图片瀑布流中有大量图片,怎么给每个图片添加事件?讲了事件代理,冒泡,捕获等等,又问addEventListner对老ie浏览器的兼容方法?

Git

给了我三个场景,问我怎么做?前两个都不会。。汗流浃背了!早知道技术栈里不写Git了

场景题

  • 讲一下你对Vue路由的理解?原理?(为后面的业务场景题做铺垫)
  • 给我讲了一个业务场景,原生ios app+H5,点击首页的营销按钮,弹出一个H5的页面,然后监听用户离开页面的操作,弹出一个模态框,问是否确定离开。让我做我会怎么做?讲不出来,一直引导,问我了解过jsbrige吗?然后提示我往路由上想一想。。。还是讲不出来,面试官直接给我讲了,用双层路由。。讨论了十几分钟,最后还是模棱两可的,尴尬

针对这个业务场景,我们可以采取以下步骤:

  1. 原生 iOS App 部分:

    • 在原生 iOS App 中,点击首页的营销按钮时,触发打开一个内嵌的 WebView,并加载相应的 H5 页面。
    • 需要确保原生 App 和 H5 页面之间能够进行通信。这可以通过 iOS 的 JavaScriptCore 框架实现,使得原生 App 和 H5 页面可以相互调用对方的方法。
  2. H5 页面部分:

    • 在 H5 页面中,可以利用 JavaScript 来监听页面的离开操作。常用的事件有 beforeunloadunload。当用户尝试离开页面时,这些事件将被触发。
    • 在监听到页面即将离开时(比如用户点击返回按钮或关闭页面),触发一个事件,询问用户是否确定离开页面。
    • 如果用户确定离开页面,可以执行默认的离开操作。如果用户取消离开页面,可以阻止默认行为,保持当前页面不变。
  3. 通信与交互:

    • 当用户点击离开按钮时,H5 页面可以向原生 App 发送一个消息,通知原生 App 弹出一个模态框,询问用户是否确定离开。
    • 在原生 App 中,可以监听到 H5 页面发送的消息,接收到消息后,弹出一个模态框。
    • 用户在模态框中做出选择后,原生 App 可以根据用户的选择执行相应的操作。如果用户确定离开页面,则关闭当前的 WebView;如果用户取消离开页面,则保持当前页面不变。

总体思路是通过 H5 页面与原生 App 之间的通信,在 H5 页面中监听用户离开操作并触发事件,然后通过通信机制通知原生 App 弹出模态框,最后根据用户的选择执行相应的操作。

  • 问我了解过数据埋点相关的吗?
  • 除了我简历上写的这些性能优化方法,还有其他的吗?(我把我知道的都写上去了。。。硬想了两个简单答了了下

对于 Vue 路由的理解和原理,可以简单概括如下:

  1. **理解:**Vue 路由是用来实现单页面应用(SPA)的核心工具之一,它允许我们在应用中实现页面之间的导航和切换,而无需重新加载整个页面。通过定义路由,我们可以将不同的 URL 映射到相应的组件,实现页面的动态加载和渲染。

  2. **原理:**Vue 路由的原理基于浏览器的 History API 或 Hash 模式。在使用 History API 时,Vue Router 会利用浏览器提供的 pushState 和 replaceState 方法来修改浏览器历史记录,从而实现 URL 的变化而不引起页面的重新加载。而在使用 Hash 模式时,Vue Router 则会通过监听 URL 的 hash 变化来切换路由,以实现页面的导航。

关于业务场景题,原生 iOS App 与 H5 页面的交互涉及到了一些前端和原生端的协作,其中弹出模态框询问用户是否确定离开页面的操作可以通过双层路由来实现。具体做法是在原生 App 中加载 H5 页面时,将 H5 页面嵌入到原生的容器中,并且在容器中使用 Vue Router 来管理 H5 页面的路由。然后在 Vue Router 中定义全局的路由导航守卫,在用户尝试离开页面时触发弹窗询问用户是否确定。这样就可以实现在 H5 页面中监听用户离开页面的操作,并在需要时弹出模态框进行确认。

至于数据埋点相关的了解,数据埋点是一种监控和分析用户行为的技术,通过在页面中埋入特定的代码来收集用户的操作数据,从而进行统计分析和业务优化。在 Vue 应用中,可以通过引入第三方埋点工具或自定义代码来实现数据埋点,监控用户在页面上的行为和交互,并上报到后端进行分析。常见的埋点数据包括页面访问、点击事件、表单提交等用户行为。

除了性能优化方法外,还有一些其他的优化策略,例如代码分割(Code Splitting)、懒加载(Lazy Loading)、缓存策略(Cache Policy)等,都可以对应用的性能进行优化。同时,优化图片和资源的加载、使用 CDN 加速等也是常见的优化手段。

反问

面试官:我这边没什么问题了,大概率会给通过,约个二面。说他自身偏向业务方面,二面主要是考察技术深度,

我:

  • 对我的评价?人生第一次面试就面大厂,都紧张到语无伦次了。。。
  • 技术栈相关

#牛客在线求职答疑中心##如何判断面试是否凉了#

作者:孙兴憨学前端_Son
链接:www.nowcoder.com/discuss/596…
来源:牛客网