2024.01.02 - 2024.04.12 更新前端面试问题总结(17道题)
获取更多面试相关问题可以访问
github 地址: github.com/pro-collect...
gitee 地址: gitee.com/yanleweb/in...
目录:
-
初级开发者相关问题【共计 1 道题】
- 677.JS 放在 head 里和放在 body 里有什么区别?【热度: 420】【web应用场景】【出题公司: 阿里巴巴】
-
中级开发者相关问题【共计 4 道题】
- 664.递归和尾递归是什么概念?【热度: 649】【JavaScript】【出题公司: TOP100互联网】
- 669.常见网络协议有哪些【热度: 724】【网络】【出题公司: TOP100互联网】
- 676.列表分页, 快速翻页下的竞态问题【热度: 444】【网络】【出题公司: 阿里巴巴】
- 680.HTTP是一个无状态的协议,那么Web应用要怎么保持用户的登录态呢?【热度: 1,092】【网络】【出题公司: TOP100互联网】
-
高级开发者相关问题【共计 12 道题】
- 663.如何禁止别人调试自己的前端页面代码?【热度: 347】【web应用场景】【出题公司: TOP100互联网】
- 665.TypeScript 内置的类型方法和工具类型【热度: 492】【TypeScript】【出题公司: TOP100互联网】
- 666.git 仓库迁移应该怎么操作【热度: 160】【web应用场景】【出题公司: 小米】
- 667.Protobuf 相关知识【热度: 216】【网络】【出题公司: 阿里巴巴】
- 668.权限管理模型相关概念【热度: 670】【web应用场景】【出题公司: TOP100互联网】
- 671.grpc 和 protobuf 是什么关系?【热度: 320】【网络】【出题公司: TOP100互联网】
- 672.JS 执行 100 万个任务, 如何保证浏览器不卡顿?【热度: 806】【web应用场景】【出题公司: TOP100互联网】
- 673.http 中 CSP 是什么【热度: 323】【网络】【出题公司: 阿里巴巴】
- 674.http 中 HSTS 是什么【热度: 374】【网络】【出题公司: 阿里巴巴】
- 675.CORS 请求中,是如何触发预检请求【热度: 229】【网络】【出题公司: 阿里巴巴】
- 678.为什么 Vite 速度比 Webpack 快?【热度: 382】【工程化】【出题公司: 腾讯】
- 679.如何检测网页空闲状态(一定时间内无操作)【热度: 329】【web应用场景】【出题公司: 百度】
初级开发者相关问题【共计 1 道题】
677.JS 放在 head 里和放在 body 里有什么区别?【热度: 420】【web应用场景】【出题公司: 阿里巴巴】
将 JavaScript 代码放在 <head>
标签内部和放在 <body>
标签内部有一些区别:
- 加载顺序 :放在
<head>
里会在页面加载之前执行 JavaScript 代码,而放在<body>
里会在页面加载后执行。 - 页面渲染 :如果 JavaScript 代码影响了页面的布局或样式,放在
<head>
里可能会导致页面渲染延迟,而放在<body>
里可以减少这种影响。 - 代码依赖 :如果 JavaScript 代码依赖其他元素,放在
<body>
里可以确保这些元素已经加载。 - 全局变量和函数 :放在
<head>
里的 JavaScript 代码中的全局变量和函数在整个页面生命周期内都可用。
以下是一个简单的示例代码,展示了如何在 <head>
和 <body>
中放置 JavaScript 代码:
xml
<!DOCTYPE html>
<html>
<head>
<script>
console.log("这是在 head 中执行的 JavaScript 代码。");
</script>
</head>
<body>
<script>
console.log("这是在 body 中执行的 JavaScript 代码。");
</script>
</body>
</html>
在这个示例中,分别在 <head>
和 <body>
中放置了简单的 JavaScript 代码,用于在控制台输出信息,以便观察执行顺序。
中级开发者相关问题【共计 4 道题】
664.递归和尾递归是什么概念?【热度: 649】【JavaScript】【出题公司: TOP100互联网】
关键词:递归和尾递归
递归和尾递归都是指在函数内部调用自身的方式,但它们有一些关键的区别。
概念
递归是一种函数调用自身的方式。在递归中,函数会不断地调用自身,直到满足某个终止条件才停止递归。递归通常使用在解决可以通过重复拆分为更小的子问题来解决的问题上。但是,递归可能会导致函数调用的层级过深,消耗大量的内存,因为每次递归调用都会在内存中创建一个新的函数调用帧。如果没有正确的终止条件,递归可能会导致无限循环。
尾递归是一种特殊的递归形式,在尾递归中,递归调用是函数的最后一个操作,并且递归调用的结果直接返回,没有进行任何额外的操作。因此,尾递归不会导致函数调用栈的增长,每次递归调用都会覆盖当前的函数帧。尾递归可以避免函数调用栈溢出的问题,因为它在递归调用时不会导致函数调用栈的增长。尾递归通常使用在需要迭代大量数据的情况下,可以有效地优化性能。
要注意,不是所有的递归都可以被优化为尾递归,只有当递归调用是函数的最后一个操作时,才可以进行尾递归优化。在一些编程语言中,编译器或解释器可以自动进行尾递归优化,将尾递归转换为迭代循环,从而提高性能。但在一些语言中,需要显示地使用尾递归优化的技巧,如使用尾递归函数的辅助参数来保存中间结果。
示例
下面是一个递归函数的例子,用于计算一个正整数的阶乘:
javascript
function factorial(n) {
if (n === 0) { // 终止条件
return 1;
} else {
return n * factorial(n - 1); // 递归调用
}
}
console.log(factorial(5)); // 输出 120
现在,我们将对上述递归函数进行尾递归优化。在这个例子中,我们使用一个辅助参数result
来保存每次递归调用的结果,并将其作为参数传递给下一次递归调用。这样,递归调用不会导致函数调用栈的增长。
javascript
function factorialTail(n, result = 1) {
if (n === 0) { // 终止条件
return result;
} else {
return factorialTail(n - 1, n * result); // 尾递归调用
}
}
console.log(factorialTail(5)); // 输出 120
通过使用尾递归优化,我们可以避免函数调用栈的溢出,并提高函数的性能。
如何理解:只有当递归调用是函数的最后一个操作时,才可以进行尾递归优化
在一个函数中,如果递归调用之后还有其他的操作或表达式需要执行,那么这个递归调用就不是尾递归。在这种情况下,函数需要等待递归调用的返回值,然后才能进行下一步操作。
而尾递归是指在函数的最后一步操作中进行的递归调用。这意味着函数在调用自身之后没有其他操作或表达式需要执行,直接返回递归调用的结果。这种情况下,函数可以被优化为尾递归形式,避免函数调用栈的溢出和性能问题。
在尾递归优化的代码示例中,递归调用factorialTail(n - 1, n * result)是函数factorialTail的最后一步操作,它的返回值直接作为函数的返回值,没有其他操作需要执行。因此,这个递归调用是尾递归,可以进行尾递归优化。
669.常见网络协议有哪些【热度: 724】【网络】【出题公司: TOP100互联网】
关键词:网络协议
协议 | 层次 | 说明 |
---|---|---|
HTTP/HTTPS | 应用层 | 用于在 Web 浏览器和 Web 服务器之间传输超文本的协议。 |
TCP | 传输层 | 面向连接、可靠的传输层协议,以字节流的形式传输数据。 |
UDP | 传输层 | 无连接、不可靠的传输层协议,以数据包的形式传输数据。 |
FTP | 应用层 | 用于在客户端和服务器之间传输文件的协议。 |
SFTP | 应用层 | 在安全通道上传输文件的协议,基于 SSH 协议。 |
SMTP | 应用层 | 用于在邮件服务器之间传递电子邮件的协议。 |
POP3 | 应用层 | 用于从邮件服务器接收电子邮件的协议。 |
IMAP | 应用层 | 用于在邮件客户端和邮件服务器之间管理和检索电子邮件的协议。 |
SSH | 应用层 | 用于在网络上安全地进行远程登录和执行命令的协议。 |
WebSocket | 应用层 | 在单个 TCP 连接上进行全双工通信的协议,用于实时双向通信。 |
gRPC | 应用层 | 基于 HTTP/2 的远程过程调用(RPC)框架,支持多语言、双向流等特性。 |
676.列表分页, 快速翻页下的竞态问题【热度: 444】【网络】【出题公司: 阿里巴巴】
关键词:翻页场景竞态问题
关键词:翻页场景竞态问题
列表分页, 快速翻页下的竞态问题
问题描述:比如在前端分页请求的时候, 因为翻页很快, 所以请求还没有来得及回来的时候, 就发起了下一次请求, 且请求返回的时间也是不固定的。 如何保证最后一次请求结果和其请求页码是对应上的。
在处理这种情况时,一种常见的方法是使用请求标记或唯一标识符来确保请求和结果之间的对应关系。
以下是一个示例代码片段,展示了一种可能的解决方案:
javascript
// 存储请求的标记
let requestId = 0;
// 发起请求的函数
function sendRequest(page) {
requestId++;
// 将请求标记与页码一起发送
fetch(`/api?requestId=${requestId}&page=${page}`)
.then(response => response.json())
.then(data => {
// 根据请求标记处理返回的数据
handleResponseData(requestId, data);
});
}
// 处理返回数据的函数
function handleResponseData(requestId, data) {
if (requestId === currentRequestId) {
// 在这里处理数据并更新页面
}
}
// 在翻页时调用 sendRequest 函数
在这个示例中,每次发起请求时都会增加请求标记 requestId
,并将其与页码一起发送到服务器。在处理返回的数据时,根据请求标记来确保与当前的请求对应。
另外,还可以考虑以下几点:
- 对快速翻页进行限制或优化,避免过于频繁的请求。
- 在服务器端处理请求时,可以根据请求标记来保证返回的数据与特定的请求相关联。
- 可以使用缓存来存储部分数据,减少不必要的请求。
保证唯一性
保证请求标记的唯一性可以通过以下几种方式:
- 使用递增的数字:就像上面示例中的
requestId
一样,每次增加 1。 - 使用随机数:生成一个随机的数字作为请求标记。
- 使用时间戳:结合当前时间生成唯一的标记。
- 组合多种因素:例如,将数字、时间戳或其他相关信息组合起来创建唯一标记。
例如,使用时间戳作为请求标记的示例代码如下:
ini
let requestId = Date.now();
这样每次请求时,requestId
都会是一个唯一的时间戳值。
680.HTTP是一个无状态的协议,那么Web应用要怎么保持用户的登录态呢?【热度: 1,092】【网络】【出题公司: TOP100互联网】
关键词:登录状态问题
涉及到的几个知识点:
- cookie,session,token(json web token,jwt)的区别
- node 中 jwt 的应用
直接参考文档即可: juejin.cn/post/735678...
高级开发者相关问题【共计 12 道题】
663.如何禁止别人调试自己的前端页面代码?【热度: 347】【web应用场景】【出题公司: TOP100互联网】
关键词:禁止别人调试自己的前端代码
无限 debugger
- 前端页面防止调试的方法主要是通过不断
debugger
来疯狂输出断点,因为debugger
在控制台被打开的时候就会执行 - 由于程序被
debugger
阻止,所以无法进行断点调试,所以网页的请求也是看不到的 - 基础代码如下:
scss
/**
* 基础禁止调试代码
*/
(() => {
function ban() {
setInterval(() => {
debugger;
}, 50);
}
try {
ban();
} catch (err) { }
})();
无限 debugger 的对策
- 如果仅仅是加上面那么简单的代码,对于一些技术人员而言作用不大
- 可以通过控制台中的
Deactivate breakpoints
按钮或者使用快捷键Ctrl + F8
关闭无限debugger
- 这种方式虽然能去掉碍眼的
debugger
,但是无法通过左侧的行号添加breakpoint
禁止断点的对策
- 如果将
setInterval
中的代码写在一行,就能禁止用户断点,即使添加logpoint
为false
也无用 - 当然即使有些人想到用左下角的格式化代码,将其变成多行也是没用的
scss
(() => {
function ban() {
setInterval(() => { debugger; }, 50);
}
try {
ban();
} catch (err) { }
})();
忽略执行的代码
- 通过添加
add script ignore list
需要忽略执行代码行或文件 - 也可以达到禁止无限
debugger
忽略执行代码的对策
- 那如何针对上面操作的恶意用户呢
- 可以通过将
debugger
改写成Function("debugger")();
的形式来应对 Function
构造器生成的debugger
会在每一次执行时开启一个临时js
文件- 当然使用的时候,为了更加的安全,最好使用加密后的脚本
scss
// 加密前
(() => {
function ban() {
setInterval(() => {
Function('debugger')();
}, 50);
}
try {
ban();
} catch (err) { }
})();
// 加密后
eval(function(c,g,a,b,d,e){d=String;if(!"".replace(/^/,String)){for(;a--;)e[a]=b[a]||a;b=[function(f){return e[f]}];d=function(){return"\w+"};a=1}for(;a--;)b[a]&&(c=c.replace(new RegExp("\b"+d(a)+"\b","g"),b[a]));return c}('(()=>{1 0(){2(()=>{3("4")()},5)}6{0()}7(8){}})();',9,9,"block function setInterval Function debugger 50 try catch err".split(" "),0,{}));
终极增强防调试代码
- 为了让自己写出来的代码更加的晦涩难懂,需要对上面的代码再优化一下
- 将
Function('debugger').call()
改成(function(){return false;})['constructor']('debugger')['call']();
- 并且添加条件,当窗口外部宽高和内部宽高的差值大于一定的值 ,我把
body
里的内容换成指定内容 - 当然使用的时候,为了更加的安全,最好加密后再使用
javascript
(() => {
function block() {
if (window.outerHeight - window.innerHeight > 200 || window.outerWidth - window.innerWidth > 200) {
document.body.innerHTML = "检测到非法调试,请关闭后刷新重试!";
}
setInterval(() => {
(function () {
return false;
}
['constructor']('debugger')
['call']());
}, 50);
}
try {
block();
} catch (err) { }
})();
参考文档
665.TypeScript 内置的类型方法和工具类型【热度: 492】【TypeScript】【出题公司: TOP100互联网】
关键词:ts 内置类型方法、ts 内置工具类型
TypeScript 提供了许多内置的类型方法和工具类型,用于处理和操作类型。以下是其中一些常用的内置类型方法:
分类
-
Utility Types(工具类型) :
- Partial : 将类型 T 的所有属性变为可选。
- Required : 将类型 T 的所有属性变为必选。
- Readonly : 将类型 T 的所有属性变为只读。
- Record<K, T> : 创建一个具有指定键类型 K 和值类型 T 的新对象类型。
- Pick<T, K> : 从类型 T 中选择指定属性 K 形成新类型。
- Omit<T, K> : 从类型 T 中排除指定属性 K 形成新类型。
- Exclude<T, U> : 从类型 T 中排除可以赋值给类型 U 的类型。
- Extract<T, U> : 从类型 T 中提取可以赋值给类型 U 的类型。
- NonNullable : 从类型 T 中排除 null 和 undefined 类型。
- ReturnType : 获取函数类型 T 的返回类型。
- Parameters : 获取函数类型 T 的参数类型组成的元组类型。
-
条件判定类型:
- Conditional Types(条件类型) : 根据类型关系进行条件判断生成不同的类型。
- Distribute Conditional Types(分布式条件类型) : 分发条件类型,允许条件类型在联合类型上进行分发。
-
Mapped Types(映射类型) :根据已有类型创建新类型,通过映射类型可以生成新的类型结构。
-
Template Literal Types(模板文字类型) :使用字符串模板创建新类型。
-
类型推断关键字:
- keyof关键字:关键字允许在泛型条件类型中推断类型变量。
- instanceof:运算符用于检查对象是否是特定类的实例。
- in:用于检查对象是否具有特定属性。
- type guards:类型守卫是自定义的函数或条件语句,用于在代码块内缩小变量的类型范围。
- as:用于类型断言,允许将一个变量断言为特定的类型。
这些工具类型和方法使得在 TypeScript 中能够更灵活地操作和利用类型系统,增强了类型的安全性和可读性。
Utility Types(工具类型)介绍
当涉及到 TypeScript 中的这些工具类型时,它们都是为了便捷地处理和操作类型而设计的。让我为你逐个介绍并提供代码示例:
1. Partial
这个类型将类型 T
的所有属性变为可选。
示例:
ini
interface User {
name: string;
age: number;
}
type PartialUser = Partial<User>;
// PartialUser 的类型为 { name?: string; age?: number; }
const partialUserData: PartialUser = {}; // 全部属性变为可选
2. Required
与 Partial
相反,该类型将类型 T
的所有属性变为必选。
示例:
ini
interface PartialUser {
name?: string;
age?: number;
}
type RequiredUser = Required<PartialUser>;
// RequiredUser 的类型为 { name: string; age: number; }
const requiredUserData: RequiredUser = { name: 'John', age: 25 }; // 全部属性变为必选
3. Readonly
将类型 T
的所有属性变为只读。 一旦复制之后是不允许更改的。
示例:
ini
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
// ReadonlyUser 的类型为 { readonly name: string; readonly age: number; }
const user: ReadonlyUser = { name: 'Alice', age: 30 };
// user.name = 'Bob'; // 这里会报错,因为属性是只读的
4. Record<K, T>
该类型创建一个具有指定键类型 K
和值类型 T
的新对象类型。
示例:
css
type PageInfo = {
title: string;
};
type Page = 'home' | 'about' | 'contact';
const pages: Record<Page, PageInfo> = {
home: { title: 'Home' },
about: { title: 'About' },
contact: { title: 'Contact' },
};
// pages 的类型为 { home: PageInfo; about: PageInfo; contact: PageInfo; }
5. Pick<T, K>
从类型 T
中选择指定属性 K
形成新类型。
示例:
ini
interface User {
name: string;
age: number;
email: string;
}
type UserBasicInfo = Pick<User, 'name' | 'email'>;
// UserBasicInfo 的类型为 { name: string; email: string; }
const basicUserInfo: UserBasicInfo = { name: 'Sarah', email: 'sarah@example.com' };
6. Omit<T, K>
与 Pick
相反,该类型从类型 T
中排除指定属性 K
形成新类型。
示例:
ini
interface User {
name: string;
age: number;
email: string;
}
type UserWithoutAge = Omit<User, 'age'>;
// UserWithoutAge 的类型为 { name: string; email: string; }
const userWithoutAge: UserWithoutAge = { name: 'Alex', email: 'alex@example.com' };
当涉及到 Exclude<T, U>
和 Extract<T, U>
时,让我们进一步丰富例子来更好地说明它们的用法。
7. Exclude<T, U>
Exclude<T, U>
从类型 T
中排除可以赋值给类型 U
的类型。
举例:
ini
type T = string | number | boolean;
type U = string | boolean;
type OnlyNumber = Exclude<T, U>;
// OnlyNumber 的类型为 number
const example1: OnlyNumber = 10; // 可以赋值,因为只有 number 类型被提取
// const example2: OnlyNumber = 'Hello'; // 这行会报错,因为 string 类型被排除
// const example3: OnlyNumber = true; // 这行也会报错,因为 boolean 类型被排除
function printValue(val: OnlyNumber) {
console.log(val);
}
printValue(20); // 可以传入,因为参数类型为 OnlyNumber
// printValue('Hi'); // 这行会报错,因为参数类型不是 OnlyNumber
在这个例子中,T
是 string | number | boolean
,U
是 string | boolean
。Exclude<T, U>
从 T
中排除了 U
中包含的类型,所以 OnlyNumber
的类型就只有 number
。这个类型可以在函数参数上提供类型安全性,确保只接受特定类型的参数。
8. Extract<T, U>
Extract<T, U>
从类型 T
中提取可以赋值给类型 U
的类型。
举例:
ini
type T = string | number | boolean;
type U = string | boolean;
type OnlyStringOrBoolean = Extract<T, U>;
// OnlyStringOrBoolean 的类型为 string | boolean
const example1: OnlyStringOrBoolean = 'Hello'; // 可以赋值,因为 string 类型被提取
const example2: OnlyStringOrBoolean = true; // 也可以赋值,因为 boolean 类型也被提取
// const example3: OnlyStringOrBoolean = 10; // 这行会报错,因为 number 类型被排除
function printValue(val: OnlyStringOrBoolean) {
console.log(val);
}
printValue('Hey'); // 可以传入,因为参数类型为 OnlyStringOrBoolean
printValue(true); // 也可以传入,因为参数类型为 OnlyStringOrBoolean
// printValue(30); // 这行会报错,因为参数类型不是 OnlyStringOrBoolean
在这个例子中,T
是 string | number | boolean
,U
是 string | boolean
。Extract<T, U>
从 T
中提取了 U
中包含的类型,所以 OnlyStringOrBoolean
的类型就是 string | boolean
。这个类型可以用在函数参数上,确保只接受特定的类型作为参数,提高代码的类型安全性。
9. NonNullable
NonNullable<T>
类型从类型 T
中排除 null
和 undefined
类型。
示例:
ini
type T = string | null | undefined;
type NonNullString = NonNullable<T>;
// NonNullString 的类型为 string
const example: NonNullString = 'Hello'; // 可以赋值,因为 null 和 undefined 被排除
// const example2: NonNullString = null; // 这行会报错,因为 null 被排除
在这个例子中,NonNullable
从 string | null | undefined
中排除了 null
和 undefined
类型,只保留了 string
类型。
10. ReturnType
ReturnType<T>
类型获取函数类型 T
的返回类型。
示例:
csharp
function greet(): string {
return 'Hello!';
}
type GreetReturnType = ReturnType<typeof greet>;
// GreetReturnType 的类型为 string
const result: GreetReturnType = 'Hi'; // 可以赋值,因为函数的返回类型是 string
// const result2: GreetReturnType = 10; // 这行会报错,因为类型不匹配
ReturnType
获取了 greet
函数的返回类型,因此 GreetReturnType
就是 string
类型。
11. Parameters
Parameters<T>
类型获取函数类型 T
的参数类型组成的元组类型。
示例:
typescript
function greet(name: string, age: number): void {
console.log(`Hello, ${name}! You are ${age} years old.`);
}
type GreetFunctionParams = Parameters<typeof greet>;
// GreetFunctionParams 的类型为 [string, number]
const example: GreetFunctionParams = ['Alice', 30]; // 可以赋值,因为参数类型匹配
// const example2: GreetFunctionParams = ['Bob', '20']; // 这行会报错,因为参数类型不匹配
Parameters
获取了 greet
函数的参数类型组成的元组类型 [string, number]
,因此 GreetFunctionParams
就是包含了函数参数类型的元组类型。
条件判定类型
条件类型是 TypeScript 中强大且灵活的类型构造方式,它允许根据类型关系进行条件判断生成不同的类型。分布式条件类型是条件类型的一种特殊形式,它允许条件类型在联合类型上进行分发,以便更精确地推断和处理类型。
Conditional Types(条件类型)
条件类型基于输入的类型关系来确定最终的类型。它使用 infer
关键字来推断和定义类型。条件类型通常结合了 TypeScript 中的extends
关键字,这样就可以根据条件来确定最终的类型。
当谈到 TypeScript 中的条件类型时,让我们通过更多的例子来深入了解它们的应用和灵活性。
1. 根据输入类型选择不同的类型 条件类型基于输入的类型关系来确定最终的类型。它使用 infer 关键字来推断和定义类型。条件类型通常结合了 TypeScript 中的extends关键字,这样就可以根据条件来确定最终的类型。
示例:
typescript
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
"other";
type A = TypeName<string>; // A 的类型为 "string"
type B = TypeName<number>; // B 的类型为 "number"
type C = TypeName<boolean>; // C 的类型为 "boolean"
type D = TypeName<object>; // D 的类型为 "other"
type E = TypeName<string | number>; // E 的类型为 "string" | "number"
在这个例子中,TypeName<T>
条件类型根据传入的类型 T
来确定最终返回的类型字符串。如果 T
是 string
、number
或 boolean
类型,则返回对应的类型字符串,否则返回 "other"
。
2. 条件类型中使用 infer
关键字
infer
关键字通常与extends
结合使用,用于在条件类型内部声明一个类型变量,并从中提取或推断出一个类型。 它允许我们在泛型条件类型中推断出待推断类型的部分。
具体左右有以下两点:
- TypeScript 支持 infer 来提取类型的一部分,通过模式匹配的方式。 示例:
typescript
type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function greet(): string {
return 'Hello!';
}
type GreetReturnType = ExtractReturnType<typeof greet>;
// GreetReturnType 的类型为 string
这个例子中的 ExtractReturnType<T>
条件类型获取函数类型 T
的返回类型。它使用了 infer
关键字来推断函数的返回类型,如果 T
是一个函数类型,则返回其返回类型,否则返回 never
。
infer extends
用来做类型转换,比如 string 转 number、转 boolean 等;
具体的例子可以参考文章:juejin.cn/post/713343...
3. 条件类型配合泛型使用
示例:
typescript
type Diff<T, U> = T extends U ? never : T;
type FilterOut<T, U> = T extends any ? Diff<T, U> : never;
type Result = FilterOut<'a' | 'b' | 'c' | 'd', 'a' | 'c'>;
// Result 的类型为 "b" | "d"
在这个例子中,FilterOut<T, U>
条件类型根据传入的两个联合类型 T
和 U
,从 T
中过滤掉属于 U
类型的成员,返回剩余的类型。通过 Diff<T, U>
辅助实现了这个操作。这种方式可以在处理类型时非常有用,比如过滤掉某些特定类型。
Distributive Conditional Types(分布式条件类型)
分布式条件类型是条件类型的一种特殊形式,它在联合类型上进行推断和分发,并返回联合类型中每个成员的条件类型。
示例:
typescript
type ToArray<T> = T extends any ? T[] : never;
type StrArray = ToArray<string>; // StrArray 的类型为 string[]
type NumArray = ToArray<number>; // NumArray 的类型为 number[]
type UnionArray = ToArray<string | number>; // UnionArray 的类型为 (string | number)[]
在这个例子中,ToArray<T>
条件类型以联合类型 T
为输入,并将其分发到联合类型的每个成员上,返回一个数组类型。这种分布式行为使得条件类型在处理联合类型时更加灵活和强大。
条件类型和分布式条件类型为 TypeScript 中的类型系统增加了极大的灵活性和表达能力,允许开发者根据复杂的类型关系来定义和推断类型。
Mapped Types(映射类型)
映射类型(Mapped Types)
是 TypeScript 中一种强大的类型操作,它允许你通过已有类型来创建新类型,通常通过映射现有类型的属性、方法或者创建新的属性来实现。
常见的映射类型是利用 keyof
关键字配合索引类型来生成新的类型。一个经典的例子是 Partial<T>
类型。它接受一个类型 T
并将所有属性设置为可选的:
ini
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface User {
name: string;
age: number;
}
type PartialUser = Partial<User>;
// PartialUser 类型为 { name?: string; age?: number; }
在这个例子中,Partial<T>
使用了映射类型,通过遍历 T
类型的所有属性(由 keyof T
获取),创建了一个新类型,该类型包含了原类型 T
的所有属性,并将它们设为可选的。
除了 Partial
,还有一些其他常见的映射类型:
Readonly<T>
:将类型T
中所有属性设置为只读。Pick<T, K>
:选择类型T
中的特定属性K
。Record<K, T>
:根据键类型K
创建一个新类型,其属性为类型T
。Exclude<T, U>
和Extract<T, U>
:从类型T
中排除或提取符合类型U
的部分。
映射类型可以使类型操作更加灵活,能够根据现有类型创建出符合特定需求的新类型。这种功能特别适用于工具类型(Utility Types)的定义,使得类型系统更具表现力和可维护性。
Template Literal Types(模板文字类型)
Template Literal Types(模板文字类型)是 TypeScript 4.1 引入的一项新特性,它允许在类型系统中对字符串文本进行操作和转换。这项功能利用了模板字符串的灵活性,使得可以在类型声明中使用类似于模板字符串的语法。
在模板文字类型中,可以使用模板字符串的 ${}
语法来动态地创建字符串字面量类型。这使得类型系统更具表现力,能够进行更复杂的字符串类型操作。
举个例子,假设有一个类型 WelcomeMessage
,用于根据用户类型生成不同的欢迎消息:
scala
type User = "admin" | "user";
type WelcomeMessage<T extends User> = `Welcome, ${Capitalize<T>}!`;
type AdminWelcome = WelcomeMessage<"admin">;
// AdminWelcome 类型为 "Welcome, Admin!"
type UserWelcome = WelcomeMessage<"user">;
// UserWelcome 类型为 "Welcome, User!"
在这个例子中,WelcomeMessage
是一个模板文字类型,利用了模板字符串中的 ${}
语法。它动态地根据传入的用户类型("admin" 或 "user")生成相应的欢迎消息。这里使用了 Capitalize<T>
来确保用户名的首字母大写。
模板文字类型在类型定义中能够进行字符串的拼接、转换等操作,使得在类型层面上能够更灵活地处理和操作字符串类型。
类型推断关键字
在 TypeScript 中,有几个关键字和操作符用于类型判定。这些关键字和操作符帮助你在代码中进行类型检查、类型判断和类型转换。
- typeof
typeof
是一个类型查询操作符,用于获取变量或表达式的类型。它可以返回该值的类型字符串表示。比如typeof variable
返回变量的类型,如'number'
、'string'
、'object'
等。
ini
const numberVar = 10;
type NumberType = typeof numberVar; // NumberType 是 number 类型
- instanceof
instanceof
运算符用于检查对象是否是特定类的实例。它返回一个布尔值表示检查结果。
scala
class Animal {}
class Dog extends Animal {}
const dog = new Dog();
if (dog instanceof Dog) {
console.log('It is a dog!');
}
- in
in
关键字用于检查对象是否具有特定属性。它在条件语句中常用于判断对象是否包含某个属性。
typescript
interface Person {
name: string;
age: number;
}
const person: Person = { name: 'Alice', age: 30 };
if ('age' in person) {
console.log('Person has age property.');
}
- type guards 类型守卫是自定义的函数或条件语句,用于在代码块内缩小变量的类型范围。它们可以是
typeof
、instanceof
或者其他自定义条件的组合。
typescript
function isNumber(value: any): value is number {
return typeof value === 'number';
}
function process(value: any) {
if (isNumber(value)) {
// value 在此处被缩小为 number 类型
console.log(value.toFixed(2)); // 可以调用 number 类型的方法
} else {
console.log('Value is not a number');
}
}
- as
as
关键字用于类型断言,允许将一个变量断言为特定的类型。
ini
const someValue: any = 'hello';
const length = (someValue as string).length;
这些关键字和操作符能够在 TypeScript 中进行类型判断、类型检查和类型转换,有助于确保代码的类型安全性和正确性。
666.git 仓库迁移应该怎么操作【热度: 160】【web应用场景】【出题公司: 小米】
关键词:git 仓库迁移
如果你想迁移仓库并保留原始仓库的所有提交历史、分支和标签,你可以使用以下步骤:
方法一:使用 git clone
和 git push
- 在仓库 B 中创建新的仓库。
- 在本地克隆仓库 A:
css
git clone --mirror <仓库 A URL>
cd <仓库 A 目录>
使用 --mirror
选项克隆仓库会保留所有分支、标签和提交历史。
- 修改远程仓库地址为仓库 B:
css
git remote set-url --push origin <仓库 B URL>
- 推送到仓库 B:
css
git push --mirror
方法二:使用 git bundle
- 在仓库 A 中创建 bundle 文件:
lua
git bundle create repoA.bundle --all
- 将
repoA.bundle
文件传输到仓库 B 所在位置。 - 在仓库 B 中克隆:
bash
git clone repoA.bundle <仓库 B 目录>
这两种方法都会保留所有分支、标签和提交历史。选择哪种方法取决于你的具体需求和迁移环境。
注意:
- 使用
--mirror
或--all
选项在git clone
或git bundle
中时,会将所有的分支和标签复制到目标仓库。 - 在执行之前,请确保仓库 B 是空的或者是一个你可以覆盖的目标仓库,因为这些操作会覆盖目标仓库的内容。
- 如果仓库 A 中包含子模块,你可能需要额外处理子模块的迁移。
667.Protobuf 相关知识【热度: 216】【网络】【出题公司: 阿里巴巴】
关键词:Protobuf 基本概念
Protobuf(Protocol Buffers)
是由 Google 开发的一种轻量级、高效的数据交换格式,它被用于结构化数据的序列化、反序列化和传输。相比于 XML 和 JSON 等文本格式,Protobuf 具有更小的数据体积、更快的解析速度和更强的可扩展性。 Protobuf 的核心思想是使用协议(Protocol)来定义数据的结构和编码方式。使用 Protobuf,可以先定义数据的结构和各字段的类型、字段等信息,然后使用Protobuf提供的编译器生成对应的代码,用于序列化和反序列化数据。由于 Protobuf 是基于二进制编码的,因此可以在数据传输和存储中实现更高效的数据交换,同时也可以跨语言使用。
相比于 XML 和 JSON,Protobuf 有以下几个优势:
- 可扩展性: Protobuf 支持向已有的消息类型中添加新的字段,而不会破坏对旧数据的兼容性。这使得系统能够逐渐演进而不需要修改所有的代码。
- 高效性: 相对于一些文本格式的序列化(如XML和JSON),Protobuf 使用二进制格式,因此更为紧凑,更高效地进行数据存储和传输。
- 语言中立: Protobuf 支持多种编程语言,包括但不限于C++, Java, Python, Go等,这使得不同语言的系统能够使用相同的数据结构进行通信。
- 自动代码生成: Protobuf 通过使用 .proto 文件定义消息结构,然后利用相应语言的编译器生成与消息结构对应的代码。这简化了开发过程,减少了手动编写序列化和反序列化代码的工作。
- 支持多种数据类型: Protobuf 提供了丰富的基本数据类型,包括整数、浮点数、布尔值、字符串等,以及可以嵌套的消息类型,使得可以构建复杂的数据结构。
- 适用于网络通信: Protobuf 在网络通信领域广泛应用,特别是在 gRPC 中作为默认的消息序列化格式。
可以参考文档 :zhuanlan.zhihu.com/p/141415216
668.权限管理模型相关概念【热度: 670】【web应用场景】【出题公司: TOP100互联网】
关键词:权限管理模型
常见的权限管理模型
- DAC (Discretionary Access Control): 用户对资源有自主权,资源的所有者可以自由地授予或撤销其他用户的访问权限。
- MAC (Mandatory Access Control): 系统管理员定义了一组强制性的规则,控制用户对资源的访问。用户不能改变这些规则,这通常应用于一些需要高度安全性的环境。
- RBAC (Role-Based Access Control): 根据用户的角色分配权限,用户被分组为角色,每个角色被赋予一定的权限。这简化了权限管理,特别适用于大型组织。
- ABAC (Attribute-Based Access Control): 根据用户的属性来控制访问权限。这可以包括用户的属性、环境信息等,提供更细粒度的控制。
- PBAC (Policy-Based Access Control): 根据预定义的策略来控制访问权限。策略可以包括多个规则和条件,灵活适应各种访问控制需求。
DAC
Discretionary Access Control(DAC)自主访问控制,是一种权限管理模型,强调资源的所有者对其资源拥有自主权,可以自由决定其他用户对其资源的访问权限。每个用户被赋予特定的权限,这些权限决定了用户能够对资源执行哪些操作,如读取、写入、执行等。资源的所有者通常是创建该资源的用户,而DAC模型提供了一定的灵活性,因为资源的所有者可以根据实际需要灵活地管理对其资源的访问。在 DAC 模型中,文件系统是一个常见的应用场景,其中文件和文件夹具有所有者,所有者决定了其他用户的访问权限。这是一种相对简单而直观的权限管理方式,适用于一些相对简单的场景。
这种设计最常见的应用就是文件系统的权限设计,如微软的 NTFS。
MAC
Mandatory Access Control(MAC,强制访问控制)是一种权限管理模型,其核心特点在于由系统管理员预先定义一组强制性规则,这些规则决定了用户对资源的访问权限。在 MAC 模型中,用户无法自行修改这些规则,这种不可修改性使得 MAC 模型适用于高度安全性的环境。与 Discretionary Access Control(DAC)不同,MAC 强调系统级别上的强制性控制,而不是资源所有者的自主权。此模型通常使用标签或级别来表示用户和资源的安全属性,并支持多级别的安全控制。 MAC 在军事、政府和情报机构等对安全性要求极高的领域中得到广泛应用。在 MAC 模型下,系统管理员的定义对于用户和资源的访问权限至关重要,确保了系统的整体安全性和合规性。
这个权限最大的一个特点就是:权限标签和分级。使用标签或级别来表示用户和资源的安全级别。这些标签反映了用户和资源的安全属性,用于决定是否允许访问。
举例:MAC 模型通常在对安全性要求极高的领域中得到广泛应用,如军事、政府和情报机构。
例如:考虑一个政府机构的文件系统,其中包含了各种敏感信息。在 MAC 模型下:
- 系统管理员定义了访问控制规则,例如只有具有 "Top Secret" 标签的用户才能访问 "Top Secret" 级别的文件。
- 用户无法自行更改其安全级别或绕过系统管理员定义的规则来进行查看。
- 文件的创建者是某个用户,该用户也是一位普通用户, 但是只能查看, 不能篡改文件的访问级别和编辑级别。
RBAC
Role-Based Access Control(RBAC,基于角色的访问控制)是一种权限管理模型,其核心思想是根据用户的角色进行访问控制。在 RBAC 模型中,用户被分配到一个或多个角色,而每个角色都具有特定的权限,用户通过角色来获取相应的访问权限。 目前来说基于角色的访问控制模型是应用较广的一个,特别是 2B 方向 SAAS 领域,应用尤其常见。
其中最重要的两个关键因素就是:权限与角色关联、角色再分配给具体的用户;
ABAC
基于属性的访问控制模型(ABAC: Attribute-Based Access Control),被一些人称为是权限系统设计的未来。 不同于常见的将用户通过某种方式关联到权限的方式,ABAC 则是通过动态计算一个或一组属性是否满足某种条件来进行授权判断(可以编写简单的逻辑)。 用户、资源和环境都有各自的属性。这些属性可以包括用户的身份、角色、部门、资源的类型、敏感级别、时间等。 访问控制策略通过属性的匹配和条件评估来确定是否允许访问。例如,如果用户的角色属性是 "Manager" 且资源的敏感级别属性是 "High",则允许访问。
举例子:考虑一个企业的文档管理系统,使用 Attribute-Based Access Control (ABAC) 模型来控制对文档的访问。在这个例子中,访问控制的决策基于用户的属性、文档的属性以及其他环境因素。
-
用户属性:
- 属性 1:用户角色(Role) - 可能的值包括 "Employee"(员工)和 "Manager"(经理)。
- 属性 2:用户部门(Department) - 包括 "Sales"(销售部门)和 "Engineering"(工程部门)。
-
文档属性:
- 属性 1:文档类型(Document Type) - 包括 "Internal"(内部文档)和 "Confidential"(机密文档)。
- 属性 2:文档部门(Document Department) - 指定文档所属的部门。
-
环境属性:
- 属性 1:访问时间(Access Time) - 确定用户访问文档的时间。
-
策略定义:
- 规则 1:如果用户角色是 "Manager" 且文档类型是 "Confidential",允许访问。
- 规则 2:如果文档部门是 "Sales" 且访问时间是工作时间,允许员工访问。
-
访问请求示例:
- 用户A是 "Manager",想要访问一个 "Confidential" 类型的文档,由于规则 1 的匹配,允许访问。
- 用户B是 "Employee",想要访问一个 "Internal" 类型的文档,在工作时间内,由于规则 2 的匹配,允许访问。
在这个例子中,ABAC 模型通过匹配用户、文档和环境的属性来决定访问权限。管理员可以根据组织的需求定义和更新访问规则,以实现更精细和动态的访问控制。
这种权限设计侧重点, 在于数据属性;
PBAC
Policy-Based Access Control (PBAC) 是一种基于策略的访问控制模型,它的核心思想是通过定义和实施一组策略来管理对系统资源的访问。在 PBAC 中,访问控制是通过规则和条件的集合来决定的,这些规则描述了在特定条件下用户能够执行的操作。
跟 ABAC 是同属于一个级别的权限控制模型, 只是侧重点不同, PBAC 更加侧重于: 重定义和实施访问控制策略。这些策略是由一组规则组成,这些规则描述了在特定条件下用户能够执行的操作。
举例子:
考虑一个企业的文件管理系统,管理员使用 Policy-Based Access Control (PBAC) 来定义访问控制策略,以确保对文件的访问仅限于授权用户和特定条件下的访问。
-
用户和角色定义:
- 角色 1:Employee(普通员工)
- 角色 2:Manager(经理)
- 角色 3:Admin(管理员)
-
资源定义:
- 资源 1:Project Documents(项目文件夹)
- 资源 2:Financial Reports(财务报告文件夹)
-
策略定义:
- 策略 1:如果用户是经理,允许访问项目文件夹。
- 策略 2:如果用户是管理员,允许访问财务报告文件夹。
- 策略 3:如果访问时间在工作时间内,允许访问项目文件夹和财务报告文件夹。
- 策略 4:如果用户是普通员工,仅在工作时间内允许访问项目文件夹。
这些策略和规则的组合允许管理员定义对文件的访问控制。例如,一个经理在工作时间内可以访问项目文件夹,而管理员可以在任何时间访问财务报告文件夹。这个例子展示了 PBAC 模型如何通过灵活的策略定义,实现对资源访问的细粒度控制。管理员可以根据企业需求调整和更新这些策略,以适应不同的访问控制需求。
参考文档 : juejin.cn/post/684490...
671.grpc 和 protobuf 是什么关系?【热度: 320】【网络】【出题公司: TOP100互联网】
gRPC(gRPC Remote Procedure Call)
和 Protocol Buffers(protobuf)
有密切的关系,可以理解为它们之间是一种上下游的关系:
- Protocol Buffers(protobuf): 这是一种由 Google 设计的数据序列化格式,用于结构化数据的序列化和反序列化。protobuf 使用 .proto 文件定义消息结构,然后通过编译器生成相应语言的代码,使得开发者可以在应用中使用这些结构化的消息。
- gRPC: 这是一个由 Google 开发的基于 HTTP/2 的远程过程调用(RPC)框架。gRPC 使用 Protocol Buffers 作为默认的序列化格式,以便在客户端和服务器之间传递结构化的消息。 gRPC 通过生成的代码支持多语言,使得开发者可以轻松地定义 RPC 服务、消息和调用远程方法。
因此,关系可以总结为:
- gRPC 使用 protobuf: gRPC 首选 Protocol Buffers 作为其默认的序列化格式,这意味着 gRPC 中的消息通信使用 protobuf 格式定义,而 gRPC 编译器将根据 protobuf 文件生成相应语言的代码,包括消息结构和 RPC 服务接口。
- protobuf 不依赖于 gRPC: 尽管 protobuf 最初是为 gRPC 设计的,但它本身并不限定于 gRPC。您可以使用 protobuf 来序列化和反序列化数据,而不仅限于在 gRPC 中使用。
总的来说,gRPC 和 protobuf 是两个相关但独立的概念。gRPC 是一个使用 Protocol Buffers 的 RPC 框架,而 Protocol Buffers 是一个通用的数据序列化工具,可以在多种场景中使用。
672.JS 执行 100 万个任务, 如何保证浏览器不卡顿?【热度: 806】【web应用场景】【出题公司: TOP100互联网】
关键词:大批量执行任务不卡顿
Web Workers
要确保浏览器在执行100万个任务时不会卡顿,你可以考虑使用Web Workers来将这些任务从主线程中分离出来。Web Workers允许在后台线程中运行脚本,从而避免阻塞主线程,保持页面的响应性。
以下是一个使用Web Workers的简单示例:
ini
// 主线程代码
const worker = new Worker('worker.js'); // 创建一个新的Web Worker
worker.postMessage({ start: 0, end: 1000000 }); // 向Web Worker发送消息
worker.onmessage = function(event) {
const result = event.data;
console.log('任务完成:', result);
};
// worker.js - Web Worker代码
onmessage = function(event) {
const start = event.data.start;
const end = event.data.end;
let sum = 0;
for (let i = start; i <= end; i++) {
sum += i;
}
postMessage(sum); // 向主线程发送消息
};
在这个示例中,主线程创建了一个新的Web Worker,并向其发送了一个包含任务范围的消息。Web Worker在后台线程中执行任务,并将结果发送回主线程。
requestAnimationFrame 来实现任务分割
使用requestAnimationFrame
来实现任务分割是一种常见的方式,它可以确保任务在浏览器的每一帧之间执行,从而避免卡顿。以下是一个使用requestAnimationFrame
来分割任务的简单例子:
scss
// 假设有一个包含大量元素的数组
const bigArray = Array.from({ length: 1000000 }, (_, i) => i + 1);
// 定义一个处理函数,例如对数组中的每个元素进行平方操作
function processChunk(chunk) {
return chunk.map(num => num * num);
}
// 分割任务并使用requestAnimationFrame
const chunkSize = 1000; // 每个小块的大小
let index = 0;
function processArrayWithRAF() {
function processChunkWithRAF() {
const chunk = bigArray.slice(index, index + chunkSize); // 从大数组中取出一个小块
const result = processChunk(chunk); // 处理小块任务
console.log('处理完成:', result);
index += chunkSize;
if (index < bigArray.length) {
requestAnimationFrame(processChunkWithRAF); // 继续处理下一个小块
}
}
requestAnimationFrame(processChunkWithRAF); // 开始处理大数组
}
processArrayWithRAF();
在这个例子中,我们使用requestAnimationFrame
来循环执行处理小块任务的函数processChunkWithRAF
,从而实现对大数组的任务分割。这样可以确保任务在每一帧之间执行,避免卡顿。
针对上面的改进一下
const chunkSize = 1000; // 每个小块的大小
是不能保证不卡的, 那么久需要动态调整 chunkSize
的大小, 代码可以参考下面的示范:
ini
const $result = document.getElementById("result");
// 假设有一个包含大量元素的数组
const bigArray = Array.from({ length: 1000000 }, (_, i) => i + 1);
// 定义一个处理函数,对数组中的每个元素执行一次
function processChunk(chunk) {
return `chunk: ${chunk}`;
}
// 动态调整 chunkSize 的优化方式
let chunkSize = 1000; // 初始的 chunkSize
let index = 0;
function processArrayWithDynamicChunkSize() {
function processChunkWithRAF() {
let startTime = performance.now(); // 记录结束时间
for (let i = 0; i < chunkSize; i++) {
if (index < bigArray.length) {
const result = processChunk(bigArray[index]); // 对每个元素执行处理函数
$result.innerText = result;
index++;
}
}
let endTime = performance.now();
let timeTaken = endTime - startTime; // 计算处理时间
// 根据处理时间动态调整 chunkSize
if (timeTaken > 16) { // 如果处理时间超过一帧的时间(16毫秒),则减小 chunkSize
chunkSize = Math.floor(chunkSize * 0.9); // 减小10%
} else if (timeTaken < 16) { // 如果处理时间远小于一帧的时间(8毫秒),则增加 chunkSize
chunkSize = Math.floor(chunkSize * 1.1); // 增加10%
}
if (index < bigArray.length) {
requestAnimationFrame(processChunkWithRAF); // 继续处理下一个小块
}
}
requestAnimationFrame(processChunkWithRAF); // 开始处理大数组
}
processArrayWithDynamicChunkSize();
在这个例子中,我们动态调整chunkSize
的大小,根据处理时间来优化任务分割。根据处理时间的表现,动态调整chunkSize
的大小,以确保在处理大量任务时,浏览器能够保持流畅,避免卡顿。
参考文档: 100万个函数执行保证浏览器不卡
requestIdleCallback
window.requestIdleCallback
是一个用于在浏览器空闲时执行任务的API。它允许开发者在浏览器的主线程空闲时执行一些任务,而不会影响用户界面的流畅性和响应性。
这个 API 的基本思想是利用浏览器在空闲时的空闲时间来执行任务,这样就可以避免在用户执行交互操作时造成卡顿。requestIdleCallback
接受一个回调函数作为参数,该回调函数会在浏览器空闲时被调用。
以下是 window.requestIdleCallback
的基本用法:
javascript
window.requestIdleCallback(function(deadline) {
// 在空闲时执行的任务
// deadline 参数提供了一些信息,比如剩余的空闲时间等
});
requestIdleCallback
的回调函数接收一个 deadline
参数,它包含了一些有关当前空闲时间的信息。通过这个参数,你可以决定是否继续执行任务或者推迟到下一次空闲时段。
此外,还有一个配套的 window.cancelIdleCallback
方法,用于取消通过 requestIdleCallback
请求的回调:
javascript
const id = window.requestIdleCallback(function(deadline) {
// 在空闲时执行的任务
});
// 取消回调
window.cancelIdleCallback(id);
需要注意的是,requestIdleCallback
并不是所有浏览器都支持的标准,因此在使用时要注意检查浏览器的兼容性。在一些现代浏览器中,这个 API 已经得到了广泛的支持,但在某些老旧的浏览器中可能并不可用。
673.http 中 CSP 是什么【热度: 323】【网络】【出题公司: 阿里巴巴】
关键词:http CSP
在 HTTP 协议中,CSP 指的是 "Content Security Policy"(内容安全策略)。CSP 是一种用于增强网站安全性的安全策略机制,通过指定浏览器只能加载指定来源的资源,以减少恶意攻击的风险。
CSP 的主要目标是防止和减缓特定类型的攻击,例如跨站脚本攻击 (XSS) 和数据注入攻击。通过配置 CSP,网站管理员可以告诉浏览器哪些资源是被信任的,从而减少恶意代码的执行。
CSP 的一些常见配置项包括:
- default-src: 指定默认情况下可以从哪些来源加载资源。
- script-src: 指定允许加载脚本的来源。
- style-src: 指定允许加载样式表的来源。
- img-src: 指定允许加载图片的来源。
- font-src: 指定允许加载字体的来源。
- connect-src: 指定允许进行网络请求的来源(例如 Ajax 请求)。
- frame-src: 指定允许加载框架的来源。
- media-src: 指定允许加载媒体资源的来源。
等等。
以下是一个简单的 CSP 示例:
css
Content-Security-Policy: default-src 'self'; script-src 'self' example.com; img-src 'self' data:;
上述 CSP 规则的含义是:
default-src 'self'
: 允许从同一站点加载默认来源的资源。script-src 'self' example.com
: 允许从同一站点和 example.com 加载脚本。img-src 'self' data:
: 允许从同一站点和 data: 协议加载图片。
CSP 可以通过 HTTP 头部来设置,也可以通过 <meta>
标签嵌入在 HTML 页面中。使用 CSP 可以帮助网站减少受到恶意攻击的风险,提高网站的安全性。
如何通过 meta 标签设置 CSP
通过 <meta>
标签设置 Content Security Policy (CSP) 的方式如下:
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="directives">
<title>Your Page Title</title>
</head>
<body>
<!-- Your content goes here -->
</body>
</html>
在上面的代码中,<meta>
标签的 http-equiv
属性被设置为 "Content-Security-Policy",而 content
属性中则包含了 CSP 指令(directives)。你需要将 "directives" 替换为你实际想要设置的 CSP 规则。
以下是一个具体的例子:
css
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' example.com; img-src 'self' data:;">
在这个例子中,CSP 规则指定了默认来源是同一站点,允许加载同一站点和 example.com 的脚本,允许加载同一站点和 data: 协议的图片。
注意:通过 <meta>
标签设置的 CSP 规则只对当前页面生效,而通过 HTTP 头部设置的 CSP 规则对整个站点生效。因此,如果你希望 CSP 规则对整个站点生效,最好在服务器端通过 HTTP 头部设置 CSP。
674.http 中 HSTS 是什么【热度: 374】【网络】【出题公司: 阿里巴巴】
关键词:http HSTS
HTTP Strict-Transport-Security
(HSTS)是一种安全策略,它通过 HTTP 头部告诉浏览器只能通过安全的 HTTPS 连接访问网站,从而增加网站的安全性。HSTS 有助于防止恶意攻击者通过中间人攻击(如SSL剥离攻击)窃取敏感信息。
HSTS 的主要作用包括:
- 强制使用 HTTPS: 通过 HSTS,网站可以强制浏览器在一定时间内只能通过 HTTPS 访问,提高数据的安全性。
- 防止 SSL 剥离攻击: HSTS 通过告知浏览器只能通过安全的连接访问网站,有效地防止了一些中间人攻击,例如 SSL 剥离攻击,其中攻击者试图将 HTTPS 连接降级为不安全的 HTTP 连接。
- 增加网站的安全性: HSTS 是一种增加网站安全性的简单而有效的手段,尤其是对于那些强调隐私和数据保护的网站。
HSTS 的工作原理如下:
- 首次访问: 当用户首次通过 HTTPS 访问网站时,服务器可以在响应头中包含 HSTS 头部,指定网站的 HSTS 策略。例如:
ini
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age=31536000
: 告诉浏览器在接下来的 1 年内,只能通过 HTTPS 访问该网站。includeSubDomains
: 表示该策略也适用于所有子域名。preload
: 表示网站希望被添加到浏览器的 HSTS 预加载列表中。
- 以后的访问: 一旦浏览器接收到包含 HSTS 头部的响应后,它会记住这个信息。在接下来的一年内,浏览器将强制使用 HTTPS 访问该网站,即使用户尝试通过 HTTP 访问。
一旦网站启用了 HSTS
,并且用户第一次通过 HTTPS 访问该网站,浏览器将在 HSTS
头部指定的时间内(max-age
参数指定的时间)记住这个策略,即使用户以后尝试通过 HTTP 访问,浏览器仍然会强制使用 HTTPS。因此,使用 HSTS 需要谨慎,确保网站支持 HTTPS 并且配置正确。
如何取消 HSTS
如果之前的请求设置了 HTTP Strict-Transport-Security 这个 header 了, 时间是 max-age=31536000; 之后因为一些原因, 取消了 Strict-Transport-Security 这个 header, 那么还是不能使用 http 吗?
一旦浏览器接收到包含 HTTP Strict-Transport-Security
(HSTS)头部的响应,并且在响应中设置了 max-age
参数,浏览器会在指定的时间内坚持使用 HTTPS 连接,即使后续的请求中不再包含 HSTS 头部。
如果之前的请求设置了 max-age=31536000
,那么浏览器将在接下来的一年内坚持使用 HTTPS 连接,即使后续的请求中不再包含 HSTS 头部。 即使之后取消了 HSTS 头部,浏览器仍然会在 max-age
规定的时间内执行强制使用 HTTPS 的策略。
如果由于一些原因需要取消 HSTS,可以采取以下步骤之一:
- 在 HTTP 响应中不再包含 HSTS 头部: 在服务器的 HTTPS 响应中,不再包含
Strict-Transport-Security
头部,或者将max-age
设置为较短的时间,以便更快地使浏览器放弃 HSTS 策略。 - 使用
includeSubDomains
指令进行逐步取消: 如果之前设置了includeSubDomains
,并且想逐步取消 HSTS,可以在不同的子域名上逐步取消。例如,可以在某个子域名上不再包含 HSTS 头部,而其他子域名仍然保持 HSTS。
请注意,取消 HSTS 头部可能导致用户在一定时间内无法通过 HTTPS 访问网站,因为浏览器会在 max-age
规定的时间内继续强制使用 HTTPS。 确保在取消 HSTS 头部之前,确保网站的 HTTPS 配置是正确的,以避免访问问题。
675.CORS 请求中,是如何触发预检请求【热度: 229】【网络】【出题公司: 阿里巴巴】
关键词:CORS 预检请求条件
其动机是,HTML 4.0
中的 <form>
元素(早于跨站 XMLHttpRequest 和 fetch
)可以向任何来源提交简单请求,所以任何编写服务器的人一定已经在保护跨站请求伪造攻击(CSRF)
。 在这个假设下,服务器不必选择加入(通过响应预检请求)来接收任何看起来像表单提交的请求,因为 CSRF 的威胁并不比表单提交的威胁差。然而,服务器仍然必须提供 Access-Control-Allow-Origin
的选择,以便与脚本共享响应。
若请求满足所有下述条件,则该请求可视为简单请求:
-
使用下列方法之一:
GET
HEAD
POST
-
除了被用户代理自动设置的标头字段(例如 Connection、User-Agent 或其他在 Fetch 规范中定义为禁用标头名称的标头),允许人为设置的字段为 Fetch 规范定义的对 CORS 安全的标头字段集合。该集合为:
Accept
Accept-Language
Content-Language
Content-Type(需要注意额外的限制)
Range(只允许简单的范围标头值 如 bytes=256- 或 bytes=127-255)
-
Content-Type 标头所指定的媒体类型的值仅限于下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
-
如果请求是使用
XMLHttpRequest
对象发出的,在返回的XMLHttpRequest.upload
对象属性上没有注册任何事件监听器;也就是说,给定一个XMLHttpRequest
实例xhr
,没有调用xhr.upload.addEventListener()
,以监听该上传请求。 -
请求中没有使用
ReadableStream
对象。
比如说,假如站点 foo.example 的网页应用想要访问 bar.other 的资源。foo.example 的网页中可能包含类似于下面的 JavaScript 代码:
678.为什么 Vite 速度比 Webpack 快?【热度: 382】【工程化】【出题公司: 腾讯】
关键词:vite 编译速度、vite 速度 与 webpack 速度
1、开发模式的差异
在开发环境中,Webpack
是先打包再启动开发服务器,而 Vite
则是直接启动,然后再按需编译依赖文件。(大家可以启动项目后检查源码 Sources
那里看到)
这意味着,当使用 Webpack
时,所有的模块都需要在开发前进行打包,这会增加启动时间和构建时间。
而 Vite
则采用了不同的策略,它会在请求模块时再进行实时编译,这种按需动态编译的模式极大地缩短了编译时间,特别是在大型项目中,文件数量众多,Vite
的优势更为明显。
2、对ES Modules的支持
现代浏览器本身就支持 ES Modules
,会主动发起
请求去获取所需文件。Vite充分利用了这一点,将开发环境下的模块文件直接作为浏览器要执行的文件,而不是像 Webpack 那样先打包
,再交给浏览器执行。这种方式减少了中间环节,提高了效率。
什么是ES Modules?
通过使用 export
和 import
语句,ES Modules 允许在浏览器端导入和导出模块。
当使用 ES Modules 进行开发时,开发者实际上是在构建一个依赖关系图
,不同依赖项之间通过导入语句进行关联。
主流浏览器(除IE外)均支持ES Modules,并且可以通过在 script 标签中设置 type="module"
来加载模块。默认情况下,模块会延迟加载,执行时机在文档解析之后,触发DOMContentLoaded事件前。
3、底层语言的差异
Webpack 是基于 Node.js
构建的,而 Vite 则是基于 esbuild
进行预构建依赖。esbuild 是采用 Go
语言编写的,Go 语言是纳秒
级别的,而 Node.js 是毫秒
级别的。因此,Vite 在打包速度上相比Webpack 有 10-100
倍的提升。
什么是预构建依赖?
预构建依赖通常指的是在项目启动或构建
之前,对项目中所需的依赖项进行预先的处理或构建
。这样做的好处在于,当项目实际运行时,可以直接使用
这些已经预构建好的依赖,而无需再进行实时的编译或构建,从而提高了应用程序的运行速度和效率。
4、热更新的处理
在 Webpack 中,当一个模块或其依赖的模块内容改变时,需要重新编译
这些模块。
而在 Vite 中,当某个模块内容改变时,只需要让浏览器重新请求
该模块即可,这大大减少了热更新的时间。
总结
总的来说,Vite 之所以比 Webpack 快,主要是因为它采用了不同的开发模式
、充分利用了现代浏览器的 ES Modules 支持
、使用了更高效的底层语言
,并优化了热更新的处理
。这些特点使得 Vite在大型项目中具有显著的优势,能够快速启动和构建,提高开发效率。
参考文档:
679.如何检测网页空闲状态(一定时间内无操作)【热度: 329】【web应用场景】【出题公司: 百度】
关键词:检测网页空闲状态
关键词:vite 编译速度、vite 速度 与 webpack 速度
1、开发模式的差异
在开发环境中,Webpack
是先打包再启动开发服务器,而 Vite
则是直接启动,然后再按需编译依赖文件。(大家可以启动项目后检查源码 Sources
那里看到)
这意味着,当使用 Webpack
时,所有的模块都需要在开发前进行打包,这会增加启动时间和构建时间。
而 Vite
则采用了不同的策略,它会在请求模块时再进行实时编译,这种按需动态编译的模式极大地缩短了编译时间,特别是在大型项目中,文件数量众多,Vite
的优势更为明显。
2、对ES Modules的支持
现代浏览器本身就支持 ES Modules
,会主动发起
请求去获取所需文件。Vite充分利用了这一点,将开发环境下的模块文件直接作为浏览器要执行的文件,而不是像 Webpack 那样先打包
,再交给浏览器执行。这种方式减少了中间环节,提高了效率。
什么是ES Modules?
通过使用 export
和 import
语句,ES Modules 允许在浏览器端导入和导出模块。
当使用 ES Modules 进行开发时,开发者实际上是在构建一个依赖关系图
,不同依赖项之间通过导入语句进行关联。
主流浏览器(除IE外)均支持ES Modules,并且可以通过在 script 标签中设置 type="module"
来加载模块。默认情况下,模块会延迟加载,执行时机在文档解析之后,触发DOMContentLoaded事件前。
3、底层语言的差异
Webpack 是基于 Node.js
构建的,而 Vite 则是基于 esbuild
进行预构建依赖。esbuild 是采用 Go
语言编写的,Go 语言是纳秒
级别的,而 Node.js 是毫秒
级别的。因此,Vite 在打包速度上相比Webpack 有 10-100
倍的提升。
什么是预构建依赖?
预构建依赖通常指的是在项目启动或构建
之前,对项目中所需的依赖项进行预先的处理或构建
。这样做的好处在于,当项目实际运行时,可以直接使用
这些已经预构建好的依赖,而无需再进行实时的编译或构建,从而提高了应用程序的运行速度和效率。
4、热更新的处理
在 Webpack 中,当一个模块或其依赖的模块内容改变时,需要重新编译
这些模块。
而在 Vite 中,当某个模块内容改变时,只需要让浏览器重新请求
该模块即可,这大大减少了热更新的时间。
总结
总的来说,Vite 之所以比 Webpack 快,主要是因为它采用了不同的开发模式
、充分利用了现代浏览器的 ES Modules 支持
、使用了更高效的底层语言
,并优化了热更新的处理
。这些特点使得 Vite在大型项目中具有显著的优势,能够快速启动和构建,提高开发效率。