一次简简单单的面试

228 阅读21分钟

11月28日腾讯一面

先要求自我介绍然后是讲一下项目难点,后面就是问基础

1.js的事件传递有哪两种

  1. 冒泡(Bubbling): 事件从最内层的目标元素开始,逐级向上传播到最外层的祖先元素。这是默认的事件传递方式,大多数事件都是冒泡的,包括常见的点击事件(click)。
  2. 捕获(Capturing): 事件从最外层的祖先元素开始,逐级向下传播到最内层的目标元素。在捕获阶段,事件首先到达最外层元素,然后逐级向下传播。

事件传递分为三个阶段:

  • 捕获阶段(Capturing Phase): 事件从最外层元素向目标元素传递。
  • 目标阶段(Target Phase): 事件到达目标元素。
  • 冒泡阶段(Bubbling Phase): 事件从目标元素向最外层元素传递。

通过使用事件处理函数的第三个参数,可以控制是在冒泡阶段还是捕获阶段执行事件处理程序。例如:

 // 捕获阶段执行事件处理程序
 element.addEventListener('click', myFunction, true);
 ​
 // 冒泡阶段执行事件处理程序(默认)
 element.addEventListener('click', myFunction, false);

这种灵活性可以让开发者根据需要选择在事件传递的不同阶段执行相应的处理程序。

2.怎么阻止冒泡

1.在原生js中

要阻止事件冒泡,可以使用事件对象的 stopPropagation() 方法。该方法是通过事件处理函数的参数(通常命名为 evente)调用的。在事件处理函数中调用 event.stopPropagation() 将停止事件在DOM中的进一步传播。

2.在vue中

你可以使用 @click.stop 修饰符或调用 stopPropagation 方法来阻止事件冒泡。下面是两种常见的方式:

3.说说什么闭包

闭包(Closure)是指在 JavaScript 中,函数可以访问其自身范围之外的变量,即便在函数执行完毕后仍然可以访问这些变量。具体说,闭包是由函数以及创建该函数的词法环境组合而成的,这个环境包含了函数创建时所能访问的所有局部变量。

一个简单的闭包示例:

 function outerFunction() {
   let outerVariable = 'I am from outer function';
 ​
   function innerFunction() {
     console.log(outerVariable);
   }
 ​
   return innerFunction;
 }
 ​
 const closure = outerFunction();
 closure(); // 输出:I am from outer function

在这个例子中,innerFunction 是一个闭包,因为它可以访问到在其词法环境之外的 outerVariable。当 outerFunction 被调用时,它返回了 innerFunction,形成了一个闭包。

闭包的主要特性用途包括:

  1. 访问外部函数的变量: 内部函数可以访问外部函数声明的变量,即使外部函数执行完成后,这些变量仍然可以被内部函数访问。
  2. 保护变量: 闭包可以用于创建私有变量,因为外部函数的变量对外部不可见,只有通过内部函数才能访问。
  3. 实现模块化: 通过闭包可以实现模块化的设计,将相关功能封装在一个函数内,避免全局命名空间的污染。
  4. 保存状态: 闭包可以用于保存函数的状态,例如在事件处理程序中。
  5. 实现回调和异步操作: 闭包经常用于实现回调函数和异步操作,因为它们可以捕获所需的上下文信息。

虽然闭包提供了很多强大的功能,但过度使用闭包可能导致内存泄漏问题,因为闭包中的变量一直被引用,可能不会被垃圾回收。因此,在使用闭包时需要注意谨慎使用,确保释放不再需要的引用。

4.闭包有什么副作用

  1. 内存泄漏: 闭包中的变量一直被引用,如果不小心造成闭包的循环引用,可能导致内存泄漏。当一个函数返回一个闭包,而该闭包持有对外部函数作用域变量的引用时,如果不及时解除这个引用,这部分内存可能不会被垃圾回收。

     function createClosure() {
       const data = 'Sensitive information';
     ​
       return function() {
         console.log(data);
       };
     }
     ​
     const closure = createClosure();
     closure(); // 闭包持有对 data 的引用,可能导致内存泄漏
    

    在这个例子中,closure 持有对 data 变量的引用,即使外部函数执行完毕,data 仍然存在于闭包中,可能导致内存泄漏。

  2. 性能开销: 闭包会占用更多的内存,因为它们保留了对外部作用域的引用。在大规模应用中,频繁创建闭包可能导致性能问题。

  3. 意外的变量共享: 闭包中的变量可能会被意外地共享。当多个闭包共享相同的外部变量时,一个闭包的修改可能影响其他闭包的行为,这可能导致难以调试和理解的问题。

     function createCounter() {
       let count = 0;
     ​
       return [
         function() {
           count++;
           console.log(count);
         },
         function() {
           console.log(count);
         }
       ];
     }
     ​
     const [increment, getValue] = createCounter();
     increment(); // 输出:1
     getValue();  // 输出:1,而不是 0
    

    在这个例子中,incrementgetValue 共享相同的 count,可能导致不符合预期的行为。

  4. 代码可读性: 过度使用闭包可能导致代码难以理解和维护。尤其是当闭包嵌套层级较深时,代码可能变得复杂。

