总结
面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。
还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。
万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
前端面试题汇总
JavaScript
前端资料汇总
- 预渲染类似于服务器端渲染方法,但渲染会提前发生在构建时而非运行时。优势:built 静态支持文件通常比服务器运行方法更简单、SEO 友好性高、快速初始页面加载。短板:需要在执行任何代码变更时提前进行完整页面重新加载、非富网站交互、浏览器功能访问限制。
客户端渲染
以前,我们将自己的主页与 Ember.js 框架一同实现为采用客户端渲染方法的单页面应用。但这种做法的一大问题在于,我们的 Ember.js 应用程序包过大。这意味着在浏览器下载 JavaScript 文件并对其进行解析、编译与执行的过程中,用户只能对着空白屏幕发呆:
最要命的空白屏幕
因此,我们决定利用 React 重构应用当中的某些部分。
-
我们的开发人员已经非常熟悉 React 应用程序的构建方法(例如嵌入式功能部件)。
-
我们已经拥有多个 React 组件库可在多个项目间随意共享。
-
新的页面中将可包含一些交互式 UI 元素。
-
庞大的 React 生态系统能够提供多种工具方案。
-
利用浏览器中的 JavaScript,我们可以通过多项强大功能构建起渐进式 Web 应用。
预渲染与服务器端渲染
客户端渲染应用程序的具体构建——例如采用 React Router DOM,仍然会带来与 Ember.js 相同的问题。JavaScript 需要占用大量资源,而且访问者需要经历一段首屏内容填充周期才能看到实际内容。
因此在决定使用 React 之后,我们开始尝试其它潜在的渲染选项,以确保浏览器能够更快地完成内容渲染。
使用 React 时的常规渲染选项
-
Gatsby.js 允许我们利用 React 与 GraphQL 构建预渲染页面。Gatsby.js 是一款强大的工具,能够直接提供多种性能优化方案。然而,预渲染方法并不适合我们的需求,因为我们的网站中可能存在无数包含用户生成内容的页面。
-
Next.js 是一套高人气 Node.js 框架,允许用户通过 React 实现服务器端渲染。然而,Next.js 设定了太多条条框框,要求用户使用它提供的路由机制以及 CSS 解决方案等等。另外,我们的现有组件库是专为浏览器构建的,与 Node.js 并不兼容。
因此,我们打算尝试一下混合方法,即发挥每一种渲染选项中的独特优势。
运行时预渲染
Puppeteer 是一套 Node.js 库,允许用户使用 headless Chrome。我们希望尝试利用 Puppeteer 在运行时当中实现预渲染。这代表着一种有趣的混合方法:利用 Puppeteer 进行服务器端渲染,同时利用 hydration 进行客户端渲染。感兴趣的朋友可以点击此处查看谷歌提供的关于如何利用 headless 浏览器进行服务器端渲染的相关提示。
利用 Puppeteer 对 React 应用程序进行运行时预渲染
这种方法具备以下优势:
-
允许 SSR,因此有利于 SEO 优化。抓取程序不需要执行 JavaScript 即可看到网页内容。
-
允许一次性构建起简单的浏览器 React 应用程序,而后将其同时用于服务器端与浏览器内。这将同时提高浏览器应用与 SSR 的速度表现,一举两得。
-
利用 Puppeteer 在服务器端渲染页面,在速度上一般快于在最终用户的移动设备上进行渲染(前者网络连接更强、硬件配置也更高)。
-
Hydration 允许我们构建起富 SPA,并可访问 JavaScript 的浏览器功能。
-
我们不再需要预先了解所有可能被调用的页面,也不需要预先进行渲染。
但在采用这种方法的过程中,我们也遇到了一些挑战:
- 吞吐量是最主要的问题。每项请求都会在单独的 headless 浏览器进程当中占用大量资源。虽然我们可以使用单一 headless 浏览器进程并在其中的各个选项卡内运行多项请求,但使用多个选项卡仍会降低整个进程的性能水平。
利用 Puppeteer 的服务器端渲染架构
• 稳定性。对众多 headless 浏览器进行规模伸缩,同时保持进程不致过热并实现负载均衡绝对是一项高难挑战。我们尝试了不同的托管方法,包括在 Kubernetes 集群内进行自托管,以及利用 AWS Lambda 与 Google Cloud Functions 实现无服务器计算。我们注意到,后一种方法在配合 Puppeteer 时存在一些性能问题:
AWS Lambdas和GCP函数的Puppeteer响应时间
在配合 AWS Lambdas 与 GCP Functions 时,Puppeteer 的响应时间结果随着我们对 Puppeteer 熟悉程度的逐步提升,我们开始对初始方法进行迭代(后文将具体说明)。我们还进行了其它一系列有趣的实验,希望通过 headless 浏览器渲染 PDF。再有,即使不编写任何代码,我们也能够利用 Puppeteer 自动进行端到端测试。而且除了 Chrome 之外,Puppeteer 现在还支持 Firefox 浏览器。
混合渲染方法
在运行时中使用 Puppeteer 并非易事。正因为如此,我们才决定在构建时中加以使用,同时配合一款工具用于在运行时内从服务器端获取用户生成的实际内容。很明显,这款工具必须拥有比 Puppeteer 更强大的稳定性与吞吐能力。
我们决定使用 Elixir 编程语言。Elixir 看起来与 Ruby 非常相似,但运行在 BEAM(Erlang VM)之上。顺带一提,BEAM 专门为构建高容错、高稳定性系统而生。
Elixir 采用 Actor 并发模型。每个“Actor”(即 Elixir 进程)的内存占用量都非常有限,仅为 1 到 2 KB。这意味着系统将能够同时运行成千上万个独立的进程。Phoenix 则是一套 Elixir Web 框架,能够支持高吞吐量,并允许开发者在各个独立的 Exlixir 进程当中处理各项 HTTP 请求。
我们将上述方法结合起来,充分利用其各自优势,希望能够切实满足自身需求:
Puppeteer 用于实现预渲染,Phoenix 则用于实现服务器端渲染
- Puppeteer 在构建时中按照我们预期的方式对 React 页面进行预渲染,并将结果保存为 HTML 文件(来自 PRPL 模式的 app shell)。
我们可以继续构建一款简单的浏览器 React 应用程序,并在无需等待最终用户设备 JavaScript 处理过程的同时获得快速初始页面加载效果。
-
我们的 Phoenix 应用负责实现页面预渲染,并以动态方式将实际内容注入至 HTML。这就使得内容的 SEO 友好性大幅提升,让按需处理大量多种页面成为可能,并显著降低了扩展难度。
-
客户端接收并立即开始显示 HTML,而后由 Hydration 将 React DOM 状态持续作为常规 SPA。如此一来,我们就构建起了高度交互的应用程序,并可访问各项 JavaScript 浏览器功能。
利用 Puppeteer 建立预渲染架构,利用 Phoenix 进行服务器端渲染,React 则在客户端上实现 hydration
网络
内容交付网络 (CDN)
利用 CDN 可帮助我们实现内容缓存,并加速其在全球范围内的交付速度。我们选择了 Fastly.com,其目前处理着全球超过 10% 的请求总量,并得到 GitHub、Stripe、Airbnb 以及 Twitter 等诸多厂商的青睐。
Fastly 允许我们编写定制化缓存,并可利用 VCL 配置语言建立路由逻辑。下面,我们将具体聊聊基础请求流如何根据路由、请求头等因素分步起效:
VCL 请求流
提高性能的另一个选项是配合 Fastly 在边缘位置使用 WebAssembly(WASM)。大家可以将其视为一种无服务器模式,只是处于边缘位置;所使用的语言则包括 C、Rust、Go 以及 TypeScript 等等。Cloudflare 就拥有一个类似的项目,用于在 Workers 上支持 WASM。
缓存
尽可能多地利用缓存处理请求是改善性能水平的关键所在。立足 CDN 层级进行缓存,将能够更快地为新用户提供响应。而通过发送 Cache-Control 头进行缓存,则可加快浏览器中重复请求的响应速度。
大多数构建工具(例如 Webpack)允许用户向文件名当中添加哈希值。由于指向这些文件的任何变更都会产生新的输出文件名,因此大家可以安心将文件添加至缓存当中。
通过 HTTP/2 进行文件缓存与编码
GraphQL 缓存
发送 GraphQL 请求的一种常见方法,就是利用 POST HTTP 方法。而我们选择了立足 Fastly 层级对部分 GraphQL 请求进行缓存:
-
我们的 React 应用会标注出那些可进行缓存的 GraphQL 查询。
-
在发送 HTTP 请求之前,我们以请求本体为基础构建一条附加 URL 参数,其中包含 GraphQL 查询与变量(我们配合 Apollo Client 使用自定义 fetch)。
-
在默认情况下 ,Varnish(与 Fastly)会使用完整的 URL 作为缓存密钥的一部分。
-
这意味着我们可以通过请求本体当中的 GraphQL 查询不断发送 POST 请求,并在无需接触服务器的前提下立足边缘位置完成缓存。
利用一条 SHA256 URL 参数发送 POST GraphQL 请求
以下是其它一些值得参考的潜在 GraphQL 缓存策略:
-
服务器端缓存:立足解析器层级或者通过模式标注对全部 GraphQL 请求进行缓存。
-
利用持久化 GraphQL 查询并发送 GET /graphql/:queryId 以使用 HTTP 缓存机制。
-
利用自动化工具(例如 Apollo Server 2.0)或者 GraphQL 专用型 CDN(例如 FastQL)实现不同 CDN 的整合。
编码
目前,所有主流浏览器都支持利用 gzip 加 Content-Encoding 标头进行数据压缩。这意味着面向浏览器的发送数据量更低,从而带来更快的内容传递速度。此外,如果浏览器支持,大家也可以尝试使用效率更高的 brotli 压缩算法。
HTTP/2 协议
HTTP/2 是 HTTP 网络协议的新版本(DevConsole 中简称为 h2)。由于存在着以下几项与 HTTP/1.x 版本间的显著差别,切换至 HTTP/2 能够带来性能提升:
-
HTTP/2 为二进制,而非文本式。因此其解析效率更高,也更加紧凑。
-
HTTP/2 具有多路复用属性,这意味着 HTTP/2 可以通过单一 TCP 连接发送多项请求。如此一来,我们就不必担心每主机浏览器连接限制以及域名分片等问题。
-
其利用标头压缩机制减少请求 / 响应的实际体积。
-
允许服务器主动推送响应。这项功能拥有诸多有趣的实际应用方式。
HTTP/2 Server Push
由于给现有工具及生态系统(例如 rack)引入了一系列颠覆性的变更,很多编程语言与库并不能完全支持 HTTP/2 的全部功能。但即便如此,我们仍然可以在部分合适的场景中使用 HTTP/2。举例来说:
-
利用 HTTP/2 在常规 HTTP/1.x 服务器之前设置一套 h2o 或者 nginx 代理服务器。Puma 与 Ruby on Rails 能够发送 Early Hints,从而在一定的限制条件下启用 HTTP/2 Server Push。
-
利用支持 HTTP/2 的 CDN 交付静态资产。例如,我们可以使用这种方法将字体以及一部分 JavaScript 文件推送至客户端。
HTTP/2 推送字体
对 JavaScript 以及 CSS 的推送功能同样非常实用。但请注意不要过度推送,您可点击此处了解一些相关问题:https://jakearchibald.com/2017/h2-push-tougher-than-i-thought/
浏览器中的 JavaScript
包大小预算
JavaScript 性能优化中的头号规则就是,不要使用 JavaScript。
—— 我自己
如果您已经拥有现成的 JavaScript 应用程序,那么设置预算规则能够提高包大小的可见性,同时确保全部内容都可容纳于同一页面当中。超出预算后,开发人员则需要谨慎考虑并尽量防止规模进一步增长。以下是预算设置方面的相关示例:
-
根据您的实际需求或推荐值设定数值。例如,不得大于 170 KB 否则压缩 JavaScript。
-
利用现有包大小作为基准,或者尝试对其进行削减——例如下调 10%。
-
尝试让网站拥有高于竞争对手的速度,并以此为依据设定预算。
您可以使用 bundlesize 工具包或者 Webpack 性能提示与限制进行预算跟踪:
Webpack 性能提示与限制
消除依赖性
Sidekiq 曾在一篇博文中提到:“代码越少,运行速度越快。代码越少,bug 就越少。代码越少,占用的内存量就越低。代码越少,理解起来就越轻松。”
遗憾的是,实际 JavaScript 场景中往往存在着不计其数的依赖关系。您可以试试:ls node_modules | wc -l。
在某些情况下,添加依赖性是种必然的选择。在这种情况下,依赖性的包大小应该被视为决定您实际工具包选择的重要依据。我强烈建议大家使用 BundlePhobia:
BundlePhobia 能够提示将 npm 工具包添加至您数据包中带来的实际成本
代码拆分
使用代码拆分是另一种能够显著提高 JavaScript 性能的好办法。其本质在于分解代码片段并仅向用户交付当前所需要的部分。以下是关于代码拆分的相关示例:
-
在不同的 JavaScript 代码块间分别加载路由机制。
-
拆分那些在页面中无法立即显示的部分,例如弹出框以及页面下方的页脚。
-
Polyfills 与 ponyfills 可支持全部主流浏览器当中的各最新浏览器功能。
-
利用 Webpack 的 SplitChunksPlugin 防止代码重复。
-
按需定位文件,以避免一次性发送所有受支持的语言。
您可以利用 Webpack 动态导入以及 React.lazy 配合 Suspense 实现代码拆分。
利用动态导入以及 React.lazy 配合 Suspense 实现代码拆分。
相较于默认导出,我们构建的函数可取代 React.lazy 以支持点名导出。
Async 与 defer 脚本
目前,全部主流浏览器皆在 script 标签上支持 async 与 defer 属性:
加载JavaScript的不同方式
几种不同的 JavaScript 加载方式:
-
内联脚本适用于加载小体积、高关键度 JavaScript 代码。
-
当您的用户或者任何其它脚本(例如分析脚本)不再需要某些特定脚本时,大家可以将 async 与这些脚本配合使用以避免 HTML 解析阻塞。
-
从性能角度来看,将 defer 与脚本配合使用能够有效提升非关键 JavaScript 代码的抓取与执行效率,且避免发生 HTML 解析阻塞。此外,这种做法还能够在调用脚本时保证执行顺序,从而确保不同脚本间存在依赖性时实时与预期相符的执行效果。
下面来看 head 标签下不同脚本间的可视化差异:
几种不同的脚本抓取与执行方式
图像优化
虽然与 100 KB 的图像相比,100 KB 的 JavaScript 代码明确会带来更高的性能成本,但我们同样有必要重视对图像内容的优化调整。
削减图像大小的有效手段之一,是在适用的浏览器当中采用更加轻量化的 WebP 图像。对于那些无法支持 WebP 的浏览器,大家则可以采取以下几种策略:
-
回退至常规的 JPEG 或者 PNG 格式(某些 CDN 会根据浏览器的 Accept 请求标头自动执行)。
-
在检测浏览器的支持情况后,加载并使用 WebP polyfill。
-
利用 Service Workers 监听 fetch 请求,并在支持时利用 WebP 变更实际 URL。
WebP 图像
仅当图像位于视图当中或者附近时才进行内容加载,堪称多图像初始页面加载过程中效果最显著的提速手段之一。您可以在受支持的浏览器当中使用 InterpObserver 功能,也可以利用其它一些替代性工具实现相同的结果——例如 react-lazyload。
在滚动过程中进行图像的延迟加载
总结
面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。
还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。
万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
前端面试题汇总
JavaScript
前端资料汇总