场景

833 阅读16分钟

HTML

服务端渲染和客户端渲染的区别?

客户端渲染:

  • 页面的渲染工作都是由浏览器来完成的,服务器只是负责提供数据。
  • 客户端渲染能尽早的把页面展示给用户,用户体验好
  • 不容易被爬虫爬取数据,同时也无法被搜索引擎搜索到

服务器渲染:

  • 页面渲染的工作都是由服务端来完成的,数据也是由服务端提供的,浏览器只负责展示页面内容
  • 容易被爬虫爬取数据,同时能被搜索引擎搜索到,能在搜索引擎中向用户展示数据

那是用客户端渲染好还是服务器渲染好呢?
其实一般的页面中,两种渲染是相结合着使用的,因为我们会有些数据不想过早的传过来,想要被用到时再去拿数据,同时也不想被爬虫那么轻易的爬取,而且也不需要被搜索引擎搜索并展示,我们就可以用客户端渲染,例如我们本文中提到的商品评论信息。
但有时,我们想让我们网页的数据被搜索引擎搜索到,能让用户在使用搜索引擎的时候,查找到我们的数据信息,我们就可以使用服务器渲染。例如我们本文提到的电商网站的商品的基本信息。

onloaded和DOMContentLoaded事件

DOMContentLoaded

当初始html文档完全加载并解析之后触发,无需等待样式、图片、子frame结束。作为明显的对比,load事件只有一个页面完全被加载时才触发。改用DOMContentLoaded的地方常常是load来代替,这是错误的。

load

当一个资源及其依赖的资源结束加载时触发。从这里可以看到需要等待依赖资源的结束加载。

!DOCTYPE 的作用是什么?

在 HTML 中, 声明是文档类型声明(Document Type Declaration)的缩写,用于指示浏览器当前页面使用的HTML版本。

 声明位于文档中的最前面的位置,处于 html 标签之前。

CSS

改变小图标的颜色,hover时颜色变深

  1. 思路:使用活跃色的图片素材,给它一个灰色的滤镜。hover的时候取消这个滤镜,还原活跃色。filter:grayscale(x%):将图像转换为灰度图
  2. 示例:
img {  
filter: grayscale(100%);  
opacity: 0.6;  
}

img:hover {  
filter: none;  
opacity: 1;  
}

效果:

屏幕截图 2024-04-20 102606.png

圣杯布局、双飞翼布局

flex布局,一行5个卡片,最后一行左对齐

// 最外层父元素
.container {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
 
    // 卡片
    > .card {
        height: 150px;
        flex: 0 0 18%; //每一个盒子占18%
        min-width: 18%;
        margin-right: calc(9.99% / 4);
        margin-bottom: calc(9.99% / 4);
 
        // 每行最后一个卡片
        &:nth-child(5n) {
          margin-right: 0;
        }
 
        // 最后一个卡片
        &:last-child {
          margin-right: auto;
        }
    }
}

css预处理器:less,scss

介绍

  1. less
  • 变量(Variables):你可以使用@符号来定义和引用变量。
  • 嵌套(Nesting):你可以在Less中使用嵌套规则来组织样式规则。
  • 运算(Operations):Less允许你在样式中执行简单的算术运算,如加法、减法、乘法和除法。
  • 导入(Import):你可以使用@import指令将其他的Less文件导入到当前文件中。这样可以将样式分为多个文件,并在需要时将它们合并到一个文件中。
  1. scss
  • 变量(Variables):SCSS引入了变量的概念,你可以使用$符号定义变量,并在整个样式表中引用它们。
  • 嵌套(Nesting):SCSS允许在样式规则中使用嵌套的语法,以表示样式的层次结构和关系。
  • 继承(Inheritance):SCSS允许通过@extend关键字实现样式的继承。这样可以减少重复的代码。
  • 运算(Operations):SCSS也支持在样式中执行算术运算。你可以在属性值中使用算术表达式,并使用+-*/等运算符。
  • 导入(Import):你可以使用@import指令将其他的SCSS文件导入到当前文件中。这样可以将样式分为多个文件,并在需要时将它们合并到一个文件中。