5.怎么阻止内存泄漏

  1. 手动解除引用: 在不再需要的时候手动解除闭包中的变量引用,可以通过将其赋值为 null 或者使 用 delete 操作符。
  2. 注意定时器和事件监听器: 在使用定时器或事件监听器时,确保在不需要时解绑它们,以避免持续引用。
  3. 避免循环引用: 当闭包函数引用外部函数中的变量时,确保这些变量不会引用闭包函数自身,以避免形成循环引用。

6.说说vue的双向绑定机制

Vue 的双向绑定机制是通过数据劫持和发布-订阅模式实现的。以下是 Vue 的双向绑定的基本原理:

  1. 数据劫持: Vue 使用 Object.defineProperty() 方法劫持数据的 getter 和 setter。当一个 Vue 实例被创建时,Vue 会遍历 data 选项中的属性,对每个属性使用 Object.defineProperty() 来添加 getter 和 setter。这样,当访问或修改属性时,Vue 能够介入,执行相应的操作。
  2. 发布-订阅模式: Vue 使用一个事件系统来实现数据的双向绑定。每个 Vue 实例都包含一个事件管理器,用于处理属性的订阅和发布。当数据发生变化时,setter 会通知订阅了该属性的所有观察者,即依赖于该属性的视图层元素。这些观察者会更新视图,保持视图与数据的同步。

7.能从源码的层面说说吗

Vue 的双向绑定机制在源码层面主要涉及以下几个模块:

  1. Observer(观察者): 该模块负责将数据对象转换成响应式对象。Vue 使用 Object.defineProperty 对数据对象进行劫持,当访问或修改属性时,通过 getter 和 setter 来进行依赖收集和触发更新。这一部分的源码主要位于 src/core/observer 目录。
  2. Dep(依赖管理器): 该模块用于管理属性的依赖关系,即观察者和响应式对象之间的关系。Dep 对象内部维护了一个 subs 数组,用于存储所有依赖于该属性的观察者。当属性发生变化时,Dep 会通知所有相关的观察者执行更新操作。这一部分的源码主要位于 src/core/observer/dep.js
  3. Watcher(观察者): 该模块定义了观察者的行为,每个观察者对象都会被关联到一个响应式属性上,并在该属性的 getter 中进行依赖收集。当属性变化时,观察者对象会接收到通知,执行回调函数。这一部分的源码主要位于 src/core/observer/watcher.js
  4. Compiler(编译器): 该模块负责解析模板,将模板编译成渲染函数。在编译过程中,会对模板中的指令进行解析,生成对应的更新函数。这一部分的源码主要位于 src/compiler 目录。
  5. Runtime(运行时): 运行时主要包括 Vue 实例的构造函数和原型方法,以及一些与运行时相关的功能。Vue 实例在初始化时会创建观察者对象,建立响应式数据与视图的联系。这一部分的源码主要位于 src/core/instance 目录。

8.说说vite和webpack之间的区别

以下是 Vite 和 Webpack 之间的主要区别:

构建方式:

  • Vite: Vite 使用了一种称为 "ESBuild" 的快速构建引擎。在开发阶段,Vite 采用了基于浏览器原生 ES 模块的开发服务器,通过实现按需编译和模块级别的热模块替换(HMR)来提供快速的开发启动速度。
  • Webpack: Webpack 是一个通用的构建工具,使用了自己的构建引擎。它在开发阶段通常需要启动一个开发服务器,对整个应用进行构建。

开发体验:

  • Vite: Vite 提供了非常快速的开发启动速度,支持模块级别的 HMR,使得开发者能够在不刷新整个页面的情况下替换模块,提高了开发效率。
  • Webpack: Webpack 在开发阶段的启动速度相对较慢,因为它需要对整个应用进行构建,且在模块替换时通常需要刷新整个页面。

