当前位置: 首页 > 面试经验 >

快手前端面试

优质
小牛编辑
63浏览
2024-09-15

快手前端面试

又金九银十了,把今年的面试记录翻了翻,发现还不少,趁着这个机会总结一下

这是24年春季招聘的面试题

面试题目

1、dispaly设置none会引起什么,渲染的时候,dom树和渲染树是一一对应的么

2、手写几种元素水平垂直居中方法

3、transform相较于margin的好处

4、position定位

5、移动端适配

6、vw、vh、rem、em有什么区别

7、手写准确判断数据类型方法以及返回的示例

8、上面slice中的参数表示什么

9、其他的类型判断方式与区别

10、手写instanceof方法

11、手写一个JS实现继承方式

12、节流防抖的区别,手写防抖

13、如何封装组件

14、如何提高前端页面性能

15、xss攻击了解吗?

16、csrf呢?localstorage存了token,怎么解决?

17、vue源码了解吗,模板编译,render函数,响应式原理,watcher和dep,patch,diff算法,computed的实现,nexttick的实现知道吗?

18、项目中做过rbac权限控制token吗,简单说说?

19、算法题:反转链表

面试详解

1、dispaly设置none会引起什么,渲染的时候,dom树和渲染树是一一对应的么

元素不会在页面上被显示,它将从文档流中移除,并且不会占据任何空间。其他元素将会填补其位置。元素的事件监听器将被禁用,不再与页面交互。

DOM 树和渲染树并不是一一对应的,DOM 树是由浏览器解析 HTML 文档而生成的,它表示了整个文档的结构和内容,包括 HTML 元素、文本节点和属性等。DOM 树中的每个节点都对应着 HTML 中的一个元素或文本。

渲染树是在 DOM 树和 CSSOM 树结合之后生成的,用于表示页面上需要渲染的元素及其样式信息。渲染树中的节点称为渲染对象(Render Object),它们与 DOM 树中的节点相对应,但并不是一一对应的关系。

在构建渲染树时,一些不需要显示的节点(如 <script><head>display: none 和一些不可见的元素)会被省略,而只有需要显示的元素才会被包含在渲染树中

2、手写几种元素水平垂直居中方法

  1. 使用flex布局设置居中。
  2. 使用flex 时也能通过给子项设置margin: auto实现居中。
  3. 使用绝对定位的方式实现水平垂直居中。
  4. 使用grid设置居中。
  5. 使用grid时还能通过给子项设置margin: auto实现居中。
  6. 使用tabel-cell实现垂直居中。
  7. 还有一种不常用的方法实现垂直居中。
  8. 最后还有一种奇葩的方法。容器设置position: relative。孩子设置 top、left、bottom、right都设置为0

详细的实现方式:我已经说了5种css居中实现的方式了,面试官竟然说还不够

3、transform相较于margin的好处

  1. 性能更好:使用 transform 属性来实现元素的移动、旋转等操作,浏览器不需要重新计算元素的布局和绘制,而是仅对元素进行单纯的变换。这样可以减少浏览器的重排(reflow)和重绘(repaint)操作,从而提高页面的渲染性能。
  2. 不影响文档流:使用 margin 属性来实现元素的移动会影响到元素所在的文档流,可能会导致其他元素的位置发生变化。而使用 transform 属性可以将元素的位置进行调整,但不会影响到其他元素的布局和位置。
  3. 可以使用硬件加速:一些浏览器支持使用 GPU 进行硬件加速来执行 transform 变换,这样可以进一步提高页面的渲染性能。

4、position定位

  1. static(默认值):元素处于正常的文档流中,不进行任何特殊的定位。top、right、bottom、left 和 z-index 属性对其没有影响。
  2. relative:相对定位。元素在正常文档流中的位置不变,但可以通过设置 top、right、bottom、left 属性来相对于其正常位置进行偏移。相对定位不会影响其他元素的布局。
  3. absolute:绝对定位。元素脱离正常文档流,并相对于最近的已定位祖先元素或 <body> 元素进行定位。可以使用 top、right、bottom、left 属性进行定位。如果没有已定位的祖先元素,则相对于视口进行定位。
  4. fixed:固定定位。元素脱离正常文档流,并相对于视口进行定位,即无论滚动条如何滚动,元素的位置都不会改变。可以使用 top、right、bottom、left 属性进行定位。
  5. sticky:粘性定位。元素根据正常文档流进行定位,然后相对于离它最近的具有滚动机制的祖先元素或视口进行定位。当用户滚动页面时,元素的定位会发生变化。