区别

  • 语法:SCSS是基于CSS的超集,它使用和CSS相同的语法,并通过添加一些新的功能和规则来扩展CSS。因此,任何有效的CSS样式表都是有效的SCSS样式表。相比之下,Less引入了一些新的语法元素,例如使用@符号定义变量和混合等。

  • 处理器:SCSS使用的是Sass(Syntactically Awesome Style Sheets)处理器,它在Sass语法的基础上添加了CSS兼容性。Sass提供了两种语法格式:SCSS和Sass(缩进风格)。而Less则使用Less处理器,它的语法更接近于常规的CSS。

  • 嵌套规则:SCSS和Less都支持嵌套规则,允许在父选择器下编写样式规则。然而,它们的嵌套语法略有不同。在SCSS中,使用大括号({})来表示嵌套,类似于CSS。而在Less中,使用小括号(())来表示嵌套。

  • 变量符号:在SCSS中,变量使用$符号进行定义和引用,例如$primary-color: #ff0000;。而在Less中,变量使用@符号,例如@primary-color: #ff0000;。

  • 混合(Mixins):SCSS和Less都支持混合的概念,可以将一组样式规则集合起来并在需要时重用。在SCSS中,使用@mixin关键字定义混合,并使用@include关键字引用它。而在Less中,使用.mixin-name()定义混合,并使用.mixin-name;引用它。

  • 继承(Inheritance):SCSS和Less都支持样式的继承。在SCSS中,使用@extend关键字来实现样式的继承。而在Less中,使用&符号来表示父选择器,实现样式的继承。

JS

一次获取一万条数据,如何渲染?

虚拟列表

参考文章:定高的虚拟列表会了,那不定高的...... 哈,我也会!看我详解一波!🤪🤪🤪 - 掘金 (juejin.cn)

可能的拓展问题

如何优化用户体验?(自己整理)

  1. 滚动条滚动到一半时就向后端请求资源,防止突然的展示和卡顿。
  2. 滚动条监听事件做防抖处理,防止频繁请求。
  3. 懒加载

从url中获取指定参数

function getQueryString(name) {
  // let url = window.location.href;
  const url_string = "https://www.baidu.com/t.html?name=mick&age=20"; // window.location.href
  const url = new URL(url_string);
  return url.searchParams.get(name);
}

console.log(getQueryString('name')) // mick

用原生JS实现懒加载、轮播图等功能

参考文章:分享一些前端常用功能集合 - 掘金 (juejin.cn)

// 获取所有图片
const imgList = document.querySelectorAll('img')
// 用于记录当前显示到了哪一张图片
let index = 0;
function lazyload() {
  // 获取浏览器视口高度,这里写在函数内部是考虑浏览器窗口大小改变的情况
  const viewPortHeight = window.innerHeight || document.documentElement.clientHeight
  for (let i = index; i < imgList.length; i++) {
    // 这里用可视区域高度减去图片顶部距离可视区域顶部的高度
    const distance = viewPortHeight - imgList[i].getBoundingClientRect().top;
    // 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明图片已经出现在了视口范围内
    if (distance >= 0) {
      // 给图片赋值真实的src,展示图片
      imgList[i].src = imgList[i].getAttribute('data-src');
      // 前i张图片已经加载完毕,下次从第i+1张开始检查是否需要显示
      index = i + 1;
    }
  }
}

// 定义一个防抖函数
function debounce(fn, delay = 500) {
  let timer = null;
  return function (...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 页面加载完成执行一次lazyload,渲染第一次打开的网页视口内的图片
window.onload = lazyload;
// 监听Scroll事件,为了防止频繁调用,使用防抖函数进行优化
window.addEventListener("scroll", debounce(lazyload, 600));
// 浏览器窗口大小改变时重新计算
window.addEventListener("resize", debounce(lazyload, 600));


JS为什么是单线程的?

  1. 可以用webworker开启多线程
  2. JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

实现sleep函数

方法一:不阻塞同步任务的执行

// sleep 函数--Promise 版本
  function sleep(time) {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, time);
    });
  }
  sleep(1000).then(fnA); // 1 秒后输出 A

方法二:阻塞同步任务的执行

// sleep 函数--Promise 版本
  function sleep(time) {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, time);
    });
  }
  async function sleepTest() {
    fnA();                // 输出 A
    await sleep(1000);    // 睡眠 1 秒
    console.log('E');     // 输出 E
    fnB();                // 输出 B
    await sleep(1000);    // 睡眠 1 秒
    fnC();                // 输出 C
    await sleep(1000);    // 睡眠 1 秒
    console.log('G');     // 输出 G
  }

虚拟DOM构建为真实DOM

CommonJS和ES6的区别

  1. 对于模块的依赖,CommonJS是动态的,ES6 Module 是静态的
  2. CommonJS导入的是值的拷贝,ES6 Module导入的是值的引用

require和import的区别?

规范

require符合CommonJS规范,而import符合ES6标准。因此,在使用时需要根据不同的规范选择相应的引入方式。

静态/动态

require是静态加载,也就是说,在编译时就会加载所需模块。而import支持动态加载,可以在程序运行时根据需要进行加载。

其他

假如说接口响应拦截错误会有全局统一的弹出错误,那如果有些路由他是不需要这个弹出错误的,那么该怎么实现?

axios 的入参扩展一个自定义参数(skipErrorHandler), 在响应拦截器中拿到这个参数, true表示忽略统一的错误处理逻辑, false反之

npm run dev是如何将本地资源打包编译运行在浏览器上的

执行npm run dev命令后会执行一个脚本 使用构建工具对项目文件进行打包编译并输出到特定的目录 然后启动了本地开发服务器并监听本地主机和端口号 浏览器输入http://localhost:xxxx后会发送请求访问本地开发服务器的资源 服务器收到请求就会返回对应的html文件

网络和设备硬件均一致的情况导致首页白屏时间不一致的原因

  1. 原因:
  • 浏览器差异:不同的浏览器或浏览器版本对网页的渲染速度可能有所不同。
  • 缓存策略:浏览器的缓存策略可能导致加载速度的差异。例如,如果某个浏览器缓存了某些资源,而其他浏览器没有,那么加载速度就会有所不同。
  • JavaScript执行:JavaScript的执行速度和效率可能因浏览器和版本的不同而有所差异,从而影响页面的渲染时间。
  • 服务器响应:服务器的响应速度、带宽、负载等因素也会影响页面的加载时间。
  • 第三方服务:如果首页依赖了第三方服务(如CDN、字体、统计工具等),这些服务的响应速度和稳定性也会影响首页的加载时间。
  • DNS解析:DNS解析的速度和稳定性也可能影响页面加载时间。
  • 用户地理位置:用户地理位置的不同可能会导致访问服务器时的网络延迟差异。
  • 页面内容动态加载:如果页面内容是通过异步加载(如AJAX)的方式获取的,那么加载速度可能会因网络状况、服务器响应速度等因素而有所不同。
  1. 方法:
  • 使用性能监控工具来分析和定位问题。
  • 优化JavaScript代码,减少执行时间。
  • 优化图片和其他资源,减少加载时间。
  • 考虑使用CDN来加速资源的加载。
  • 对服务器进行优化,提高其响应速度和带宽。
  • 考虑使用缓存策略来减少重复加载相同资源的时间。
  1. 如果是CDN(内容分发网络)的原因导致资源加载时间不一致,可能是由以下因素造成的:
  • 源站的带宽限制:当源站的带宽达到峰值时,会导致访问源站的速度变慢,从而影响CDN边缘服务器上的资源响应速度。
  • CDN配置问题:CDN需要正确配置以实现资源访问与源站一致。如果配置不当,可能会导致资源加载时间不一致。
  • 源站和CDN的缓存规则不合适:如果源站资源和CDN的边缘资源未正确缓存,可能会导致资源加载时间不一致。
  • 网络抖动或不稳定:网络的不稳定性可能导致CDN的边缘服务器与源站之间的通信受到影响,从而影响资源加载时间。
  • CDN服务器数量不足:如果CDN服务器数量不足,可能导致网络容量不足和稳定性下降,从而影响资源加载时间。

为了解决这些问题,可以采取相应的措施,如优化源站服务器性能、确认CDN设置正确、审查源站的防火墙规则、增加CDN服务器数量、优化网络拥塞等。

pc端如何实现扫码登录

轮询或websocket获取二维码状态 image.png

参考文章:面试官:如何实现扫码登录功能? - 掘金 (juejin.cn)

如何自动保存表单数据,防止页面崩溃、用户异常退出时数据未保存的问题

自动保存(个人设想,待实践)

localstorage定时存储表单数据,当异常关闭后进入表单填写页面。判断localstorage是否存在该对象,存在则提示用户是否继续填写,该字段在表单正常提交后被清空。

参考链接:vue--localStorage临时存储未提交表单数据,下次登陆后展示上次未提交的数据_vue 离开页面前,实现表单数据保存,回来页面后重新展示-CSDN博客

提交错误时正常回跳

keep-alive包裹(?)

SPA首屏加载慢该如何解决?

首屏时间计算

// 方案一:
document.addEventListener('DOMContentLoaded', (event) => {
    console.log('first contentful painting');
});

加载慢的原因

  • 网络延时问题
  • 资源文件体积是否过大
  • 资源是否重复发送请求去加载了
  • 加载脚本的时候,渲染内容堵塞了

解决方案

  1. 减小入口文件体积 常用的手段是路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加
  2. 静态资源本地缓存

后端返回资源问题:

  • 采用HTTP缓存,设置Cache-ControlLast-ModifiedEtag等响应头
  • 采用Service Worker离线缓存

前端合理利用localStorage

  1. 图片资源的压缩:字体图标、雪碧图

权限管理相关

参考链接:面试官:vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做? | web前端面试 - 面试官系列 (vue3js.cn)

Node

Node.js和浏览器之间的区别

1. 环境的不同

Node.js和浏览器的环境非常不同,由于其不同的目的,它们之间的一些关键区别包括:

  • 浏览器在宿主环境中执行JavaScript,在客户端执行。浏览器提供了一个表示网页结构的文档对象模型(DOM)。
  • Node.js提供了一个运行时环境,允许JavaScript在服务器上运行,不在浏览器中。此外,它没有像Web浏览器一样的DOM。

2. 用户界面

一个主要的区别是执行方式。浏览器在其宿主环境中执行JavaScript并渲染HTML、CSS和JavaScript,并提供图形用户界面(GUI)与网页进行交互,因为用户与网页进行交互,所以使用GUI是必不可少的。但是Node.js使用命令行界面(CLI),因为它与用户没有交互,并且用于服务器端开发构建Web服务器和API。

3. 用法不同

由于两者都执行JavaScript,它们的用途是使它们不同的关键点。浏览器用于客户端,执行JavaScript在浏览器的宿主环境中,开发者使用JavaScript使Web内容动态化。另一方面,Node.js是用于执行JavaScript的运行时环境,用于服务器端开发创建Web服务器和API,因此JavaScript在这里主要用于逻辑构建和算法部分。

NodeJS事件循环

  1. 6个阶段
  • Timers:用于存储定时器的回调函数(setlnterval,setTimeout)。
  • Pendingcallbacks:执行与操作系统相关的回调函数,比如启动服务器端应用时监听端口操作的回调函数就在这里调用。
  • idle,prepare:系统内部使用。
  • Poll:存储1/O操作的回调函数队列,比如文件读写操作的回调函数。