生态系统:

  • Vite: Vite 的生态系统相对较新,但它是 Vue 3 的官方构建工具,对 Vue 3 提供了更好的支持。由于使用了 ESBuild,一些 Webpack 插件和工具需要适配。
  • Webpack: Webpack 有一个庞大而成熟的生态系统,支持广泛的应用场景。有大量的 Loader 和 Plugin 可以处理各种类型的文件和进行各种构建任务。

配置:

  • Vite: Vite 的配置相对简单,大多数情况下不需要额外的配置。Vite 支持 TypeScript、CSS 预处理器等,配置都直接放在项目根目录下。
  • Webpack: Webpack 的配置相对复杂,需要编写一份详细的配置文件。虽然提供了强大的自定义能力,但初学者可能需要一定时间来适应。

适用场景:

  • Vite: Vite 更适合中小型项目,追求快速开发启动和开发体验的团队。特别适用于 Vue 3 项目。
  • Webpack: Webpack 适用于各种规模的项目,特别是大型和复杂的项目,以及需要广泛支持的项目。

9.vite的原理是什么

Vite 的原理主要基于两个核心概念:按需编译和基于浏览器原生 ES 模块的开发服务器。

  1. 按需编译: Vite 采用了一种称为 "ESBuild" 的快速构建引擎。在开发阶段,Vite 只会编译和构建当前正在开发的模块,而不是整个应用。这种按需编译的方式使得只有真正需要的代码会被处理,大大加快了开发启动的速度。
  2. 基于浏览器原生 ES 模块的开发服务器: Vite 利用了现代浏览器对 ES 模块的支持。它将每个模块作为一个单独的文件,在浏览器中使用原生 ES 模块进行加载,而不像传统的打包工具那样将所有模块合并成一个文件。这使得浏览器可以根据需要按模块进行懒加载,并且支持模块级别的热模块替换(HMR),即只替换修改的模块,而不刷新整个页面。

10.为什么vite启动速度和热更新速度快

Vite 启动速度和热更新速度快的原因主要有以下几点:

  1. ESBuild 构建引擎: Vite 使用了 ESBuild,一个基于 Go 语言的非常快速的构建引擎。ESBuild 的特点是速度极快,它能够在开发阶段以及进行模块构建时实现极佳的性能,远超过传统的构建工具。
  2. 按需编译: Vite 在开发阶段采用了按需编译的策略。它会仅仅编译和构建当前需要的模块,而不是整个应用。这使得开发者在修改一个模块时,只需要重新构建该模块,而不必重新构建整个项目,从而大幅减少了构建时间。
  3. 模块级别的热模块替换(HMR): Vite 支持模块级别的 HMR,即在开发过程中,如果修改了一个模块,只有该模块会被替换,而不是刷新整个页面。这带来了更快的热更新速度,开发者可以即时看到代码变化的效果。
  4. 浏览器原生 ES 模块支持: Vite 利用了浏览器原生对 ES 模块的支持。在开发模式下,Vite 将 ES 模块直接传递给浏览器,无需进行额外的构建。这充分利用了浏览器对 ES 模块的解析能力,减少了构建和传输的时间。

11.vite的热更新的原理是什么

Vite 的热更新(Hot Module Replacement,HMR)原理是基于浏览器原生的 ES 模块支持和模块级别的替换。以下是 Vite 热更新的主要原理:

  1. 浏览器原生 ES 模块支持: Vite 利用了现代浏览器对 ES 模块的原生支持。浏览器可以直接加载和运行 ES 模块,而无需通过其他构建工具的处理。这使得 Vite 在开发模式下可以将 ES 模块直接传递给浏览器。
  2. 模块级别的热更新: Vite 将每个模块都视为一个独立的单元,并且能够在运行时动态地替换这些模块。当一个模块发生变化时,Vite 会通过浏览器原生的模块热更新 API,将新模块发送到浏览器,并在不刷新整个页面的情况下替换旧模块。这使得开发者能够即时看到代码变化的效果,而不中断当前页面的状态。
  3. WebSocket 实时通信: Vite 使用 WebSocket 进行实时通信。当一个模块发生变化时,Vite 会通过 WebSocket 将变更的模块信息推送到浏览器端。浏览器接收到变更信息后,会触发相应的模块替换操作,实现了热更新。

12.为什么vite可以做到模块级别的热更新,而webpack不行