5、移动端适配有哪些方法

  1. 响应式布局:使用 CSS 媒体查询和相对单位(如百分比、em 或 rem)来自适应不同的屏幕尺寸。通过设置不同的样式规则,使页面在不同的视口大小下布局合理,并根据需要隐藏或显示特定的元素。
  2. 视口设置:使用 <meta> 标签中的 viewport 属性来控制网页在移动设备上的显示方式。设置 viewport 的宽度、缩放比例、初始缩放等属性,以确保页面在不同设备上正确缩放和显示。
  3. 弹性图片和媒体:使用 max-width: 100% 样式来确保图片和媒体元素能够自适应其容器的宽度。这样,无论设备屏幕的大小如何,这些元素都会按比例缩放并适应屏幕宽度。
  4. 移动优先设计:采用移动优先的设计方法,注重移动设备上的用户体验,并根据需要逐渐增强功能和布局以适应更大的屏幕。
  5. 使用 CSS Flexbox 或 Grid 布局:这些现代的布局技术可以更方便地创建灵活的、自适应的布局,适应不同尺寸的屏幕。

6、vw、vh、rem、em有什么区别

vw、vh、rem 和 em 都是用于响应式布局和移动端适配的相对单位,它们有一些区别:

  1. vw(Viewport Width):视口宽度的百分比单位。1vw 等于视口宽度的 1%。使用 vw 单位可以根据屏幕宽度来调整元素的大小,以实现自适应效果。
  2. vh(Viewport Height):视口高度的百分比单位。1vh 等于视口高度的 1%。使用 vh 单位可以根据屏幕高度来调整元素的大小,以实现自适应效果。
  3. rem(Root EM):相对于根元素的字体大小的单位。默认情况下,根元素的字体大小等于浏览器的默认字体大小(通常为 16px)。如果根元素设置了字体大小为 16px,那么 1rem 相当于 16px。通过在根元素设置合适的字体大小,可以方便地进行整体的比例调整。
  4. em(Emphasis):相对于父元素字体大小的单位。如果父元素的字体大小为 16px,那么 1em 相当于 16px。em 单位具有继承性,子元素的字体大小也会受到父元素字体大小的影响。

主要区别如下:

  • vw 和 vh 是相对于视口宽度和高度的单位,而 rem 和 em 是相对于字体大小的单位。
  • vw 和 vh 单位可以直接根据屏幕尺寸进行调整,而 rem 和 em 单位则需要依赖于父元素的字体大小。
  • rem 单位更适合用于整体的比例调整,而 em 单位更适合用于相对布局中的局部调整。

7、手写准确判断数据类型方法以及返回的示例

使用Object.prototype.toString方法可以获取一个值的具体类型

function getType(obj) {
  return Object.prototype.toString.call(obj).slice(8, -1);
}

console.log(Object.prototype.toString.call(42)); // 输出 "[object Number]"
console.log(Object.prototype.toString.call('Hello')); // 输出 "[object String]"
console.log(Object.prototype.toString.call(true)); // 输出 "[object Boolean]"
console.log(Object.prototype.toString.call(undefined)); // 输出 "[object Undefined]"
console.log(Object.prototype.toString.call(null)); // 输出 "[object Null]"
console.log(Object.prototype.toString.call([])); // 输出 "[object Array]"


8、上面slice中的参数表示什么

.slice(8, -1) 的含义是截取字符串中从第 8 个字符到倒数第 1 个字符之间的部分。也就是说它会去掉字符串开头的 "[object " 和结尾的 "]",只返回中间的类型部分。

.slice() 方法可以接受两个参数,分别是起始位置和结束位置。如果省略结束位置,则表示截取到字符串末尾。如果参数是负数,则表示倒数第几个字符。因此 -1 表示倒数第一个字符

