# 如何解决跨域?
jsonp
cors
postMessage
websocket
Node 中间件代理(两次跨域)
nginx 反向代理
window.name + iframe
location.hash + iframe
document.domain + iframe
不受跨域限制的标签和属性
link、script、img、background: url()、@font-face() 等均不受跨域限制。
# JSONP 的原理和优缺点
原理
- jsonp 的原理本质就是利用 script 标签的 src 属性的天然可跨域特性实现跨域,让服务器接收浏览器传过来的函数名,然后返回一个带参的函数调用,浏览器接收数据,执行函数调用,对返回的数据进行操作。
优点
可以实现跨域。
兼容性好。
在请求完毕后可以通过调用 callback 的方式回传结果。将回调方法的权限给了调用方。如果有两个页面需要渲染同一份数据,只需要有不同的渲染逻辑就可以了,逻辑都可以使用同一个 jsonp 服务。
缺点
只支持 GET 请求。
jsonp 在调用失败的时候不会返回各种 HTTP 状态码。
不安全可能会遭受 XSS 攻击。
# CORS 的原理
CORS(Cross-Origin-Resource-Sharing) 是在服务器端设置的,不需要在客户端进行操作。
CORS 背后的思想就是使用自定义的 http 头部让浏览器和服务器进行沟通,从而决定请求或响应应该成功还是失败。浏览器向服务器发送请求,如果服务器认为这个请求可以接受,就在
Access-Control-Allow-Origin
头部中返回相同的源信息(如果是公共资源,则返回*
);如果没有这个头部,或者有这个头部但信息源不匹配,浏览器就会驳回请求。正常情况下,浏览器会处理请求。请求和响应都不包含 cookie 信息。设置 CORS 之后,在发送请求时,会出现两种情况:简单请求和复杂请求。
(1)简单请求
只要同时满足以下两大条件,就属于简单请求。
条件 1:使用下列方法之一:
GET
HEAD
POST
条件 2:Content-Type
的值仅限于下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是头信息之中,增加一个 Origin 字段。Origin 字段用来说明请求来自哪个源(协议+域名+端口号)。服务端根据这个值,决定是否同意本次请求。
(2)复杂请求
不符合以上条件的请求就肯定是复杂请求了。比如请求方法为 PUT 或 DELETE,或者 Content-Type 字段为 application/json。
复杂请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为 “预检” 请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。预检请求的头信息中,关键字段有以下几个:
Origin
:表示请求来自哪个域;Access-Control-Request-Method
:必选,用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法;Access-Control-Request-Headers
:该字段是一个用逗号分割的字符串,执行浏览器 CORS 请求会额外发送的头信息字段。
服务器收到预检请求以后,检查了以上三个字段后,确认允许跨域请求,就可以做出回应。如果浏览器否定了 “预检” 请求,就会返回一个正常的 HTTP 回应,但是没有任何 CORS 相关的头信息字段,这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被 XMLHttpRequest 对象的 onerror 回调函数捕获。
一旦服务器通过了预检请求,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样,会有一个 Origin 头信息字段。服务器的回应,也都会有一个 Access-Control-Allow-Origin 头信息字段。
详细信息可参考:CORS 原理及 @koa/cors 源码解析 (opens new window)
# 图片懒加载和预加载的实现原理
1. 懒加载
懒加载就是延迟加载。
它的优点是页面加载速度快,可以减轻服务器的压力,节约流量,用户体验好。
它的实现原理是:先不设置图片的 src 属性,而是自定义一个 data-src 属性来储存图片的路径,由于不是 src,所以不会发送 http 请求。当图片滚动到可视区域时,再将 data-src 的值赋给 src。
2. 预加载
预加载就是提前加载图片,当用户需要查看时可直接从本地缓存中获取图片进行渲染。
预加载可以说是牺牲服务器的性能,增加服务器的压力,以换取更好的用户体验,这样可以使用户的操作得到最快的反应。
常用的是 new Image(),设置其 src 来实现预加载,再使用 onload 方法回调预加载完成事件。只要浏览器把图片下载到本地,同样的 src 就会使用缓存,这是最基本也是最实用的预加载方法。当 Image 下载完图片头后,会得到宽和高,因此可以在预加载前得到图片的大小(方法是用记时器轮循宽高变化)。
# 按要求实现 sum 函数
实现一个 sum 函数,有如下效果:
console.log(sum(1, 2, 3).sumOf()); // 6
console.log(sum(2, 3)(2).sumOf()); // 7
console.log(sum(1)(2)(3)(4).sumOf()); // 10
console.log(sum(1)(10, 100)(1000).sumOf()); // 1111
2
3
4
实现如下:
function sum(...arg1) {
function fn(...arg2) {
Array.prototype.push.apply(arg1, arg2);
return fn;
}
fn.sumOf = function() {
return arg1.reduce((prev, cur) => (prev += cur), 0);
};
return fn;
}
2
3
4
5
6
7
8
9
10
# 按要求实现 go 函数
console.log(go("1")); // go1
console.log(go()("1")); // goo1
console.log(go()()()("1")); // goooo1
2
3
这道题实际上考察的就是闭包,原理就是当没有传入参数时就默认传 'o',然后累加起来返回函数本身;当最后有参数时就返回之前累计的参数拼接字符串返回。
function go(...arg1) {
let str = "go";
function fn(...arg2) {
if (arg2[0]) {
str += arg2[0];
return str;
} else {
str += "o";
return fn;
}
}
return fn(...arg1);
}
2
3
4
5
6
7
8
9
10
11
12
13
# 按要求实现 curry 函数
function add(a, b) {
return a + b;
}
function curry(fn) {}
console.log(curry(add)(1)(2)); // 3
console.log(curry(add)(1, 2)); // 3
2
3
4
5
6
7
8
实现如下:
function curry(fn) {
const args = [];
return function cb() {
args.push(...arguments);
if (args.length === fn.length) {
return fn(...args);
}
return cb;
};
}
2
3
4
5
6
7
8
9
10
# 实现大整数相加
~ 是按位取反运算,~~ 是取反两次。因为位运算的操作值要求是整数,其结果也是整数,所以经过位运算的都会自动变成整数。
与 Math.floor() 不同的是,它只是单纯的去掉小数部分,不论正负都不会改变整数部分。
function addLargeInt(a, b) {
var res = "",
c = 0;
a = a.split("");
b = b.split("");
while (a.length || b.length || c) {
c += ~~a.pop() + ~~b.pop();
res = (c % 10) + res;
c = c > 9;
}
return res.replace(/^0+/, "");
}
2
3
4
5
6
7
8
9
10
11
12
# 图片转换成 base64 的优缺点
优点
base64 格式的图片是文本格式,占用内存小,转换后的大小比例大概为 1/3,降低了资源服务器的消耗。
减少 http 请求。
没有跨域问题,更适合不同平台、不同语言的传输。
缺点
base64 无法缓存,要缓存只能缓存包含 base64 的文件,比如 js 或者 css,这比直接缓存图片要差很多。
使用 base64 不代表性能优化,虽然能够减少 http 请求,但是与之同时付出的代价则是 CSS 文件体积的增大。CSS 文件体积的增大意味着 CRP 的阻塞。
补充
CRP(Critical Rendering Path,关键渲染路径):当浏览器从服务器接收到一个 HTML 页面的请求时,到屏幕上渲染出来要经过很多个步骤。浏览器完成这一系列的运行,或者说渲染出来我们常常称之为 “关键渲染路径”。
总而言之,就是图片不会导致关键渲染路径的阻塞,而转化为 base64 的图片大大增加了 CSS 文件的体积,CSS 文件的体积直接影响渲染,导致用户会长时间注视空白屏幕。
# 数组去重时间复杂度为 n 的方法
- 时间复杂度为 O(n^2)
function fn(arr) {
return arr.filter((item, index, arr) => arr.indexOf(item) === index);
}
2
3
- 时间复杂度为 O(n)
function fn(arr) {
const obj = {};
arr.map((item) => {
obj[item] = item;
});
return Object.values(obj);
}
2
3
4
5
6
7
# 如何判断一个元素在可视窗口范围内
1. 使用 getBoundingClientRect 方法
通过获取元素的位置信息,直接比较元素的位置和可视窗口的位置来判断。
function isElementInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <=
(window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
// 用法示例
const myElement = document.getElementById("myElement");
if (isElementInViewport(myElement)) {
console.log("Element is in the viewport!");
} else {
console.log("Element is not in the viewport!");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2. 使用 Intersection Observer API
使用 Intersection Observer API 能够异步地监听目标元素与视口的交叉,并在交叉状态发生改变时触发回调函数。
// 创建一个 IntersectionObserver 实例
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log("Element is in the viewport!");
} else {
console.log("Element is not in the viewport!");
}
});
});
// 监听特定元素
const targetElement = document.getElementById("myElement");
observer.observe(targetElement);
2
3
4
5
6
7
8
9
10
11
12
13
14
# 长列表的优化
# 虚拟列表的实现原理
# 成熟的虚拟列表组件
# 判断两个整数相除的结果
如何判断两个整数相除的结果是有限小数还是无限循环小数呢?
当除数(分母)只有 2 或者 5 这两个质因数时,就是有限小数;当除数(分母)含有 2 和 5 以外的质因数时就是无限循环小数。
补充知识
被除数 ÷ 除数 = 商 …… 余数
,因此分子是被除数,分母是除数。自然数是指表示物体个数的数,即由 0 开始,0,1,2,3,4,…… 一个接一个,组成一个无穷的集体,即指非负整数。
质数是指在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然数。
质因数(素因数或质因子)在数论里是指能整除给定正整数的质数。除了 1 以外,两个没有其他共同质因子的正整数称为互质。因为 1 没有质因子,1 与任何正整数(包括 1 本身)都是互质。
若整数 b 除以非零整数 a,商为整数,且余数为零, 我们就说 b 能被 a 整除(或说 a 能整除 b),b 为被除数,a 为除数,即 a|b(“|”是整除符号),读作“a 整除 b”或“b 能被 a 整除”。a 叫做 b 的约数(或因数),b 叫做 a 的倍数。
# 实现大文件上传和断点续传
大文件上传:
前端上传大文件时使用 Blob.prototype.slice (opens new window) 将文件切片,并发上传多个切片,最后发送一个合并的请求通知服务端合并切片。
服务端接收切片并存储,收到合并请求后使用 fs.appendFileSync (opens new window) 对多个切片进行合并。
使用原生 XMLHttpRequest 的 upload.onprogress (opens new window) 实现对切片上传进度的监听。
使用 Vue 计算属性根据每个切片的进度算出整个文件的上传进度。
断点续传:
使用 spark-md5 (opens new window) 根据文件内容算出文件 hash。
通过 hash 可以判断服务端是否已经上传该文件,从而直接提示用户上传成功(秒传)。
通过 XMLHttpRequest 的 abort (opens new window) 方法暂停切片的上传。
重新上传前服务端返回已经上传的切片名,前端跳过这些切片的上传。
实现大文件上传和断点续传 (opens new window)
# 前端常见的设计模式有哪些
1. 单例模式(Singleton Pattern)
介绍:保证一个类只有一个实例,并提供一个全局访问点。
应用:用于创建唯一的对象,例如全局状态管理、资源管理等。
示例:Redux 和 Vuex 中的 Store 就是一个单例模式的应用。
2. 观察者模式(Observer Pattern)
介绍:定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。
应用:用于事件处理、数据监听等场景。
示例:Vue 中的响应式数据就是基于观察者模式的。
3. 工厂模式(Factory Pattern)
介绍:定义一个用于创建对象的接口,但由子类决定要实例化的类是哪一个,工厂方法使得一个类的实例化延迟到其子类。
应用:用于对象的创建和初始化。
示例:React 中的组件工厂函数,根据传入的参数创建不同的组件。
4. 适配器模式(Adapter Pattern)
介绍:将一个类的接口转换成客户端希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
应用:用于解决不同接口之间的兼容性问题。
示例:封装第三方库的接口以适应自己的代码结构。
5. 装饰者模式(Decorator Pattern)
介绍:动态地给一个对象添加一些额外的职责,就增加功能而言,装饰者模式比生成子类更为灵活。
应用:用于动态添加对象的功能。
示例:React 中使用高阶组件。
6. 策略模式(Strategy Pattern)
介绍:定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。
应用:用于在运行时动态选择算法。
示例:JavaScript 中的排序算法,可以根据需要选择不同的排序策略。
7. 代理模式
介绍:为其他对象提供一种代理以控制对这个对象的访问。
应用:
虚拟代理,延迟加载大型图片,等图片需要显示时再加载;
保护代理,限制某些对象的访问权限。
# 内嵌 h5 和普通浏览器 h5 的区别
1. 环境和容器
内嵌 H5:指的是将 HTML5 页面嵌入到其他应用程序或容器中,通常是移动应用程序。这种情况下,H5 页面运行在应用程序的 webview 中。
普通浏览器 H5:指的是直接在普通的网页浏览器中打开的 HTML5 页面。
2. 性能和访问权限
内嵌 H5:可以访问应用程序的原生功能和设备硬件,例如相机、位置信息等。但有时可能受到应用程序容器的限制。
普通浏览器 H5:通常受到浏览器的安全策略和沙箱环境的限制,可能无法访问某些原生功能。
3. 用户体验
内嵌 H5:由于可以与应用程序深度集成,可能提供更一致的用户体验,包括共享样式、导航等。
普通浏览器 H5:在浏览器中打开,可能会有一些差异,例如导航栏、工具栏等可能不同于应用程序的设计。
4. 更新和维护
内嵌 H5:更新可能需要通过应用程序的更新来实现,因此可能不如网页那样灵活。
普通浏览器 H5:可以通过 web 更新,用户只需刷新页面即可获取最新版本。
# iframe 嵌套判断方法问题
请问使用如下代码判断当前页面是否被 iframe 嵌套会有什么缺陷吗?
if (window.self === window.top) {
console.log("不在iframe中");
} else {
console.log("在iframe中");
}
2
3
4
5
这种判断方法会存在以下 3 个问题:
1. 同源策略限制
如果页面被嵌入到一个具有不同源(不同的协议、域名或端口)的 iframe 中,浏览器的同源策略可能会阻止访问 window.top。在这种情况下,尝试访问 window.top 会报错。
2. 安全性考虑不全面
比如点击劫持,除了检测页面是否在 iframe 中,还应该采取额外的安全措施,比如设置 HTTP 头部的 X-Frame-Options 为 DENY 或 SAMEORIGIN 来防止页面被其他站点嵌入。
3. 兼容性问题
有些什么 QQ 或者微信内核啥的浏览器不一定对。
可以使用 postMesssage 通信的方式来告知当前页面是否被嵌套。
# 微前端
微前端是将前端单体应用拆分成一系列小型、独立的子应用,每个子应用都可以独立开发、测试、部署和运行。这些子应用可以独立存在,也可以组合成一个完整的前端应用。
# 组成部分
主应用(Shell):整个应用的外壳,负责加载和组合各个子应用,并提供整体的导航和布局。
子应用(Micro Frontends):拆分出的独立的功能单元,每个子应用都有自己的开发团队,可以使用不同的技术栈和框架。
# 通信机制
微前端的子应用之间需要进行通信,以便在整体上协同工作。通信可以通过以下方式实现:
自定义事件:子应用通过发布和订阅自定义事件的方式进行通信。
共享状态:使用状态管理工具,例如 Redux,将状态共享给其他子应用。
跨域通信:子应用可以通过跨域通信的方式实现数据的交互。
# 路由管理
微前端中的路由管理可以由主应用或者子应用自行处理。主应用可以负责整体路由,也可以将路由委托给各个子应用自行处理。这样,每个子应用都可以独立定义自己的路由规则。
# 独立部署
每个子应用都可以独立部署,这使得团队可以更加灵活地进行开发和发布。不同的子应用可以按照自己的发布周期进行部署,而不会影响整个应用的稳定性。
# 技术栈多样性
不同的子应用可以使用不同的技术栈和框架,因为它们之间是独立的。这样,团队可以选择最适合自己业务需求的技术栈,而不会受到整体应用的限制。
# 优势
松耦合:子应用之间相互独立,减少了耦合度。
独立开发和部署:提高团队的开发和发布效率,降低冲突和合并的复杂性。
技术栈多样性:允许团队根据业务需求选择最适合的技术栈。
可维护性:将大型应用拆分成小块,更容易理解、测试和维护。
# 挑战
版本管理:管理各个子应用的版本兼容性可能是一个挑战。
共享状态管理:在微前端架构中,如何有效地管理和共享状态是一个需要考虑的问题。
安全性:子应用之间的通信需要考虑安全性问题,防止潜在的攻击。
# 实现方案
# Serverless
Serverless 是一种云计算服务模型,允许开发者构建和运行应用程序,而无需关心底层服务器的管理。
在 Serverless 架构中,应用程序由事件触发的小型函数组成,这些函数由云服务提供商动态地管理和扩展。
# 解决的问题
运维复杂性:传统的服务器管理涉及到配置、维护和监控,而 Serverless 将这些任务交给云服务提供商,开发者无需关注。
弹性扩展:Serverless 提供弹性伸缩,按需自动调整资源,无需手动调整服务器规模,从而更好地适应应用的变化。
成本优化:通过按使用量计费,Serverless 让开发者只需支付实际使用的计算资源,而无需预先购买和维护庞大的服务器群。
快速部署:Serverless 允许开发者快速部署应用,而无需关心服务器的配置和环境问题。
# 特点和优势
事件驱动:Serverless 应用通常是事件驱动的,函数在特定事件发生时被触发执行,如 HTTP 请求、文件上传等。
无服务器架构:开发者不需要关心服务器的配置、维护和扩展,只需编写函数代码,云服务商负责处理底层基础设施。
按需计费:Serverless 模型按照实际使用的资源进行计费,开发者只需支付实际消耗的计算资源,而不需要预先购买。
自动扩展:Serverless 提供自动伸缩能力,根据请求负载自动调整资源,确保应用在高负载时能够保持响应。
多语言支持:支持多种编程语言,开发者可以根据项目需求选择最适合的语言来编写函数。
无状态函数:Serverless 函数通常是无状态的,每个函数都是独立的,不保留上下文信息,使得水平扩展更容易实现。
快速部署:Serverless 应用可以更快速地部署和更新,无需关心底层基础设施的变化。
# 应用场景
API 和后端服务:构建 RESTful API、后端服务、微服务等,使开发者能够快速部署和扩展。
数据处理:适用于数据处理、转换、清洗、分析等场景,例如处理实时日志、图像处理等。
实时通知:Serverless 可以用于构建实时通知系统,例如推送通知、即时聊天等。
定时任务:用于执行定时触发的任务,如定时备份、数据清理等。
Web 应用前端:用于构建静态网站、单页应用等前端应用,例如使用服务端渲染框架。
IoT 应用:处理大量传感器数据,进行实时分析和响应,适用于物联网应用。
事件驱动的处理:使用 Serverless 构建事件驱动的处理系统,例如处理队列消息、数据库变更通知等。函数可以根据事件动态触发,响应不同的事件类型。
自动化工作流:Serverless 可以用于构建自动化工作流,例如自动化部署、自动化测试、自动化数据流程等。函数可以作为工作流的各个步骤。
# WebAssembly
WebAssembly (opens new window)(通常缩写为 Wasm)不是一种语言,而是规定了一种虚拟指令集,可以作为各个语言的编译目标,然后通过 wasm 的虚拟机运行到浏览器还有其他各个平台中。
对于前端领域,WebAssembly 是⼀种为⽹络浏览器设计的低级编程语⾔,它允许在⽹⻚中运⾏接近原⽣性能的⾼效代码。WebAssembly 被⼴泛⽤于各种应⽤场景,特别是在性能要求较⾼的场合。
WebAssembly 的应⽤⼴泛,涉及游戏开发、图形处理、⾳频处理、机器学习、科学模拟等多个领域。它为 Web 开发带来了前所未有的性能提升,使得以前只能在桌⾯应⽤中实现的功能现在可以通过浏览器实现,极⼤地拓展了 Web 技术的应⽤范围。
1. 游戏开发
原⽣游戏移植:将 C++ 或其他语⾔编写的原⽣游戏移植到 Web 上,使其能够在浏览器中以接近原⽣的性能运⾏。
2. 图形和视频处理
图像编辑器:开发⾼性能的在线图像和视频编辑⼯具,例如 Photoshop 的在线版或其他图形处理软件。
3. ⾳频处理
⾳频合成:实现在浏览器中的⾼效⾳频合成和处理,例如⾳乐制作软件。
4. 3D 渲染
WebGL 集成:结合 WebGL 使⽤ WebAssembly 进⾏ 3D 渲染,适⽤于在线游戏、CAD 应⽤和虚拟现实(VR)体验。
5. 加密和安全
密码学应⽤:在客户端实现⾼效的加密和哈希算法,提升数据处理的安全性和速度。
6. 机器学习和⼈⼯智能
浏览器端机器学习:在浏览器中运⾏机器学习模型,例如使⽤ TensorFlow.js。
7. 科学模拟和数据可视化
物理模拟:进⾏复杂的物理和科学模拟,如分⼦建模、天⽓模拟等。
8. 移动应⽤和跨平台解决⽅案
PWA(渐进式⽹络应⽤):提⾼ PWA 的性能,使其接近原⽣应⽤体验。
9. ⾼性能计算
数值计算:执⾏复杂的数值计算任务,如⾦融建模、⼯程计算等。
10. 编程语⾔和⼯具链
编译器和⼯具链:在浏览器中运⾏编程语⾔的编译器或解释器,实现浏览器端的代码编译和执⾏。
# Web Component
Web Component (opens new window) 是⼀组不同的技术,允许创建可重⽤的⾃定义元素,并在 Web 应⽤中封装功能和样式。
它的核心技术包括 Custom Elements (opens new window)、Shadow DOM (opens new window) 和 HTML Templates (opens new window)。
Custom element(自定义元素):允许开发者定义自己的 HTML 元素。通过创建自定义元素,你可以将复杂的功能打包成一个独立的元素,使其可以在不同的页面和项目中重复使用。
Shadow DOM(影子 DOM):提供了一种将样式和脚本封装在元素内部的方法,以防止它们影响到外部文档。这有助于避免全局样式和脚本的冲突,并提高组件的封装性。
HTML template(HTML 模板):
<template>
和<slot>
元素允许开发者将标记代码定义为模板,并在需要时克隆和插入到文档中。这有助于创建可重复使用的结构,并通过 JavaScript 进行实例化和填充。
Web Component 作为 Web 标准的⼀部分,由浏览器原⽣⽀持。它可以与任何 JavaScript 框架⼀起使⽤,或者完全独⽴使⽤。不过,虽然现代浏览器⼤多⽀持 Web Components,但在旧版浏览器中可能需要额外的 polyfills。
# Tailwind CSS
Tailwind CSS (opens new window) 是一个基于原子类的 CSS 框架,它提供了一组低级、单一职责的 CSS 类,以构建灵活且高度可定制的界面。与传统的 CSS 框架(如 Bootstrap 或 Foundation)不同,Tailwind 不会提供预定义的组件,而是专注于提供原子级的样式类,让开发者能够组合这些类来构建自己的界面。
原子类:Tailwind 的主要特点是原子类。这些类名通常表示单一样式属性,比如 text-center 用于文本居中,bg-blue-500 用于设置背景颜色为蓝色。
响应式设计:Tailwind 提供了一套响应式的工具类,用于在不同屏幕尺寸下设置样式。例如,你可以使用 lg:text-xl 来指定在大屏幕上使用更大的文字大小。
自定义配置:Tailwind 是高度可定制的,你可以通过配置文件调整默认的颜色、字体大小、边距等值。这使得它非常适用于各种设计系统和项目需求。
插件系统:Tailwind 允许你通过插件系统扩展其功能。这意味着你可以集成第三方插件,或者编写自己的插件以满足特定的需求。
简化开发流程:Tailwind 的使用可以极大地简化开发流程,减少需要手写的 CSS 代码。通过原子类的组合,你能够快速构建出样式,而无需深入了解每个具体的 CSS 属性。
易于学习:相对于其他复杂的 CSS 框架,Tailwind 的学习曲线相对较低。它提供了清晰简单的类名,而且文档详尽,使得开发者能够快速上手。
无需编写自定义 CSS:在使用 Tailwind 时,你通常不需要编写大量的自定义 CSS。通过合理地组合现有的类,你可以实现各种样式和布局。
尽管 Tailwind 在许多项目中得到了广泛的应用,但也有一些人认为它可能使 HTML 变得更加臃肿,因为需要添加大量的类名。不过,这个问题也可以通过使用 @apply 提取类 (opens new window)来优化:
<!-- Before extracting a custom class -->
<button
class="py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75"
>
Save changes
</button>
<!-- After extracting a custom class -->
<button class="btn-primary">
Save changes
</button>
2
3
4
5
6
7
8
9
10
11
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn-primary {
@apply py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75;
}
}
2
3
4
5
6
7
8
9
# GraphQL
GraphQL (opens new window) 是由 Facebook 开发的⼀种数据查询和操作语⾔,以及与之配套的运⾏时。它有以下优点:
精确查询:客户端可以精确指定所需的数据结构,避免过度获取或不⾜获取数据。
单⼀端点:与 RESTful API 不同,GraphQL 通常只有一个端点(Endpoint),客户端可以通过这个端点发送不同的查询来获取所需的数据,而不是依赖多个固定的端点。
类型系统:GraphQL 有⼀个强类型系统,可以在查询执⾏前进⾏验证。
实时数据(通过订阅):⽀持实时数据更新功能,服务器能够主动推送数据变更给客户端,而不需要客户端轮询。
可扩展性:GraphQL 是一个灵活的查询语言,允许在后端进行逐步演进和扩展,而不会破坏现有的客户端查询。
文档性:GraphQL 支持自动文档生成。通过查询端点,可以获得 API 的详细文档,包括可用的类型、字段和关联关系。
生态系统:GraphQL 已经在多个编程语言中得到实现和支持,使其成为一个跨平台、跨语言的解决方案。
GraphQL 的核心思想是将数据的获取权力交给客户端,使得客户端能够更灵活地获取所需的数据,而不受服务器端的限制。它适合需要⾼度灵活性和精确控制数据交互的复杂应⽤,尤其是在多变的产品需求和多端数据需求频繁的场景。
# tRPC
tRPC(Typed RPC) (opens new window)是一个用于 TypeScript 的端到端类型安全的远程过程调用(RPC)框架。它强调使用 TypeScript 的类型系统,通过在 TypeScript 中定义接口来确保远程调用的类型安全性。
1. 类型安全
tRPC 是一个强类型的 RPC 框架,它利用 TypeScript 的类型系统来确保在编译时捕获潜在的远程调用错误。这意味着你可以在开发过程中受益于 TypeScript 的强大类型检查,避免在运行时发现类型错误。
2. 自动代码生成
通过在 TypeScript 中定义接口,tRPC 提供了自动代码生成的功能,它可以为你生成客户端和服务端的代码。这消除了手动同步客户端和服务端代码的问题,确保它们一直保持同步,减少了潜在的错误。
3. 减少文档不一致性
tRPC 中的接口定义即是文档。你不需要额外的文档来描述远程调用的接口,因为接口已经以代码的形式存在。这有助于避免文档和代码不一致的问题。
4. 开发效率提升
使用强类型的远程调用接口,可以在开发过程中获得智能感知、自动完成和类型检查等功能,从而提高开发效率。你可以在编辑器中直接看到可用的远程调用方法,以及它们的输入和输出类型。
5. 错误处理
tRPC 提供了强大的错误处理机制。你可以定义各种错误类型,并在服务端和客户端上进行捕获和处理。这有助于更好地处理错误情况,提高应用的稳定性。
6. 扩展性
tRPC 是可扩展的,支持插件和中间件。这意味着你可以根据自己的需求自定义和扩展功能,使 tRPC 更好地适应你的项目要求。
7. 支持多种传输协议
tRPC 支持多种传输协议,包括 HTTP、WebSocket 等,使其适用于不同的应用场景。你可以选择最适合你项目的传输方式。
# Monorepo
Monorepo (opens new window) 是一种项目代码管理方式,指单个仓库中管理多个项目,有助于简化代码共享、版本控制、构建和部署等方面的复杂性,并提供更好的可重用性和协作性。
1. 优点
一个仓库中多个相关项目,很容易看到整个代码库的变化趋势,更好的团队协作。
多项目代码都在一个仓库中,相同版本依赖提升到顶层只安装一次,节省磁盘内存。
代码复用高,方便进行代码重构。
依赖调试方便,依赖包迭代场景下,借助工具自动 npm link,直接使用最新版本依赖,简化了操作流程。
多项目在一个仓库,工程配置一致,代码质量标准及风格也很容易一致。
构建性 Monorepo 工具可以配置依赖项目的构建优先级,可以实现一次命令完成所有的部署。
2. 缺点
增加了非 owner 改动代码的风险。
多个项目代码都在一个仓库中,没有项目粒度的权限管控,一个项目出问题,可能影响所有项目。
多项目在一个仓库中,代码体积多达几个 G,git clone 时间较长。
# 面试资源
← JavaScript 实用工具 →