Vite和Webpack在热更新方面的不同主要源于它们处理模块的方式和对浏览器原生 ES 模块支持的利用。

  1. 模块级别的处理:

    • Vite: Vite将每个模块视为独立的单元,因此在进行热更新时,它能够精确地替换修改过的模块,而不会影响其他模块。这是因为Vite使用浏览器原生的ES模块系统,支持模块级别的独立加载和替换。
    • Webpack: Webpack的热更新通常以整个包或包含多个模块的 chunk 为单位进行。在发生变化时,Webpack通常会重新构建整个 chunk,并在必要时刷新整个页面。这意味着热更新的粒度相对较大,可能会导致页面刷新和状态丢失。
  2. 浏览器原生 ES 模块支持:

    • Vite: Vite直接利用现代浏览器对ES模块的原生支持。浏览器能够动态加载和执行ES模块,因此Vite可以将模块直接传递给浏览器,实现模块级别的热更新。
    • Webpack: Webpack通常使用一种被称为“模块热替换”(HMR)的技术来实现热更新。虽然HMR可以在不刷新整个页面的情况下替换模块,但它通常在 chunk 的层面上进行,而不是模块级别。

综合而言,Vite在设计上更注重模块级别的处理,而Webpack在热更新时通常以更大的粒度操作。这使得Vite能够提供更细粒度、更快速的热更新体验,特别是在开发阶段能够更快地反映代码变化。

13.为什么three.js和cocos.js为什么在网页上可以这么流畅

Three.jscocos.js是基于 WebGL 技术实现的。WebGL(Web Graphics Library)是一种用于在浏览器中进行图形渲染的底层 API,它允许使用 JavaScript 在网页上进行高性能的 3D 图形渲染。

Three.js 提供了对 WebGL 的封装,简化了在浏览器中创建复杂的 3D 场景的过程。通过 Three.js,开发者可以使用相对简单的 JavaScript 代码创建和控制 3D 对象、光照、相机等元素,而不必直接编写复杂的 WebGL 代码。

Three.js 提供了一系列的类和方法,使得创建和管理 3D 场景变得更加容易。它支持常见的 3D 图形特性,如光照、纹理、阴影等,同时也有丰富的文档和社区支持,使得开发者可以更轻松地入门和使用。由于其便捷性和功能强大的特点,Three.js 成为了 Web 上创建交互性和引人入胜的 3D 内容的流行选择。

14.webGL是什么语言写的

WebGL 本身并不是用一种特定的编程语言编写的。它是一种底层的图形渲染 API,用于在浏览器中进行图形渲染。WebGL API 通过 JavaScript 绑定到 OpenGL ES 2.0 上。

OpenGL ES 2.0 是一种嵌入式系统版本的 OpenGL(开放图形库)标准,专门设计用于移动设备等资源受限环境。WebGL 借助 JavaScript 和 HTML5 的 canvas 元素,通过在浏览器中执行 JavaScript 代码来调用这些底层的图形渲染功能。

所以,WebGL 的底层实现是通过 C/C++ 编写的 OpenGL ES 2.0,而在浏览器中通过 JavaScript 脚本调用这些底层的图形渲染功能,实现在浏览器中进行高性能图形渲染

15.为什么webGL在浏览器的性能会好

WebGL 在浏览器中的性能优越主要得益于以下几个方面:

  1. 硬件加速: WebGL 利用了计算机的图形硬件加速。通过与 GPU(图形处理单元)的直接交互,它可以在进行复杂的图形渲染时获得更高的性能,因为 GPU 专门设计用于处理图形任务。
  2. 并行处理: WebGL 允许并行处理图形计算,这是因为 GPU 是为并行计算而设计的。它可以同时处理多个图形任务,提高了整体的渲染效率。
  3. 底层优化: WebGL 是基于 OpenGL ES 2.0 标准的,这是一种经过多年优化和改进的图形渲染标准。OpenGL ES 2.0 在设计上注重了性能和效率,使得通过 WebGL 访问这些底层功能的 Web 应用能够充分利用这些优化。
  4. 硬件加速 API: WebGL 提供了硬件加速的 API,能够直接利用计算机的图形硬件。这使得在浏览器中实现复杂的 3D 渲染、游戏等图形密集型应用时,性能较之其他图形渲染方式更为优越。

总体而言,WebGL 利用底层硬件加速和并行计算的特性,使得在浏览器中实现高性能图形渲染成为可能。这使得WebGL成为一种强大的工具,特别适用于需要处理复杂图形的网络应用,比如 3D 游戏、数据可视化等。

16.知道什么是xss攻击吗,那又该如何防范

XSS 是 "Cross-Site Scripting"(跨站脚本攻击)的缩写,XSS 攻击是一种通过在网页中注入恶意脚本,从而在用户浏览器上执行非法代码的攻击方式。攻击者通过注入恶意脚本,可以窃取用户的敏感信息、劫持用户会话、破坏网页结构等。XSS 攻击通常分为两类:存储型 XSS 和反射型 XSS。

  1. 存储型 XSS: 攻击者将恶意脚本存储在目标网站的服务器上,用户访问包含这些恶意脚本的页面时会执行。
  2. 反射型 XSS: 恶意脚本通过构造恶意链接,诱使用户点击,然后将恶意脚本注入到目标页面中,用户浏览器执行。

防范 XSS 攻击的方法:

  1. 输入验证: 对用户输入进行验证,只接受符合预期格式的数据。使用白名单过滤,而不是黑名单,以防止绕过过滤规则。
  2. 输出转义: 在输出用户输入内容时,对特殊字符进行转义。这包括 HTML、JavaScript、CSS 等上下文中的特殊字符。这可以通过使用安全的编码库来实现,如在 HTML 上使用 < 代替 <
  3. HTTP Only Cookie: 将敏感信息存储在 HTTP Only Cookie 中,这样客户端的 JavaScript 就无法访问这些 Cookie,从而防止 XSS 攻击获取敏感信息。
  4. 内容安全策略(CSP): 使用 CSP 头部来定义允许加载的资源源和执行的脚本来源。CSP 可以减轻 XSS 攻击,限制浏览器加载外部资源。
  5. 安全的开发实践: 定期审查和更新代码,确保采用最新的安全标准和实践。避免使用不安全的函数,如 eval()innerHTML
  6. HTTPS 使用: 通过使用 HTTPS 来保护数据在传输过程中的安全性,防止被劫持和篡改。
  7. 安全 HTTP 头: 使用安全的 HTTP 头,如 Strict-Transport-Security(HSTS)、X-Content-Type-Options、X-Frame-Options 等,来增加网站的安全性。
  8. 限制第三方脚本: 仅允许从受信任的域加载脚本,避免从不受信任的来源引入脚本。

17.npm,yarn,pnpm之间的区别,分别解决了什么问题

  1. npm(Node Package Manager):

    • 问题解决: npm 是 Node.js 官方的包管理工具,用于管理 JavaScript 包的依赖关系。它解决了在 JavaScript 项目中引入和管理第三方包的问题。
    • 特点: npm 的包管理速度相对较慢,安装时会将依赖包复制到项目的 node_modules 目录下,可能会导致大量冗余和占用磁盘空间。
  2. Yarn:

    • 问题解决: Yarn 是由 Facebook 创建的 JavaScript 包管理工具,旨在解决 npm 的一些性能和安全性问题。Yarn 提供了更快的安装速度和离线支持。
    • 特点: Yarn 引入了本地缓存机制,通过缓存已下载的包,减少了对网络的依赖,提高了安装速度。它还使用锁定文件(yarn.lock)确保每个团队成员和构建环境使用相同版本的依赖。
  3. pnpm:

    • 问题解决: pnpm 是一种更加注重磁盘空间和网络带宽的包管理工具。它通过符号链接和硬链接来共享依赖项,减少了重复下载和占用的磁盘空间。
    • 特点: pnpm 采用一种称为“符号链接依赖”的策略,通过链接共享相同版本的依赖项,从而减小项目的磁盘占用。此外,pnpm 具有并发安装的能力,能够更快地安装依赖项。

总结:

  • npm 是 Node.js 官方的包管理工具,解决了引入和管理 JavaScript 包的问题,但安装速度相对较慢。
  • Yarn 是由 Facebook 创建的 JavaScript 包管理工具,提供更快的安装速度和离线支持,通过本地缓存机制减少了对网络的依赖。
  • pnpm 更注重磁盘空间和网络带宽的优化,采用符号链接和硬链接来共享依赖项,减小了项目的磁盘占用,具有并发安装的能力。

总结:

这次面试比较紧张,很多问题都答出来了但脑子空空很多可以深入的点没有说清楚,但这次面试感觉让我学习到了很多,好像很多问题都可以这样一点一点的深入进去,一点一点的问自己,直到问到自己完完全全搞懂,并且能给人流利的讲出来,之前有点懒了,从现在在开始一点一点慢慢的重新开始吧。