9、其他的类型判断方式与区别

  1. typeof 操作符:typeof 操作符可以返回一个表示操作数类型的字符串。

typeof只能判断基础类型,typeof null 返回 "object" 是 JavaScript 的历史遗留问题。实际上,null 是一个空对象指针,应该返回 "null" 才更准确。

console.log(typeof 42); // 输出 "number"
console.log(typeof 'Hello'); // 输出 "string"
console.log(typeof true); // 输出 "boolean"
console.log(typeof undefined); // 输出 "undefined"
console.log(typeof null); // 输出 "object"(注意这是个历史遗留问题)
console.log(typeof []); // 输出 "object"


  1. instanceof 操作符:instanceof 操作符用于检查对象是否属于某个类或构造函数的实例。
let arr = [];
let date = new Date();

console.log(arr instanceof Array); // 输出 true
console.log(date instanceof Date); // 输出 true

  1. Array.isArray() 方法:Array.isArray() 方法用于判断一个值是否为数组。

10、手写instanceof方法

function myInstanceof(left, right) {
  let proto = Object.getPrototypeOf(left);
  let prototype = right.prototype;
  while(true) {
    if(proto === null) { // 到达原型链顶端仍未找到目标原型,则返回 false
      return false;
    }
    if(proto === prototype) { // 找到目标原型,则返回 true
      return true;
    }
    proto = Object.getPrototypeOf(proto); // 继续查找
  }
}

function Person() {}
let p = new Person();
console.log(myInstanceof(p, Person)); // true

首先通过 Object.getPrototypeOf 方法获取对象 left 的原型,并将其保存在变量 proto 中。然后,获取目标构造函数 right 的原型,并将其保存在变量 prototype 中。

接下来,使用一个无限循环来遍历 left 对象的原型链。while(true) 会导致无限循环,如果没有找到目标原型将一直循环下去,因此需要在循环内部做出判断避免死循环。

11、手写一个JS实现继承方式

  1. 原型链继承:原型链继承的实现非常简单,只需要让子类的原型对象指向父类的实例对象即可。

但是,使用原型链继承时,父类中引用类型的属性会被所有子类实例共享,容易出现修改一个子类实例中的引用类型属性,导致其他子类实例中的该属性也被修改的问题。

function Parent() {
  this.name = 'Parent';
  this.colors = ['red', 'green', 'blue'];
}
Parent.prototype.sayName = function() {
  console.log(this.name);
}

function Child() {
  this.name = 'Child';
}
Child.prototype = new Parent();

let child1 = new Child();
child1.sayName(); // "Child"

  1. 构造函数继承

借用构造函数继承的实现比较简单,只需要在子类的构造函数中调用父类的构造函数,并将 this 指向子类实例即可。这样可以避免引用类型属性被所有子类实例共享的问题。但是,使用该种方式继承时,子类无法访问到父类原型对象中定义的方法和属性。

function Parent() {
  this.name = 'Parent';
  this.colors = ['red', 'green', 'blue'];
}
Parent.prototype.sayName = function() {
  console.log(this.name);
}

function Child() {
  Parent.call(this);
  this.name = 'Child';
}

let child1 = new Child();
child1.sayName(); // 报错:child1.sayName is not a function


12、节流防抖的区别,手写防抖

防抖:n 秒后再执行回调,若在 n 秒内被重复触发,则重新计时;防抖的基本思想是在函数被连续调用时,只执行最后一次调用,并在指定的时间间隔内没有新的调用才执行函数。如果在时间间隔内有新的调用,则重新计时。

  • 输入框搜索:当用户在输入框中连续输入字符时,使用防抖可以避免每次输入都触发搜索请求,而是在用户停止输入一段时间后才触发搜索请求,减少不必要的请求。
  • 窗口调整:当窗口大小调整时,使用防抖可以确保调整完成后才执行相应的操作,避免频繁触发操作。
  • 按钮点击:当用户频繁点击按钮时,使用防抖可以确保只有最后一次点击有效,避免误操作或重复操作。