在这个阶段需要特别注意,如果事件队列中有回调函数,则执行它们直到清空队列 ,否则事件循环将在此阶段停留一段时间以等待新的回调函数进入。

但是对于这个等待并不是一定的,而是取决于以下两个条件:

    • 如果setlmmediate队列(check阶段)中存在要执行的调函数。这种情况就不会等待。
    • timers队列中存在要执行的回调函数,在这种情况下也不会等待。事件循环将移至check阶段,然后移至Closingcallbacks阶段,并最终从timers阶段进入下一次循环。
  • Check:存储setlmmediate的回调函数。
  • Closingcallbacks:执行与关闭事件相关的回调,例如关闭数据库连接的回调函数等。 image.png
  1. 宏任务与微任务
  • 主线程同步代码执行完毕后,会先执行微任务再执行宏任务。
  • 虽然nextTick同属于微任务,但是它的优先级是高于其它微任务,在执行微任务时,只有nextTick中的所有回调函数执行完成后才会开始执行其它微任务。
  • 在事件循环的六个阶段每个阶段执行完后会清空微任务队列。
  1. 与浏览器时间循环的区别?
  • 在浏览器事件循环中,每执行完一个宏任务,便要检查并执行微任务队列;而node事件循环中则是在“上一阶段”执行完,“下一阶段”开始前执行微任务队列中的任务。
  • 在浏览器事件循环中,process.nextTick()属于微任务,而且和其他微任务的优先级是一样的,不存在哪个微任务的优先级高就先执行谁。但是在node中,process.nextTick()的优先级要高于其他微任务,也就是说,在两个阶段之间执行微任务时,若存在process.nextTick(),则先执行它,然后再执行其他微任务。

多线程实现

方法

1. 使用 Worker:通过 Worker 类可以创建和管理工作线程。例如,可以创建一个新的线程,并传递一个 JavaScript 文件给该线程执行:

const { Worker, isMainThread, parentPort } = require('worker_threads');
if (isMainThread) {
  const worker = new Worker('./my-worker.js');
  // 主线程的逻辑
} else {
  // 工作线程的逻辑
  parentPort.postMessage('来自工作线程的问候');
}

2. 利用线程池:Node.js 允许创建线程池,为多个任务创建线程并分配给它们,这对于需要执行大量短期任务的情况非常有用。可以使用 workerpool 模块来实现线程池。

const WorkerPool = require('workerpool').pool;
const pool = WorkerPool({ maxWorkers: 4 });
pool.exec(someTask).then(result => {
  // 处理结果
});

3. 使用 worker_threads 模块:Node.js 内置的 worker_threads 模块提供了对多线程的支持,通过使用 Worker 类和其他相关的 API 来创建和管理线程。

const { Worker, isMainThread, parentPort } = require('worker_threads');
if (isMainThread) {
  const worker = new Worker('./my-worker.js');
  // 主线程的逻辑
} else {
  // 工作线程的逻辑
  parentPort.postMessage('来自工作线程的问候');
}

使用场景

在 Node.js 中,以下几种情况下使用多线程是较为合适的:

  1. CPU 密集型操作:对于需要大量计算的应用,比如图像处理、加密破解或数据分析,多线程可以大幅提高性能。
  2. I/O 密集型工作:虽然 Node.js 在处理 I/O 操作上已经相当高效,但在并发处理大量数据库查询或文件操作的场景下,多线程能够提升吞吐量。
  3. WebSocket 服务器:WebSocket 服务器可以采用多线程来管理连接,确保实时通信的及时响应。
  4. 网络爬虫与数据抓取:在爬虫和数据抓取等应用中,采用多线程并行处理不同的网页或数据源,能够加快数据获取速度。