1. JS原始类型有哪些?
JavaScript 中的原始类型包括:
- Number:表示数值(整数和浮点数)。
- String:表示文本字符串。
- Boolean:表示布尔值(true 或 false)。
- Null:表示空值,指明一个空对象引用。
- Undefined:表示未定义的值,变量声明但未赋值时默认值为
undefined
。 - Symbol:表示唯一的不可变值,通常用作对象属性的标识符。
- BigInt:用于表示任意精度的整数。
2. 它们存在栈中还是堆里的呢?
原始类型的值是不可变的,并且由于它们大小固定、内存分配相对简单,通常存储在栈中。这使得它们的访问速度非常快。而引用类型(如对象、数组、函数)存储在堆中。
3. 引用类型有哪些?
引用类型包括:
- Object:这是 JavaScript 中最基本的引用类型,包括普通对象(如
{}
)、数组(如[]
)、函数(如function () {}
)等。 - Array:一种特殊类型的对象,用于存储有序的集合。
- Function:也是一种特殊的对象,可以被调用。
- Date:用于表示日期和时间。
- RegExp:用于表示正则表达式。
- Map、Set:ES6 引入的集合类型。
- WeakMap、WeakSet:类似于
Map
和Set
,但对键或元素是弱引用。
4. 他们为什么要去区分呢?都存在栈里面有没有问题?
区分原始类型和引用类型的主要原因是内存管理和性能。
- 原始类型的数据大小固定,通常很小,因此存储在栈中,这样可以提高访问速度。
- 引用类型通常是复杂的数据结构,大小不固定,因此存储在堆中,栈中只存储它们的引用(即指针)。如果引用类型也存在栈中,可能会导致栈溢出,影响性能和稳定性。
5. 调用栈的数据是怎么回收的?堆的数据是怎么回收的?
调用栈中的数据:栈是通过自动管理的。当函数执行完毕后,函数调用所占用的内存会自动释放。因此,栈中的数据回收不需要手动干预,由系统自动管理。
堆中的数据:堆内存由垃圾回收机制(如 JavaScript 引擎的垃圾回收器)管理,主要采用标记-清除、引用计数等算法。当一个对象不再被引用时,它占用的内存将被标记为可回收,垃圾回收器会在合适的时间回收这些内存。
6. ES Module 和 CommonJS区别
ES Module:
- 语法:
import
和export
。 - 模块是静态加载的,即在编译时解析依赖关系,确定模块导入导出。
- 是原生支持的模块系统,浏览器和 Node.js 12+ 版本都支持。
- 支持树摇(Tree Shaking)优化未使用的代码。
- 语法:
CommonJS:
- 语法:
require
和module.exports
。 - 模块是动态加载的,依赖关系在运行时解析。
- 主要用于 Node.js 环境中,浏览器中需借助工具(如 Browserify、Webpack)转换。
- 不支持树摇优化,因为模块加载是动态的。
- 语法:
7. 浏览器事件循环机制
浏览器的事件循环机制用于管理异步操作的执行顺序。主线程执行同步代码,异步任务(如 setTimeout
、Promise
)推送到任务队列,主线程空闲时从任务队列中取出任务执行。事件循环(Event Loop)不断检查调用栈和任务队列的状态,确保异步代码能在正确的时间点被执行。
8. 宏任务和微任务是什么?哪些属于宏任务哪些属于微任务?
宏任务:包括 script(整体代码)、
setTimeout
、setInterval
、I/O
操作等。宏任务执行完后,事件循环会查看微任务队列并执行所有微任务,然后再执行下一个宏任务。微任务:包括
Promise.then
、MutationObserver
、queueMicrotask
。微任务在当前宏任务结束后、下一个宏任务开始前执行。
9. HTTP1和HTTP2的区别
HTTP1:
- 每个请求都需要单独的 TCP 连接,导致性能瓶颈。
- 头部冗余,每次请求都会重复发送相同的头部信息。
HTTP2:
- 支持多路复用,一个连接上可以发送多个请求,极大地减少了延迟。
- 头部压缩,减少数据传输量。
- 服务端推送,服务器可以主动将资源推送到客户端。
10. 多路复用是什么?主要解决什么问题?
多路复用允许在一个 TCP 连接上同时发送多个 HTTP 请求,解决了 HTTP/1.1 中“每个请求需要一个连接”的问题,减少了连接的建立和管理开销,提高了传输效率。
11. 浏览器缓存机制有哪些?
- 强缓存:利用
Expires
和Cache-Control
头部实现,命中后直接从缓存读取,避免请求发到服务器。 - 协商缓存:利用
Last-Modified
和ETag
头部字段,缓存过期后由客户端向服务器验证资源是否更新,未更新则继续使用缓存。
12. 讲一下React虚拟DOM
React 的虚拟 DOM 是对真实 DOM 的抽象表示。通过虚拟 DOM,可以在内存中对 DOM 进行快速更新,然后将差异(diff)应用到实际的 DOM 上,减少直接操作 DOM 的性能开销,提高渲染效率。
13. 讲一下React的diff算法
React 的 diff 算法主要用于比较两棵虚拟 DOM 树的差异。算法假设同一层级下的元素类型相同,逐层比较节点,采用分而治之的策略,(dfs深搜)大大减少了复杂度。对于列表类型的组件,通过 key
属性来识别节点,避免不必要的重渲染。
14. React有哪些Hooks?
- useState:用于在函数组件中添加状态。
- useEffect:用于处理副作用(如数据获取、订阅等)。
- useContext:用于在组件树中传递和接收上下文数据。
- useReducer:用于复杂的状态逻辑处理。
- useMemo:用于性能优化,避免不必要的计算。
- useCallback:用于性能优化,避免不必要的函数重新创建。
- useRef:用于获取 DOM 节点或保存任意变量的引用。
- useImperativeHandle:定制
useRef
暴露给父组件的实例值。 - useLayoutEffect:与
useEffect
类似,但它会在所有 DOM 变更之后同步触发。 - useDebugValue:用于自定义 Hook 在 React 开发者工具中的标签。
15. React Hooks和高阶组件有什么区别?
- React Hooks:允许在函数组件中使用状态和其他 React 特性,代码更加简洁和模块化,不会增加组件嵌套的复杂度。
- 高阶组件(HOC):是一种模式,用于重用组件逻辑,通过将一个组件传递给高阶组件,返回一个增强版组件。它增加了组件嵌套的复杂度,可能导致“Wrapper Hell”。
16. 高阶组件有哪些?
常见的高阶组件包括:
- withRouter:将路由相关的 props 注入到组件中。
- connect:在 React-Redux 中使用,将 Redux 状态和操作注入到组件中。
- withAuth:自定义的高阶组件,通常用于鉴权,限制访问权限。
17. ES6 map和weakmap的区别
Map:
- 键值对集合,键可以是任意类型(对象、原始值)。
- 强引用,键不会被垃圾回收。
WeakMap:
- 键必须是对象,值可以是任意类型。
- 弱引用,键可以被垃圾回收,因此更适合存储临时对象。
18. WeakMap的常见使用场景
WeakMap 的常见使用场景主要集中在需要对对象进行弱引用管理的地方。
- 缓存和数据存储
- WeakMap 可以用来存储与对象关联的数据,而不影响垃圾回收。当对象不再被引用时,WeakMap 中与之关联的数据也会被自动清除,避免内存泄漏。
- 示例:在缓存计算结果、存储对象元数据时,使用 WeakMap 来避免内存泄漏。
const cache = new WeakMap();
function compute(obj) {
if (!cache.has(obj)) {
// 进行计算并存储结果
const result = /* 计算逻辑 */;
cache.set(obj, result);
}
return cache.get(obj);
}
DOM 节点关联数据
- WeakMap 非常适合用来将 DOM 节点与数据关联起来。因为 DOM 节点可能会被删除,WeakMap 能够确保这些节点不再使用时,相关联的数据可以被自动回收。
- 示例:为每个 DOM 元素存储事件处理器或其他相关数据。
javascriptconst elementData = new WeakMap(); function bindDataToElement(element, data) { elementData.set(element, data); } function getDataFromElement(element) { return elementData.get(element); }
隐藏私有数据
- WeakMap 可以用作对象的“私有”属性存储,使得这些数据无法通过对象本身直接访问,提供了一种隐藏私有数据的方式。
- 示例:在类中使用 WeakMap 存储实例的私有属性。
const privateData = new WeakMap();
class MyClass {
constructor(value) {
privateData.set(this, { value });
}
getValue() {
return privateData.get(this).value;
}
}
const instance = new MyClass(42);
console.log(instance.getValue()); // 42
- 用于管理弱引用的监听器或回调
- WeakMap 可以用于管理与对象相关的监听器或回调函数,这样当对象不再需要时,相关的监听器或回调可以被自动清除。
const listeners = new WeakMap();
function addListener(obj, listener) {
listeners.set(obj, listener);
}
function trigger(obj) {
const listener = listeners.get(obj);
if (listener) listener();
}
总结:
WeakMap 的关键特性是它对键的弱引用,适用于存储那些与对象相关但不需要强引用的值,这样可以有效避免内存泄漏,特别适合在缓存、DOM 操作和隐藏私有数据等场景中使用。
古茗二面
1. TypeScript 设计原理 vs JavaScript 设计原理
JavaScript 设计原理
- 动态类型:JavaScript 是一种动态类型语言,变量类型在运行时确定。虽然灵活,但容易引发类型错误和不确定性。
- 解释执行:JavaScript 通过解释器直接运行在浏览器或 Node.js 环境中,代码按行解释执行,具有即时性和动态性。
- 弱类型检查:由于没有严格的类型约束,类型转换和类型错误时有发生,这可能导致运行时错误。
- 原型继承:JavaScript 采用基于原型的继承机制,与传统的类继承不同,所有对象都可以直接或间接地继承自其他对象。
- 单线程与异步:JavaScript 是单线程运行的,但通过事件循环机制可以处理异步任务,如回调、Promise、async/await。
TypeScript 设计原理
- 静态类型:TypeScript 增加了静态类型系统,在编译时进行类型检查,确保类型安全,减少运行时错误。
- 类型推断:TypeScript 能够根据上下文自动推断变量的类型,减少显式类型声明的负担,同时提供类型安全保障。
- 面向对象特性:TypeScript 引入了基于类的继承、接口、抽象类、泛型等面向对象编程特性,增强了代码的组织性和可维护性。
- 兼容性:TypeScript 是 JavaScript 的超集,兼容所有 JavaScript 代码,并能通过编译器生成纯 JavaScript 代码。
- 增强开发体验:通过静态类型和代码提示,TypeScript 提高了开发效率,减少了调试和维护的时间成本。
2. Vite vs Webpack 深入对比
Webpack 设计原理与特点
- 模块打包:Webpack 是一个模块打包工具,可以处理 JavaScript、CSS、图片等多种资源文件,并将其打包成一个或多个 bundle 文件,减少 HTTP 请求数,提升页面加载性能。
- 依赖图:Webpack 构建项目时,会从入口文件开始,递归地解析所有依赖,生成一个依赖图(Dependency Graph),并根据图中的关系进行打包。
- 多功能插件:Webpack 提供了强大的插件系统,几乎可以拦截打包过程中的每一个环节,开发者可以通过插件扩展 Webpack 的功能,如代码压缩、热模块替换(HMR)、静态资源优化等。
- 灵活配置:Webpack 的配置文件
webpack.config.js
允许用户进行高度定制,可以根据项目需求配置各种打包策略。 - 慢启动,快构建:由于 Webpack 需要分析整个依赖图,因此在大型项目中,初次构建时间可能较长,但随着增量构建和持久缓存策略的引入,后续的构建速度会显著加快。
Vite 设计原理与特点
- ES Modules 原生支持:Vite 采用浏览器原生支持的 ES Modules 进行开发阶段的模块加载,不需要对所有文件进行打包,避免了 Webpack 的繁重构建过程。
- 极速启动:Vite 只在需要时按需编译文件,因此启动速度极快,特别适合大型项目的开发环境。
- 基于 Rollup 打包:在生产环境中,Vite 通过 Rollup 进行打包,生成优化后的静态文件。Rollup 专注于高效打包和代码拆分,生成的包体积更小。
- 内置优化:Vite 内置了许多优化策略,如依赖预构建、按需加载、模块热替换(HMR)等,开发体验流畅。
- 插件系统:Vite 也支持插件,插件基于 Rollup 实现,可以与 Rollup 生态中的许多插件共享。
对比总结
- 启动速度:Vite 的启动速度远快于 Webpack,特别是在开发阶段,Vite 的体验更加流畅。
- 打包方式:Webpack 是一切皆模块的打包工具,而 Vite 更依赖于浏览器原生的模块系统,生产环境则借助 Rollup 打包,二者在打包策略上有本质区别。
- 复杂度:Webpack 提供了更为复杂和全面的配置能力,适用于复杂的企业级项目;Vite 则更注重简洁性和开发效率,适合快速开发。
- 生态系统:Webpack 拥有广泛的插件和 loader 生态,能处理几乎所有前端开发需求;Vite 则借助 Rollup 插件系统,并逐步形成自己的生态。
3. 前端工程化
概念与背景
前端工程化是指在前端开发中引入工程化思维和工具,以提高开发效率、代码质量和团队协作能力。随着前端技术栈的复杂化,前端工程化变得越来越重要。
核心要素
- 模块化开发:将代码拆分为独立模块,通过模块化管理依赖关系,增强代码的可维护性和复用性。常用的模块化工具包括 Webpack、Rollup、Vite 等。
- 自动化构建:通过自动化工具(如 Webpack、Gulp、Grunt)实现代码的打包、压缩、转换等操作,减少手动操作,提升效率。
- 版本控制:通过 Git 等版本控制系统管理代码变更,支持团队协作和回滚操作。
- 持续集成与部署(CI/CD):通过 Jenkins、Travis CI 等工具自动化构建、测试和部署流程,确保代码的稳定性和快速发布。
- 代码规范与静态检查:使用 ESLint、Prettier 等工具进行代码规范检查和格式化,保证代码一致性和可读性。
- 测试:通过单元测试、集成测试等保障代码的正确性和稳定性。Jest、Mocha、Cypress 是常用的测试工具。
- 文档化与自动化工具:使用工具(如 Storybook、Docusaurus)生成组件文档和 API 文档,确保团队对项目的理解一致。
- 性能优化:通过代码分割、资源压缩、缓存策略、CDN 等手段优化前端性能,提升用户体验。
- 监控与分析:通过前端监控工具(如 Sentry、Google Analytics)监控用户行为和异常,分析性能瓶颈。
前端工程化的典型工具链
- 开发阶段:Vite、Webpack、Babel、TypeScript
- 代码管理:Git、GitHub/GitLab
- 自动化构建:Webpack、Rollup、Gulp
- 代码规范:ESLint、Prettier、Stylelint
- 测试:Jest、Mocha、Chai、Cypress
- 持续集成/部署:Jenkins、Travis CI、CircleCI
- 文档生成:Storybook、Docusaurus、Typedoc
- 性能优化:Lighthouse、Webpack Bundle Analyzer
- 监控与分析:Sentry、LogRocket、Google Analytics
总结
前端工程化是一套涵盖开发、测试、部署、优化、监控等各个环节的系统性方案,目标是提升开发效率、降低出错率、保证项目质量。通过合理的工程化实践,团队可以更高效地协作,快速响应需求变化,并且保持代码库的健康状态。