function debounce(func, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效;节流的基本思想是限制函数在一定时间间隔内的执行次数,例如每隔一段时间执行一次,并在该时间间隔内忽略其他的函数调用。

  • 页面滚动:当用户滚动页面时,使用节流可以控制触发事件的频率,减少滚动事件的处理次数,提高页面的流畅度。
  • 鼠标移动:当用户在页面上移动鼠标时,使用节流可以限制触发事件的频率,避免触发过多的事件处理逻辑。
function throttle(func, delay) {
  let timer;
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

13、如何封装组件

首先进行需求分析,确定组件功能和实现方式。再定义组件 API,包括传入的 props 和事件。最后编写组件测试用例,确保组件功能正确。

封装组件时要注意组件的可复用性、易维护性和可测试性。在编写组件时,应该遵循单一职责原则,尽量避免组件之间的耦合,提供清晰简洁的 API,确保组件功能正确,并编写充分的测试用例,以便于其他开发者和用户使用和维护组件。

14、如何提高前端页面性能

  1. 减少 HTTP 请求:减少静态资源的请求次数,可以通过使用 CSS Sprites、合并 JavaScript 和 CSS 文件等手段来实现。
  2. 压缩文件大小:压缩 JavaScript、CSS 和图片等文件,可以有效减小文件大小,加快文件下载速度。
  3. 避免重复加载:避免重复加载相同的文件或资源,可以通过浏览器缓存、HTTP 缓存和服务端缓存等手段来实现。
  4. 异步加载资源:延迟加载非关键资源,可以通过懒加载和异步加载等手段来实现。
  5. 减少 DOM 操作:减少不必要的 DOM 操作,可以通过避免多次查询 DOM 元素、使用文档片段、事件委托等手段来实现。
  6. 优化 CSS 选择器:避免使用过于复杂的 CSS 选择器,可以通过简化选择器、使用 ID 选择器、避免使用通配符等手段来实现。
  7. 使用 Web Workers 和 Service Workers:使用 Web Workers 和 Service Workers 可以将一些耗时的计算和 I/O 操作转移到后台线程中,避免阻塞主线程,提高页面响应速度。
  8. 移动端优化:移动端页面的性能优化可以通过图片压缩、使用 CSS3 动画代替 JavaScript 动画、避免使用 fixed 定位等手段来实现。

15、xss攻击了解吗?

XSS(跨站脚本攻击)是一种常见的网络安全漏洞,攻击者通过在网页中注入恶意脚本代码,使得用户在浏览网页时执行该恶意脚本,从而达到攻击的目的。XSS 攻击可以分为三种类型:

  1. 存储型 XSS:攻击者将恶意脚本存储在服务端,当用户访问包含这些脚本的页面时,恶意脚本会被执行。
  2. 反射型 XSS:攻击者将恶意脚本作为参数或路径的一部分发送给受害者,当受害者点击特定的链接时,恶意脚本会被执行。
  3. DOM 型 XSS:攻击者通过修改页面的 DOM 结构来执行恶意脚本,通常通过修改 URL 参数、表单提交或者修改 JavaScript 脚本等方式实现。

为了防止 XSS 攻击,可以采取以下措施:

  1. 输入验证和过滤:对用户输入的数据进行验证和过滤,确保只接受合法的数据。可以使用白名单过滤器或转义特殊字符等方式来防止恶意脚本的注入。
  2. 输出编码:对输出到页面的数据进行编码,确保恶意脚本不能被执行。可以使用适当的编码函数,如 HTML 编码、URL 编码等。
  3. 设置 HTTP 头部:通过设置 Content-Security-Policy(CSP)头部来限制页面中可以加载的资源和执行的脚本,减少攻击者的可操作空间。
  4. 使用安全的框架和库:使用经过安全验证的框架和库,这些框架和库通常提供了对 XSS 攻击的防护机制。
  5. 定期更新和修补:保持软件系统和相关组件的更新,及时修补已知的漏洞。

16、csrf了解吗?localstorage存了token,怎么解决?

CSRF(跨站请求伪造)是一种利用受信任用户的身份执行非意愿操作的攻击方式。

在 CSRF 攻击中,攻击者通过诱使受害者在登录状态下访问恶意网站或点击恶意链接,来执行受害者在其他网站上的非意愿操作,如修改密码、发表言论等。

对于使用 localStorage 存储 token 的情况,可以采取以下措施来防止 CSRF 攻击:

  1. 同源检测:在服务器端对请求进行同源检测,确保请求来自合法的源站点。可以通过检查请求头中的 Referer 字段或 Origin 字段来实现。
  2. 随机化 token:在生成 token 时使用随机数和时间戳等信息,使得每个 token 的值都是唯一的,并将 token 存储在 session 或 http-only 的 cookie 中,而不是 localStorage 中。
  3. 添加 CSRF Token:在每个需要进行敏感操作的请求中,添加一个 CSRF Token 参数。该参数的值由服务器生成,并在服务器端进行验证。攻击者无法获取到合法的 CSRF Token 值,从而无法成功执行 CSRF 攻击。
  4. 设置 SameSite 属性:对于支持 SameSite 属性的浏览器,可以将 cookie 的 SameSite 属性设置为 Strict 或 Lax,以限制跨站点的 cookie 发送,进一步减少 CSRF 攻击的风险。
  5. 使用验证码:对于特别敏感的操作,如修改密码、删除账户等,可以要求用户输入验证码,增加安全性。

17、vue源码了解吗,模板编译,render函数,响应式原理,watcher和dep,patch,diff算法,computed的实现,nexttick的实现知道吗?

模板编译

当 Vue 编译模板时,会将模板字符串转换为渲染函数,这个过程称为模板编译。模板编译的目的是将模板转换为可执行的 JavaScript 代码,用于生成 Virtual DOM(虚拟 DOM)树并最终渲染到真实的 DOM 上。

Vue 的模板编译分为以下几个阶段:

  1. 解析:模板编译开始时,Vue 会先进行解析阶段。在解析阶段,Vue 会遍历模板字符串,识别其中的指令、表达式、文本等内容,并构建对应的 AST(抽象语法树)。
  2. 优化:在解析完成后,Vue 进入优化阶段。在这个阶段,Vue 会对 AST 进行一些静态优化操作,如静态节点提升、静态属性提升等。这些优化操作可以在渲染过程中减少不必要的计算,提高性能。
  3. 代码生成:优化完成后,Vue 开始生成可执行的 JavaScript 代码。它会根据 AST 生成渲染函数,也就是 render 函数。Render 函数的作用是根据组件实例的状态和属性生成对应的虚拟 DOM 树。
  4. 编译结果:最后一步是将编译生成的渲染函数和其他相关的代码封装成一个模块,并输出编译结果。这个编译结果可以被 Vue 运行时使用,完成组件的实例化、数据的响应式处理以及渲染等操作。

需要注意的是,Vue 的模板编译只在开发阶段进行,生产环境中通常会使用预编译的方式,将模板编译为 render 函数,并在运行时直接使用 render 函数进行渲染,以提高性能。

render函数

render 函数是用来生成 Virtual DOM 树的函数,在 Vue 中,模板字符串会被编译成一个 render 函数,然后在组件实例化时执行这个 render 函数,生成对应的虚拟 DOM 树

在 render 函数中,以使用 h 函数创建不同类型的虚拟 DOM 元素,除了使用 h 函数外,render 函数还可以使用 JavaScript 的标准语法,包括变量、表达式、条件语句、循环语句等。通过这些语法结构,可以根据组件的状态和属性动态地生成 Virtual DOM 树,从而实现组件的渲染和更新。

响应式原理

Vue 的响应式原理基于以下几个核心概念:

  1. 数据劫持:Vue 在初始化组件实例时,会对 data 函数或者 props 中的数据进行递归地劫持,即将数据转换为可监听的对象,并在对象上添加一个名为 __ob__ 的 Observer 实例,用于监听数据的变化。
  2. 依赖收集:当渲染组件时,Vue 会根据模板或 render 函数中的数据引用建立一个依赖关系图,用于追踪数据和视图之间的关联关系。每个数据的变化都会触发依赖收集器重新计算依赖关系图,找到需要更新的视图组件。
  3. 派发更新:当数据发生改变时,Vue 会通知依赖收集器,让它更新需要变化的视图组件。在依赖收集器中,Vue 会使用异步队列的方式来批量更新所有需要更新的组件,这样可以优化性能并避免不必要的重复渲染。

Watcher、Dep、Patch和Diff算法是Vue响应式原理中的重要概念和机制。Watcher负责跟踪数据的变化并调度更新操作,Dep用于收集依赖于数据属性的Watcher实例,Patch将虚拟DOM转换为实际的DOM操作,Diff算法用于比较新旧虚拟DOM树的差异并生成补丁操作。这些机制协同工作,实现了Vue的数据驱动视图更新的核心功能。

Watcher(观察者)

Watcher是连接依赖收集器(Dep)和更新操作之间的桥梁。每个组件实例都会创建一个Watcher实例,当组件渲染时,Watcher会在渲染过程中跟踪所有被使用的数据属性,并将自身添加到这些数据属性的依赖收集器中。当数据发生变化时,Watcher会被通知并调度更新操作。

Dep(依赖收集器)

Dep是一个用于存储Watcher实例的容器。每个被劫持的数据属性(如对象的属性或数组的元素)都会有一个对应的Dep实例,用于收集依赖于该属性的Watcher实例。当属性被读取时,Watcher会被添加到Dep实例中;当属性发生变化时,Dep实例会通知其中的所有Watcher实例进行更新操作。

Patch(补丁)

Patch是指将虚拟DOM(Virtual DOM)转换为实际DOM操作的过程。在Vue的更新机制中,Vue会先将模板编译成虚拟DOM树,然后通过比较新旧虚拟DOM树的差异(Diff算法),找到需要更新的部分,并生成一系列的补丁操作,最后将这些补丁操作应用到实际的DOM上,从而完成视图的更新。

diff算法

Diff算法是比较两个树结构差异的算法。在Vue中,当数据发生变化时,Vue会通过比较新旧虚拟DOM树的差异,找出需要更新的部分,而不是直接重新渲染整个组件。Diff算法通过遍历新旧虚拟DOM树的节点,逐个比较它们的差异,并生成一系列的补丁操作(Patch)来描述这些差异。这样可以减少了不必要的DOM操作,提高了性能。

18、项目中做过rbac权限控制token吗,简单说说?

RBAC权限控制 RBAC(Role-Based Access Control)是基于角色的访问控制,是一种常用的权限管理模型。它将用户分配到不同的角色中,每个角色被赋予不同的权限,而用户只能使用与其角色相匹配的权限。在实际项目中,可以通过RBAC来实现对系统中各种资源(如API接口、页面元素等)的权限控制。

RBAC权限控制和Token认证的使用,流程大致如下:

  1. 用户登录时,客户端向服务端发送登录请求,服务端验证用户名密码是否正确,如果正确则返回一个Token值给客户端。
  2. 客户端在后续的请求中,在Header中携带这个Token值,服务端根据Token值来识别用户身份。
  3. 服务端根据用户的身份和角色信息,对请求的API接口或页面元素进行权限判断,如果有权限则返回数据,否则返回403或401等错误码。

在实现RBAC权限控制和Token认证时,通常需要使用一些框架和工具来简化开发。例如,可以使用Spring Security框架来实现RBAC权限控制,使用JWT(JSON Web Token)来实现Token认证。同时,为了保证安全性,还需要注意一些细节问题,如Token的有效期、Token的加密方式、token的存储方式等。

19、算法题:反转链表

使用迭代法反转链表的过程如下:

  • 初始化两个指针:prev指向前一个节点(初始为null),curr指向当前节点。
  • 进入循环,每次迭代时,先将curr的next指针指向prev,完成反转操作。
  • 然后移动prev和curr指针,使它们分别指向curr和curr的下一个节点。
  • 重复上述操作,直到链表的最后一个节点为止。
  • 最后将头节点指向prev,完成整个链表的反转
function reverseList(head) {
    let prev = null;
    let curr = head;

    while (curr) {
        let nextNode = curr.next;
        curr.next = prev;
        prev = curr;
        curr = nextNode;
    }

    return prev;
}

let reversedHead = reverseList(head);


#机械制造笔面经##24届软开秋招面试经验大赏##我发现了面试通关密码##快手求职进展汇总#
 类似资料: