这几家是4月底面的,一直存在草稿箱里不发出来觉得难受,于是呢,整理整理就发出来了
metaApp
1. 适配手机端和 PC 端,说一下你的一个适配逻辑。
先根据当前浏览器中的 navigator.userAgent
正则匹配到是否为移动端设备,通过在 App.vue 中给 body
设置属性 screen-mode
,如果是移动端,则给 screen-mode
值为 mobie
,如果不是,则为 pc
,通过属性选择器分别给双端设置不同的样式,完成双端适配。
js
// env.ts
export function isIos(ipadIsMobdile = true) {
if (ipadIsMobdile) {
return (
navigator.userAgent.match(/iPhone|iPad|iPod|Mac OS/i) &&
"ontouchend" in document
);
} else {
return navigator.userAgent.match(/iPhone|iPod/i);
}
}
export function isAndriod() {
return navigator.userAgent.match(/Android/i);
}
html
// App.vue
export default {
name: "App",
mounted() {
document.body.setAttribute("screen-mode", settingService.mode);
},
}
<style scoped lang="less">
[screen-mode="pc"] {
.screen-mode-test {
background-color: aqua;
}
}
[screen-mode="mobile"] {
.screen-mode-test {
background-color: yellow;
}
}
</style>
2. 适配不同手机尺寸的方案
-
方案一:百分比设置(不推荐)
- 因为不同属性的百分比值,相对的可能是不同参照物,所以百分比往往很难统一
- 所以百分比在移动端适配中使用是非常少的
-
方案二:rem单位+动态html的font-size
- 媒体查询
- 动态改写font-size,编写js代码
-
方案三:vw单位(推荐)
-
方案四:flex的弹性布局
3. rem 的使用原理
rem
是根据 DOM 文档根节点 HTML
的 CSS 样式 font-size
来进行计算的,浏览器默认 html 中的 font-size
为 16px
,所以 1rem = 16px
这样来设计的。因此我们只需要更改 html 的 font-size 属性就可以轻松的控制 rem 的大小了。
改变rem完成适配
媒体查询 + rem
css
// 320px:这是一些比较小型的移动设备,比如小尺寸的手机或者老款手机的屏幕宽度。
@media screen and (min-width: 320px) {
html {
font-size: 20px;
}
}
// 375px:这是一些中等尺寸的手机,比如iPhone 6、7、8的屏幕宽度。
@media screen and (min-width: 375px) {
html {
font-size: 24px;
}
}
// 414px:这是一些较大型的手机,比如iPhone 6 Plus、7 Plus、8 Plus的屏幕宽度,
// 以及一些Android手机的屏幕宽度。
@media screen and (min-width: 414px) {
html {
font-size: 28px;
}
}
// 480px:这个宽度可以涵盖一些平板电脑的屏幕宽度,
// 同时也是一些中等尺寸的笔记本电脑或者桌面电脑的最小宽度。
@media screen and (min-width: 480px) {
html {
font-size: 32px;
}
}
// 注意font-size可能会引起继承问题,改为自己想要的值即可
body {
font-size: xxpx;
}
.box {
width: 5rem;
height: 5rem;
background-color: blue;
}
编写js代码
思路:通过监听屏幕尺寸的变化来动态修改 html 元素的 font-size 大小
方法:
- 根据 html 的宽度计算出 font-size 的大小,并设置到 html 上
- 监听页面尺寸的变化,实时修改 font-size 大小
js
function setRemUnit() {
// 获取所有的 html 元素
const htmlEl = document.documentElement
// 我们需要动态更改字体大小,因此需要获取网页的宽度
// 拿到客户端宽度
const htmlWidth = htmlEl.clientWidth
// 将宽度分成10份,每一份代表 1rem ,即视口宽度的十分之一,更加灵活,其实类似vw。
const htmlFontSize = htmlWidth / 10
console.log('htmlFontSize', htmlFontSize);
// 将值给到html的font-size
htmlEl.style.fontSize = htmlFontSize + 'px'
}
setRemUnit()
// 给 window 添加监听事件
window.addEventListener('resize', setRemUnit)
4. 关键字 new 操作符帮我们去处理了哪些事情呢?
是真喜欢问 new 啊,之前牛客上看他们公司之前的面试题,基本都有 new
构造函数
js
// 定义一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
// ruturn
}
// 通过构造函数创建一个对象
const person1 = new Person('Alice', 30);
// 输出对象的属性
console.log(person1.name); // 输出:Alice
console.log(person1.age); // 输出:30
执行流程
- 创建对象:
new
首先创建了一个新的空对象
js
const obj = {};
- 设置原型: 将空对象的原型(proto )设置为构造函数的
prototype
对象。
js
obj.__proto__ = Person.prototype;
- 控制指向: 让构造函数的
this
指向这个空对象,执行构造函数的代码(为这个新对象添加属性)
js
const result = Person.apply(obj, args)
- 判断返回:
- 如果构造函数返回的是一个对象,则返回该对象。
- 如果构造函数没有显式返回一个对象,则返回一开始创建新的空对象。
js
return result instanceof Object ? result : obj;
完整代码
js
function myNew(constructor, ...args) {
// 1. 创建一个新的空对象
const obj = {};
// 2. 将新对象的原型设置为构造函数的 prototype 对象
Object.setPrototypeOf(obj, constructor.prototype);
// obj.__proto__ = constructor.prototype;
// 3. 让构造函数的 this 指向新对象,并执行构造函数的代码
const result = constructor.apply(obj, args);
// 4. 判断构造函数的返回值类型,如果是值类型则返回新对象,
// 如果是引用类型则返回构造函数的返回值
return result instanceof Object ? result : obj;
}
// 示例构造函数
function Person(name) {
this.name = name;
}
// 使用 myNew 创建对象
const person = myNew(Person, 'Alice');
console.log(person); // 输出:Person { name: 'Alice' }
console.log(person.name); // 输出:'Alice'
5. vue 中写一个函数让定时器自动的销毁而不是主动的在生命周期函数中销毁
使用组合式函数(广义上大致 组合式函数 ≈ hooks函数 ≈ Composition API,我开发中后两种称呼使用的比较多),面试官想考察的是这一点。当时面试官问法好奇怪啊(先提我简历中项目使用了定时器,然后又说主动销毁嫌麻烦,想要自动销毁),脑子没反应过来,一愣一愣地,一直在思考vue本身有什么函数能自动销毁。现在回想起来此时的我正"满地爬滚ing",太菜了啊,居然陷入了思维误区。
js
// 定义一个组合式函数,用于创建定时器并自动销毁
export const useAutoDestroyTimer = (callback, delay) => {
const timer = ref(null);
onMounted(() => {
timer.value = setInterval(callback, delay);
});
onBeforeUnmount(() => {
clearInterval(timer.value);
});
};
6. 组合式函数使用,如何手写
Vue 中常见的组合式 API
setup、ref、reactive、computed、watch、watchEffect、toRef、toRefs、toRaw、生命周期钩子、provide、inject
个人语义化总结
接收外部传的参数,然后多个函数集中处理这些参数,最后暴露出让他人使用。
7. v-if 和 v-show 的区别(难蚌,说反了)
v-if 在编译过程中会被转化成三元表达式,条件不满足时不渲染此节点。(display:none)
v-show 会被编译成指令,条件不满足时控制样式将对应节点隐藏 (opacity: 0)
使用场景
v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景
v-show 适用于需要非常频繁切换条件的场景
8. v-if 和 v-show 重绘重排的性能影响
v-if 优先级高于 v-show
如果同时在同一个元素上使用了 v-if
和 v-show
,那么 v-if
应该具有更高的优先级,即在 v-if
条件为真时,元素会被渲染,不管 v-show
的值是什么。
这样设计的原因是 v-if
会在 DOM 结构中添加或移除元素,而 v-show
只是通过 CSS 控制元素的显示与隐藏。因此,如果 v-if
的条件为假,元素不会被渲染到 DOM 中,此时 v-show
的值也不会起作用。
这种设计可以保持对模板指令的直觉理解,即 v-if
控制元素的存在与否,而 v-show
控制元素的显示与隐藏。
9.(算法1)题目描述:给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足: 左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:输入: "()"
输出: true
示例 2:输入: "()[]{}"
输出: true
示例 3:输入: "(]"
输出: false
示例 4:输入: "([)]"
输出: false
示例 5:
输入: "{[]}"
输出: true
js
// 用一个 map 来维护左括号和右括号的对应关系
const leftToRight = {
"(": ")",
"[": "]",
"{": "}"
};
/**
* @param {string} s
* @return {boolean}
*/
const isValid = function(s) {
// 结合题意,空字符串无条件判断为 true
if (!s) {
return true;
}
// 初始化 stack 数组
const stack = [];
// 缓存字符串长度
const len = s.length;
// 遍历字符串
for (let i = 0; i < len; i++) {
// 缓存单个字符
const ch = s[i];
// 判断是否是左括号,这里我为了实现加速,没有用数组的 includes 方法,直接手写判断逻辑
if (ch === "(" || ch === "{" || ch === "[") stack.push(leftToRight[ch]);
// 若不是左括号,则必须是和栈顶的左括号相配对的右括号
else {
// 若栈为空,或栈顶的左括号没有和当前字符匹配上,那么判为无效
if (!stack.length || stack.pop() !== ch) {
return false;
}
}
}
// 若所有的括号都能配对成功,那么最后栈应该是空的
return !stack.length;
};
10. (算法2)手写一个节流函数
我写的一版,由于之前没被问过这个问题,所以习惯性的写了不知道哪里得知的复杂版本,被面试官吐槽了。。。没这么复杂
js
const throttle = (func, delay)=> {
let timer, last;
return function(...args) {
const that = this;
if (last && last > now - delay) {
clearTimeout(timer);
timer = setTimeout(() => {
last = now;
func.apply(that, args;
}, delay)
} else {
last = now;
func.apply(that, args);
}
}
}
简洁版
js
const throttle = (func, delay)=> {
let timer;
return function(...args) {
const that = this;
// 判断当前是否已经存在定时器,如果存在则直接返回,不继续执行后续的代码
if (timer) return;
timer = setTimeout(() => {
func.apply(that, args);
timer = null;
}, delay)
}
}
11. 职业的规划,想写的项目,想学的技术?
最后面试官潇洒地对着镜头挥手再见,看到连常规反问环节都没有就知道寄了。
源头科技
1. 从输入URL到页面加载的全过程
2. 浏览器什么情况下会缓存文件?被缓存的文件,需不需要再向服务端发起请求?
主要是看 HTTP 响应体中有无缓存标识,即强缓存和弱缓存(或者强制缓存和协商缓存)
被缓存的文件是否需要再次向服务器发起请求,取决于缓存策略:
-
强缓存 (Strong Cache) :
- 如果文件在缓存有效期内,浏览器直接从缓存中读取文件,不会向服务器发起请求。
Cache-Control: max-age
和Expires
头部可以实现这种行为。
- 如果文件在缓存有效期内,浏览器直接从缓存中读取文件,不会向服务器发起请求。
-
协商缓存 (Conditional Cache) :
-
即使文件在缓存中,浏览器也会向服务器发送一个条件请求(如
If-Modified-Since
或If-None-Match
)。 -
服务器会根据文件是否改变返回不同的状态码:
- 304 Not Modified:文件未改变,浏览器使用缓存文件。
- 200 OK:文件已更新,服务器发送新文件,浏览器更新缓存。
-
3. 已经缓存的文件,那你怎么知道服务端对文件进行了修改?
主要有弱缓存中的两组控制字段 Last-Modified / If-Modified-Since
和 Etag / If-None-Match
4. 什么叫做强缓存呢?
5. 304 它是哪个字段决定的?字段携带的是什么?
304 Not Modified
状态码的返回是由 If-Modified-Since
和 If-None-Match
这两个请求头决定的,分别携带的是对应于资源的最后修改时间 (Last-Modified
) 和资源的版本标识 (ETag
)
-
If-Modified-Since:
- 携带内容 :这个字段携带的是浏览器上次从服务器获取资源时的
Last-Modified
时间。例如:If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
。 - 服务器处理 :服务器接收到请求后,会将资源的当前修改时间与
If-Modified-Since
时间进行比较。如果资源在此时间之后没有修改过,服务器返回304 Not Modified
,否则返回200 OK
和新的资源内容。
- 携带内容 :这个字段携带的是浏览器上次从服务器获取资源时的
-
If-None-Match:
- 携带内容 :这个字段携带的是浏览器上次从服务器获取资源时的
ETag
值。例如:If-None-Match: "686897696a7c876b7e"
. - 服务器处理 :服务器接收到请求后,会将资源的当前
ETag
与If-None-Match
值进行比较。如果ETag
没有改变,服务器返回304 Not Modified
,否则返回200 OK
和新的资源内容。
- 携带内容 :这个字段携带的是浏览器上次从服务器获取资源时的
6. 同步、异步、阻塞和非阻塞的区别?
-
同步:任务按顺序执行,当前任务完成后才能执行下一个任务,容易出现阻塞。
-
异步:异步是指一个操作在开始执行之后,不需要等待它完成(即返回结果)就可以继续执行其他操作的一种执行模式。换句话说,异步操作允许程序在等待某个操作完成的同时,继续执行其他任务,而不必阻塞整个程序的执行流程。
-
阻塞:在阻塞操作中,任务会阻止后续代码的执行,直到任务完成。会导致程序"卡住",无法进行其他操作。
-
非阻塞:在非阻塞操作中,任务会立即返回,允许后续代码继续执行。
7. 什么情况要用到异步?异步有什么好处?
需要花费一定的时间完成某个函数或者任务的时候需要用到异步,常用的场景比如网络请求、定时器等等。
网络请求:开始执行网络请求,但网络请求需要花费时间,结果不能立即返回,这时就需要使用对网络请求使用异步,以防该网络请求阻塞了后面的任务,这期间会执行其他的任务,而当该网络请求完成之后,就可以直接得到返回的结果了。
定时器:设置好了需要定时的时间,开启定时器,定时器的回调函数已经开始执行,由于定时器本身是异步任务,不需要额外的操作,当等待了设置好的定时时间之后,会立即返回回调函数中的结果。
难怪在event Loop中先是执行第一个异步任务,再执行完所有的同步任务,而不是先执行同步任务。
8. 异步的好处
- 非阻塞 I/O:在发起网络请求或其他 I/O 操作时,不会阻塞主线程,使得 UI 能保持响应。
- 提高性能:通过并行处理多个任务,可以更高效地利用资源,缩短整体执行时间。
9. 浏览器怎么把一张图展示到屏幕上面?或者说为什么能把图片展示到屏幕上?
浏览器内置了各种图像解码器,用于将压缩格式的图像数据(如 JPEG、PNG)解码成可以直接在屏幕上显示的像素数据。 下面是整个图片展示的过程:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Display Example</title>
</head>
<body>
<img src="https://example.com/image.jpg" alt="Example Image">
</body>
</html>
-
HTML 解析:
- 浏览器解析 HTML,遇到
<img>
标签。 - 读取
src
属性,获取图像 URL。
- 浏览器解析 HTML,遇到
-
网络请求:
- 浏览器发送 HTTP 请求,获取
https://example.com/image.jpg
图像数据。
- 浏览器发送 HTTP 请求,获取
-
图像解码:
- 接收到图像数据后,浏览器使用 JPEG 解码器解码数据,将其转换为像素数据。
-
布局计算:
- 计算图像在页面中的位置和尺寸。
-
绘制和显示:
- 将像素数据绘制到屏幕上,显示图像。
10. nodejs要怎么连接数据库?访问数据库一般要处理什么问题呢?连接失败要怎么办呢?
先引入"mysql"数据库驱动,然后再进行有关数据库的信息配置
js
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'localhost',
port: "3306",
user: 'root',
password: 'password',
database: 'test'
});
connection.connect(error => {
if (error) {
console.log('👻 连接数据服务失败:', error.message);
return;
}
console.log('🚛 成功连接数据服务 ~~');
});
需要处理什么问题?
- 需要处理有关连接失败的问题
连接失败要怎么办?
-
重试连接:尝试多次连接数据库。
-
日志记录:记录错误日志,便于排查问题。
-
回退机制:在无法连接数据库时,启用备用机制(如读取缓存)。
11. 连接数据库什么叫长链接,什么叫短连接?长连接和短连接分别有什么问题?
长连接:"长"的字面意思是维持时间长,「连接长期存在,也就是说程序可以复用这条连接,不用每次发起请求都重新建立连接」。
短连接:"短"的字面意思是维持时间短,「连接在请求结束之后就被释放了,也就是说程序无法复用这条连接,每次发起请求都要建立新的连接」
长短连接的优缺点如下表所示。
优点 | 缺点 | |
---|---|---|
长连接 | 1. 减少连接开销 :建立和断开连接的过程会消耗资源,长连接通过复用连接减少了这种开销。2. 提高性能 :减少了频繁连接和断开带来的延迟,提高了应用的性能。3. 适合高频操作:对于需要频繁访问数据库的应用,长连接能显著提高效率。 | 1. 资源占用 :长时间占用数据库连接资源,如果连接数过多,可能导致数据库连接池资源耗尽。2. 连接泄漏 :如果代码中有连接未正常关闭的情况,会导致连接泄漏,长期占用资源。3. 负载均衡问题:在负载均衡的环境中,长连接可能导致负载不均衡。 |
短连接 | 1. 资源释放及时 :每次操作完毕后立即释放连接资源,不会长期占用数据库连接。 2. 避免连接泄漏 :由于每次操作后都关闭连接,可以避免连接泄漏问题。3. 负载均衡友好:更适合负载均衡,因为连接是短暂的,不会导致长时间的连接占用。 | 1. 连接开销大 :每次操作都需要重新建立和关闭连接,会增加连接的开销。 2. 性能较低:频繁的连接建立和关闭会导致性能下降,尤其是在高频操作时。 |
爱用科技
1. 单点登录具体是怎样一个流程呢?
点击登录,先是会弹出一个登录的iframe页面,关于iframe页面的交互由于篇幅问题这里不再多阐述。
选择其它登入方式后,就开始了SSO的流程
先初始化第三方登录的 script ,即引入第三方的 sdk ,方便后续调用相关接口,而且需要传入相关的客户端 id 来标明(客户端id需要在相应开发平台获取)。
js
const init = () => {
thirdPartyService.init();
};
init();
//thirdPartyService.init();
init() {
facebookAuthService.init(env.thirdParty.facebook);
}
//facebookAuthService.init
init({
appId,
autoLogAppEvents = true,
xfbml = true,
version = "v16.0",
}: FacebookInitOption) {
// loadScript得自己写,反正就是script赋值
loadScript(FACEBOOK_SCRIPT_OPTION); // 常数在最下面
(window as any).fbAsyncInit = () => {
// @ts-ignore
FB.init({
appId,
autoLogAppEvents,
xfbml,
version,
});
};
}
const FACEBOOK_SCRIPT_OPTION = {
src: "https://connect.facebook.net/en_US/sdk.js",
async: true,
defer: true,
crossOrigin: "anonymous",
};
初始化完之后开始授权了即SSO正式开始
在auth中调用FB.login(以facebook为例)就会弹出登录弹窗,
js
const loginByChannel = (
type: "apple" | "facebook" | "twitter" | "google"
) => {
thirdPartyService
.auth(type, () => {})
// thirdPartyService.auth
auth(
type: "apple" | "facebook" | "twitter" | "google",
preServerCallback?: () => void
): Promise<{ user: User }> {
return this.channelLoginApis[type](preServerCallback).then((user: User) => {
return { user };
});
}
private channelLoginApis = {
facebook: (preServerCallback?: () => void) => {
return facebookAuthService.startAuth(preServerCallback);
}
};
// facebookAuthService.startAuth
startAuth(preServerCallback?: () => void): Promise<User> {
return new Promise((resolve, reject) => {
const doLogin = () => {
// @ts-ignore
FB.login((response: any) => {
// FB授权成功即登录成功
if (response?.authResponse) {
preServerCallback?.();
postFacebookLogin({
idToken: response.authResponse.accessToken,
})
.then(resolve)
.catch(reject);
return;
}
});
};
}
然后登录成功后,拿到token凭着传给后端并返回用户信息。
授权成功后把后端返回的用户信息存储到变量中,然后再执行回调方法,把信息返回给主页面,主页面拿到session就可以正常访问了。
js
.then(
({
user
}: {
user: User;
}) => {
settingService.setUser(user);
Message.success(i18n.tc(Translate.LOGIN_SUCCESS_TIP));
if (settingService.user.value) {
// iframe 弹窗的模式,这里其实按业务需求可能也会有第三方页的模式
window.parent.postMessage({
type: "login_sso",
channel: type,
isLogin: true,
session: user?.session || null,
}, "*");
}
}
)
2. 对于 SSO 这种模式而言,它是怎么去做这件事情呢?或者什么叫单点登录呢?
单点登录
单点登录有个简称是sso,它是一个功能可以控制多个有联系的系统操作,简单地理解为通过单点登录可以让用户只需要登录一次软件或者系统,那么同系统下的平台都可以免去再次注册、验证、访问权限的麻烦程序,通俗易懂的理解为一次性登录也可以一次性下线。
前端需要知道的单点登录概述:
1、一个系统登录流程:用户进入系统------未登录------跳转登录界面------用户名和密码发送------服务器端验证后,设置一个cookie发送到浏览器,设置一个session存放在服务器------用户再次请求(带上cookie)------服务器验证cookie和session匹配后,就可以进行业务了。
2、多个系统登录:如果一个大公司有很多系统,a.seafile.com, b.seafile.com,c.seafile.com。这些系统都需要登录,如果用户在不同系统间登录需要多次输入密码,用户体验很不好。所以使用 SSO (single sign on) 单点登录实现。
3、相同域名,不同子域名下的单点登录:在浏览器端,根据同源策略,不同子域名的cookie不能共享。所以设置SSO的域名为根域名。SSO登录验证后,子域名可以访问根域名的 cookie,即可完成校验。在服务器端,可以设置多个子域名session共享(Spring-session)
4、不同域名下的单点登录:CAS流程:用户登录子系统时未登录,跳转到 SSO 登录界面,成功登录后,SSO 生成一个 ST (service ticket )。用户登录不同的域名时,都会跳转到 SSO,然后 SSO 带着 ST 返回到不同的子域名,子域名中发出请求验证 ST 的正确性(防止篡改请求)。验证通过后即可完成不同的业务。
总之就是单点登录可以有子域名的单点登录和不同域名的单点登录
3. 在登录系统里面就是我们前后端的鉴权方式会分为哪几种?
cookie、session、token、单点登录
4. cookie 和 token 的区别是什么?
Cookie
- 存储位置:Cookie 是存储在客户端浏览器上的小数据文件。
- 自动发送:浏览器会自动将与请求的域名相关的所有 Cookie 附加到每个请求中(根据路径和域)。
- 大小限制:每个 Cookie 大小限制一般为 4KB 左右。
- 有效期:Cookie 可以设置有效期,过期后浏览器会自动删除它。
- 安全性 :可以通过设置
HttpOnly
和Secure
标志提高安全性。HttpOnly
防止客户端脚本访问 Cookie,Secure
仅在 HTTPS 连接中发送。
Token
- 存储位置:Token 可以存储在客户端的内存、Local Storage 或 Session Storage 中。
- 手动发送 :Token 需要在每个请求中手动包含,通常通过 HTTP 头部(如
Authorization
头)发送。 - 大小限制:Token 大小没有严格限制,但通常应保持小巧以减少带宽消耗。
- 有效期:Token 通常具有有效期,过期后需要重新获取。
- 安全性:Token 通过使用复杂算法生成,并通常在传输和存储时加密,以提高安全性。
主要区别
-
存储和传输方式:
- Cookie:浏览器自动处理,存储在客户端浏览器中,自动附加到相关请求。
- Token:需要手动处理和传输,可以存储在任意客户端存储(如 Local Storage),通常通过 HTTP 头部发送。
-
安全性:
- Cookie :受限于浏览器的安全机制,可以设置
HttpOnly
和Secure
标志。 - Token:通常更灵活且更安全,通过 HTTPS 传输,加密存储,避免 XSS 和 CSRF 攻击。
- Cookie :受限于浏览器的安全机制,可以设置
-
有效期和更新:
- Cookie:可以设置长期有效期,也可以设置会话有效期(浏览器关闭即失效)。
- Token:通常有短期有效期,并通过刷新令牌(Refresh Token)机制来延长会话。
XSS 和 CSRF 补充
- 跨站脚本攻击(XSS) :如果网站存在XSS漏洞,攻击者可以通过注入恶意脚本来获取用户的Cookie信息,包括Token。攻击者可以利用Token冒充用户进行恶意操作。
- 跨站请求伪造(CSRF) :攻击者可以利用CSRF漏洞,诱使用户在已经登录的情况下访问恶意网站,该网站可能利用用户的Token发起伪造的请求,从而执行未经授权的操作。
避免
避免跨站脚本攻击(XSS)
- 输入验证和过滤 :对所有用户输入的数据(如表单提交、URL参数、Cookie等)进行严格的验证和过滤。移除或转义所有的特殊字符,如
<
,>
,&
,"
,'
等,以防止恶意脚本的注入。 - 安全的 Cookie 设置:在设置 Cookie 时,使用 HttpOnly 标志确保 Cookie 只能通过 HTTP 协议访问,不能通过 JavaScript 访问。这样可以防止通过 XSS 攻击获取到用户的 Cookie 信息。
避免跨站请求伪造(CSRF)
- SameSite Cookie 属性:设置 Cookie 的 SameSite 属性为 Strict 或 Lax,以限制跨站点请求中是否包含 Cookie。这可以帮助减少 CSRF 攻击的风险。
- 避免使用 GET 请求执行敏感操作:对于可能导致状态变化或数据修改的操作,应使用 POST、PUT 或 DELETE 等安全的 HTTP 方法,而不是 GET 方法。GET 请求容易受到 CSRF 攻击。
- 限制敏感操作的访问权限:对于需要执行的敏感操作(如修改用户信息、支付操作等),实施适当的访问控制和身份验证机制。例如,要求用户在执行这些操作前重新验证身份。
Session
- session 是另一种记录服务器和客户端会话状态的机制
- session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie 中
- 总之就是通过sessionId找到服务器中存储在session中与Session ID 关联的用户数据,从而识别用户并维护其状态
-
session 认证流程:
- 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
- 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器
- 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
- 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
根据以上流程可知,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。
Cookie 和 Session 的区别
- 安全性: Session 比 Cookie 安全,Session 是存储在服务器端的,Cookie 是存储在客户端的。
- 存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型。
- 有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
- 存储大小不同: 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。
5. 定时器这种东西,它其实不准时,你采取了什么方式让这问题变得更准时
手写一个更加精准的自我校准
先给出一个demo
大致思路是先记录初始时间,然后函数调用后再获取最新的时间,用最新时间减去初始时间,得出误差的毫秒值,再之后除余1000,得出每秒的误差值,用1秒减去该误差值即是真正的下一次计时器触发的时间。
js
let localTime = new Date(); //记录本地时间
function timer() {
const now = new Date(); // 获取当前本地时间
const timeGap = now - localTime; // 计算时间差
// 下一次计时器应该触发的时间,
const nextTickTime = 1000 - (timeGap % 1000); // 注意这里的 timeGap 是除余
setTimeout(() => {
// 在此处执行计时器的操作
console.log('tick');
timer(); // 递归调用,实现循环
}, nextTickTime);
}
timer(); // 启动计时器
一般倒计时的时间是后端传的
js
// 假设我们有一个 HTML 元素来显示倒计时
const display = document.getElementById('countdown');
// 假设后端返回的剩余时间是20小时
const remainingHours = 20
// 转成毫秒
const duration = remainingHours * 60 * 60 * 1000;
// 开始倒计时
startCountdown(duration, display);
function startCountdown(duration, display) {
let localTime = Date.now(); // 记录初始时间
function updateCountdown() {
const now = Date.now(); // 获取当前时间
const timeGap = now - localTime; // 计算时间差
// 下一次计时器应该触发的时间
const nextTickTime = 1000 - (timeGap % 1000);
// 计算剩余时间
let remainingTime = duration - timeGap;
if (remainingTime <= 0) {
display.innerHTML = "00:00:00";
return;
}
// 计算剩余小时、分钟和秒数
const hours = Math.floor(remainingTime / (1000 * 60 * 60));
const minutes = Math.floor((remainingTime % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((remainingTime % (1000 * 60)) / 1000);
// 格式化显示
display.innerHTML = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
// 使用 setTimeout 进行自我校准
setTimeout(updateCountdown, nextTickTime);
}
// 初始调用
updateCountdown();
}
webworker
定时器由于主线程的其他异步任务稍微的阻塞导致了计时不准,那么我们可以使用 Web Workers 直接新建一个子线程,来避免主线程的阻塞问题。Web Workers 运行在独立线程中,可以提高定时器的准确性。 主线程代码:
js
const worker = new Worker('worker.js');
worker.postMessage({ interval: 1000 });
worker.onmessage = (event) => {
console.log('Timer tick', event.data);
};
js
onmessage = (event) => {
const interval = event.data.interval;
function tick() {
postMessage('tick');
setTimeout(tick, interval);
}
setTimeout(tick, interval);
};
对于webworker不熟的小伙伴可以看看这篇文章 实战(简单):20分钟页面不操作,页面失效
6. 一个 promise 后面跟了三个 .then ,第三个 .then 这个方法里面的入参它是由谁决定的?
由第二个 .then 里的返回值决定。
如果说上一个 .then 的回调里面它其实没有 return 值,第三个 .then 会执行吗?
如果前一个 .then
的回调函数没有显式返回值(即没有当第二个 .then
返回一个 Promise
对象时,第三个 .then
仍然会执行。这种情况下,第三个 .then
的参数会接收到第二个 .then
返回的 Promise
对象的解析值或拒绝原因。使用 return
语句返回一个值或另一个 Promise
),那么后续的 .then
仍然会执行,但是它们的参数会接收到 undefined
。
7. 第二个.then return 一个promise,第三个 .then 还能执行吗?能够执行的话,那它拿到的值是什么?是拿到这个 promise 吗?
当第二个 .then
返回一个 Promise
对象时,第三个 .then
仍然会执行。这种情况下,第三个 .then
的参数会接收到第二个 .then
返回的 Promise
对象的解析值或拒绝原因。
8. 你在做的项目里面,就是你就是做过的,比如说你觉得遇到过最大的挑战是什么呢?就是遇到基础难题这相关的事情
这里需要根据自己的经历提前准备好了,以防被问到没回答好
- axios的封装
- 手写功能函数,比如表单的失焦和聚焦(保存功能),表单数据的变动
- 国际化多语言,文件上传下载和数据的处理,webworker的实际应用
- SSO单点登录的理解
9. 你最近有没有在了解或者学习什么新的东西啊?
四达时代
1. 假如说一个 DIV 元素,我想让它在页面上显示成一个三角形,怎么处理?
js
/*记忆口诀:盒子宽高均为零,三面边框皆透明。 */
div:after{
position: absolute;
width: 0px;
height: 0px;
content: " ";
border-right: 100px solid transparent;
border-top: 100px solid transparent;
border-left: 100px solid transparent;
border-bottom: 100px solid #ff0;
}
由上图可知,只要对有关一边的边框宽度设置为0,即可再弄一个方向不一样的三角形
js
div:after{
position: absolute;
width: 0px;
height: 0px;
content: " ";
border-right: 0px solid transparent;
border-top: 100px solid transparent;
border-left: 100px solid transparent;
border-bottom: 100px solid #ff0;
}
2. HTML 有 a 标签, a 标签有一个叫做 target 的属性, target 属性有哪些?取值都分别代表什么含义
语法
js
<a target="_blank|_self|_parent|_top|framename">
target
属性的用途是告诉浏览器希望将所链接的资源显示在哪里。默认情况下,浏览器使用的是显示当前文档的窗口、标签页或框架(iframe),所以新文档将会取代现在显示的文档,不过还有其他选择,请看下表:
属性值
值 | 描述 |
---|---|
_blank | 在新窗口或选项卡中打开链接文档。 |
_self | 在与点击相同的框架中打开链接的文档(默认)。 |
_parent | 在父框架中打开链接文档。(此时你处于iframe框架中) |
_top | 在窗口的整个主体中打开链接的文档。(此时你处于iframe框架中) |
framename | 在指定的 iframe 中打开链接文档。 |
3. HTML 里面头部有一个叫做 Meta 的标签,它是干什么用的?常用的 Meta 有哪些?
定义
meta标签提供关于HTML文档的元数据。元数据不会显示在页面上,但是对于机器是可读的。它可用于浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他 web 服务。 ------ W3School
<meta>
标签可以设置字符编码、页面描述、关键词、作者等。
SEO优化
-
页面关键词,每个网页应具有描述该网页内容的一组唯一的关键字。
人们可能会使用搜索,并准确描述网页上所提供信息的描述性和代表性关键字及短语。标记内容太短,则搜索引擎可能不会认为这些内容相关。另外标记不应超过 874 个字符。
html<meta name="keywords" content="your tags" />
-
页面描述,每个网页都应有一个不超过 150 个字符且能准确反映网页内容的描述标签。
html<meta name="description" content="150 words" />
移动设备
-
viewport:能优化移动浏览器的显示。如果不是响应式网站,不要使用initial-scale或者禁用缩放。
大部分4.7-5寸设备的viewport宽设为360px;5.5寸设备设为400px;iphone6设为375px;ipone6 plus设为414px。
html
<meta name="viewport" content="
width=device-width,
initial-scale=1.0,
maximum-scale=1.0,
user-scalable=no"/>
//`width=device-width` 会导致 iPhone 5 添加到主屏后以 WebApp 全屏模式打开页面时出现黑边
- width:宽度(数值 / device-width)(范围从200 到10,000,默认为980 像素)
- height:高度(数值 / device-height)(范围从223 到10,000)
- initial-scale:初始的缩放比例 (范围从>0 到10)
- minimum-scale:允许用户缩放到的最小比例
- maximum-scale:允许用户缩放到的最大比例
- user-scalable:用户是否可以手动缩 (no,yes)
注意,很多人使用initial-scale=1到非响应式网站上,这会让网站以100%宽度渲染,用户需要手动移动页面或者缩放。如果和initial-scale=1同时使用user-scalable=no或maximum-scale=1,则用户将不能放大/缩小网页来看到全部的内容。
4. 一个标准的 HTML,假如说它包含一些外链的 CSS 和 JS 文件,然后浏览器是如何解析一个 HTML 文件,并去获取相应的资源,然后渲染,最后上屏展示给用户的?
7. 简单描述一下,从网址里输入地址到页面显示的过程 拿到文件后的部分。
5. 在 CSS 里什么叫做 BFC
BFC的概念
BFC
是 Block Formatting Context
的缩写,即块级格式化上下文。BFC
是CSS布局的一个概念,是一个独立的渲染区域,规定了内部box如何布局, 并且这个区域的子元素不会影响到外面的元素,其中比较重要的布局规则有内部 box 垂直放置,计算 BFC 的高度的时候,浮动元素也参与计算。
BFC的原理布局规则
- 内部的Box会在
垂直方向
,一个接一个地放置 - Box
垂直方向的距离由margin决定
。属于同一个BFC的两个相邻Box的margin会发生重叠 - 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反
- BFC的区域
不会与float box重叠
- BFC是一个独立容器,容器里面的
子元素不会影响到外面的元素
- 计算BFC的高度时,
浮动元素也参与计算高度
- 元素的类型和
display属性,决定了这个Box的类型
。不同类型的Box会参与不同的Formatting Context
。
如何创建BFC?
- 根元素,即HTML元素
- float的值不为none
- position为absolute或fixed
- display的值为inline-block、table-cell、table-caption
- overflow的值不为visible
BFC的使用场景
- 去除边距重叠现象
- 清除浮动(让父元素的高度包含子浮动元素)
- 避免某元素被浮动元素覆盖
- 避免多列布局由于宽度计算四舍五入而自动换行
6. ES6 里面的箭头函数和普通函数有什么区别。
7. 现在有一个普通函数,我想要避免它被用户作为一个构造函数来调用,应该如何做?
new.target
是 ES6 引入的一个元属性,用于检测函数或构造函数是否通过new
关键字调用。它在构造函数中非常有用,可以用来确保某些函数不会被错误地用作构造函数调用。
当一个函数或构造函数通过 new
关键字调用时,new.target
会指向该构造函数本身。如果函数或构造函数不是通过 new
关键字调用的,那么 new.target
将是 undefined
。
js
function myFunction() {
if (new.target !== undefined) { // 表示该函数是通过 new 调用的,那么就给它报错
throw new Error('myFunction cannot be called with new.');
}
// 其他函数逻辑
}
// 正常调用
myFunction(); // 这是正常调用
// 使用 `new` 调用会抛出错误
try {
new myFunction(); // 抛出错误: myFunction cannot be called with new.
} catch (e) {
console.error(e.message); // 输出: myFunction cannot be called with new.
}
8. 假如我你开发的一个网页,然后你会发现它这个网页过了 10 秒才出现首屏的内容,对于你来说你有哪些想法或者说工具去分析这个网页慢的这个过程?
加载慢的原因
- 网络延时问题
- 资源文件体积是否过大
- 资源是否重复发送请求去加载了
- 优化静态资源的加载,比如图片、CSS 和 JavaScript 文件
- 检查关键资源(如 CSS 和首屏所需的 JavaScript 文件)的加载顺序,确保它们在首屏内容之前加载。
- 加载脚本的时候,渲染内容堵塞了,检查是否有阻塞渲染的 JavaScript 和 CSS 文件。尽量将非关键的 JavaScript 文件放在页面底部或使用
async
或defer
异步属性。
解决方案
常见的几种SPA首屏优化方式
- 减小入口文件积
- 静态资源本地缓存
- UI框架按需加载
- 图片资源的压缩
- 组件重复打包
- 开启GZip压缩
- 使用SSR
- 静态资源部署
9. vue2 和 vue3 对于响应式的底层实现有什么不同?
2. Vue2和Vue3数据更新时有什么不一样?中的 Proxy 替代 Object.defineProperty
10. 在 vue2 里,我一个父组件和一个子组件,当父组件被实例化或者说初始化的时候,父组件和子组件它们分别的生命周期的钩子函数的调用顺序是什么样?
组件生命周期
生命周期(父子组件) 父组件beforeCreate --> 父组件created --> 父组件beforeMount --> 子组件beforeCreate --> 子组件created --> 子组件beforeMount --> 子组件 mounted --> 父组件mounted -->父组件beforeUpdate -->子组件beforeDestroy--> 子组件destroyed --> 父组件updated
加载渲染过程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
挂载阶段 父created->子created->子mounted->父mounted
父组件更新阶段 父beforeUpdate->父updated
子组件更新阶段 父beforeUpdate->子beforeUpdate->子updated->父updated
销毁阶段 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
11. vue2 里面插槽有哪几种?
分类
slot
可以分来以下三种:
- 默认插槽
- 具名插槽
- 作用域插槽
默认插槽
子组件用 <slot>
标签来确定渲染的位置,标签里面可以放 DOM
结构,当父组件使用的时候没有往插槽传入内容,标签内 DOM
结构就会显示在页面
父组件在使用的时候,直接在子组件的标签内写入内容即可
子组件Child.vue
html
<template>
<slot>
<p>插槽后备的内容</p>
</slot>
</template>
父组件
html
<Child>
<div>默认插槽</div>
</Child>
具名插槽
子组件用name
属性来表示插槽的名字,不传为默认插槽
父组件中在使用时在默认插槽的基础上加上slot
属性,值为子组件插槽name
属性值
子组件Child.vue
html
<template>
<slot>插槽后备的内容</slot>
<slot name="content">插槽后备的内容</slot>
</template>
父组件
html
<child>
<template v-slot:default>具名插槽</template>
<!-- 具名插槽⽤插槽名做参数 -->
<template v-slot:content>内容...</template>
</child>
作用域插槽
子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件v-slot
接受的对象上
父组件中在使用时通过v-slot:
(简写:#)获取子组件的信息,在内容中使用
子组件Child.vue
html
<template>
<slot name="footer" testProps="子组件的值">
<h3>没传footer插槽</h3>
</slot>
</template>
父组件
html
<child>
<!-- 把v-slot的值指定为作⽤域上下⽂对象 -->
<template v-slot:footer="slotProps">
来⾃⼦组件数据:{{slotProps.testProps}}
</template>
<template #footer="slotProps">
来⾃⼦组件数据:{{slotProps.testProps}}
</template>
</child>
小结:
v-slot
属性只能在<template>
上使用,但在只有默认插槽时可以在组件标签上使用- 默认插槽名为
default
,可以省略default直接写v-slot
- 缩写为
#
时不能不写参数,写成#default
- 作用域插槽可以通过解构获取子组件传俩的值
v-slot={user}
,还可以重命名v-slot="{user: newName}"
和定义默认值v-slot="{user = '默认值'}"
12. VUE 的路由有几种路由模式
13. history 路由的时候,服务端需要做哪些配置?
## 7.history配置的路由变化的时候会向后端发送请求吗?后端需要做什么配置?
14. NPM 里面每次安装的时候会根据有一个叫做 package.json,还有一个 package-lock.json,它俩都是干什么用?
package.json
-
作用:
- 用于描述项目的元数据和依赖信息。
- 包含了项目的名称、版本、作者、许可证等信息。
- 以及项目所依赖的各种 Node 模块及其版本范围。
-
具体内容:
name
:项目名称。version
:项目版本号。dependencies
:正式环境下需要安装的依赖。devDependencies
:开发环境下需要安装的依赖。scripts
:定义可以通过 npm 执行的脚本命令。author
、license
等元数据。
-
管理依赖:
- 当执行
npm install
时,根据package.json
中的依赖信息,npm 将安装对应的包及其依赖。 - 依赖的版本可以是一个范围,如
^1.2.3
表示安装1.x.x
的最新版本。
- 当执行
package-lock.json
-
作用:
- 确定安装的确切版本以及安装顺序,保证在不同的环境中安装的结果是一致的。
- 锁定每个依赖包的精确版本号,包括它们的子依赖项。
-
具体内容:
- 记录了每个直接依赖项的精确版本号。
- 记录了每个依赖项的依赖关系树,以及它们的下载地址。
-
使用场景:
- 在团队协作中,保证每个开发者和构建服务器安装的依赖版本一致性。
- 当多次安装相同项目时,保证每次安装的依赖版本都是一致的,避免因为依赖版本的不同而导致的问题。
区别和关系
package.json
定义了项目的基本信息和依赖范围,是开发者手动维护和编辑的文件。package-lock.json
自动生成并由 npm 管理,用于确保在不同机器上、不同时间点安装的依赖版本一致。
package.json
是项目的元数据和依赖描述文件,而package-lock.json
是 npm 自动管理的锁定依赖版本的文件,确保项目在不同环境下的一致性和稳定性。
15. 这个 dependencies 里面这些相关的依赖包,它有一个关于版本的命名规范,包括大版本、小版本。分别介绍一下怎么更新小版本、大版本和全量版本
要不是之前实习的时候导师当着我的面现场改团队用的埋点依赖包然后发布,我早就懵逼了
在 package.json
文件中,通常使用的是语义化版本规范(Semantic Versioning),即 SemVer 规范,来定义项目依赖的版本范围。这些版本号由三部分组成:主版本号、次版本号和修订版本号,形式为 MAJOR.MINOR.PATCH
。
更新小版本(PATCH)
更新小版本号意味着引入了向后兼容的 bug 修复或非关键性更新。在 package.json
中,通常可以使用 ~
符号来更新小版本号。
例如,假设当前依赖的版本是 1.2.3
,可以通过以下方式更新小版本:
json
"dependencies": {
"example-package": "~1.2.3"
}
这将允许 npm 安装 1.2.x
的最新版本,但不包括 1.3.0
或更高的版本。
更新大版本(MINOR)
更新大版本号表示引入了向后兼容的新功能。在 package.json
中,可以使用 ^
符号来更新大版本号。
例如,假设当前依赖的版本是 1.2.3
,可以通过以下方式更新大版本:
json
"dependencies": {
"example-package": "^1.2.3"
}
这将允许 npm 安装 1.x.x
的最新版本,包括 1.3.0
,但不包括 2.0.0
或更高的版本。
更新全量版本(MAJOR)
更新全量版本号意味着引入了不向后兼容的改变。在 package.json
中,可以直接指定全量版本号。
例如,假设当前依赖的版本是 1.2.3
,可以通过以下方式更新全量版本:
json
"dependencies": {
"example-package": "2.0.0"
}
这将安装指定的 2.0.0
版本,忽略 1.x.x
的所有版本。
总结
- 小版本更新(PATCH) :使用
~
符号,例如~1.2.3
,安装1.2.x
的最新版本。 - 大版本更新(MINOR) :使用
^
符号,例如^1.2.3
,安装1.x.x
的最新版本。 - 全量版本更新(MAJOR) :直接指定版本号,例如
2.0.0
,安装指定的版本。
16. 如果说我想让这个包尽量是小版本可以变,但是中间的版本不变的情况下,或者说中间版本可变、大版本不能变的情况下,分别怎么配置
在 package.json
中,可以通过使用不同的符号来配置依赖的版本范围,以满足特定的更新需求。具体地:
小版本可变、中间版本固定的情况
如果希望依赖包的大版本和中间版本固定,只允许小版本变化,可以使用 ~
符号。这样配置后,npm 将允许安装指定的小版本和修订版本的最新版本,但不会升级到新的大版本或者更新中间版本。
例如,假设当前依赖的版本是 1.2.3
,可以这样配置:
json
"dependencies": {
"example-package": "~1.2.3"
}
这将允许 npm 安装 1.2.x
的最新版本,例如 1.2.4
、1.2.5
等,但不会安装 1.3.0
或更高版本。
中间版本可变、大版本固定的情况
如果希望依赖包的大版本固定,只允许中间版本和小版本变化,可以使用 ^
符号。这样配置后,npm 将允许安装指定的大版本和中间版本的最新版本,但不会升级到新的大版本。
例如,假设当前依赖的版本是 1.2.3
,可以这样配置:
json
"dependencies": {
"example-package": "^1.2.3"
}
这将允许 npm 安装 1.x.x
的最新版本,例如 1.3.0
、1.4.0
等,但不会安装 2.0.0
或更高版本。
总结
- 小版本可变、中间版本固定 :使用
~
符号,例如"~1.2.3"
。 - 中间版本可变、大版本固定 :使用
^
符号,例如"^1.2.3"
。
17. 在这个依赖的包的名字后面,关于版本号还应该有一个修饰符,这个修饰符是决定这个升级版本的规则的。这个修饰符都有什么?然后规则是什么样的?了解过吗?
在 npm 的 package.json
文件中,用于定义依赖版本范围的修饰符有几种,它们决定了依赖包在升级时的规则。常见的修饰符包括:
-
无修饰符
- 如果没有明确指定修饰符,默认情况下是精确版本匹配,即只安装指定的精确版本。
- 例如:
"example-package": "1.2.3"
-
波浪号
~
- 波浪号表示安装指定版本及其后续的补丁版本,不包括新的次版本。
- 例如:
"example-package": "~1.2.3"
允许安装1.2.x
的最新版本,例如1.2.4
、1.2.5
,但不包括1.3.0
。
-
插入号
^
- 插入号表示安装指定版本及其后续的次版本和补丁版本,不包括新的主版本。
- 例如:
"example-package": "^1.2.3"
允许安装1.x.x
的最新版本,例如1.3.0
、1.4.0
,但不包括2.0.0
。
-
星号
*
- 星号表示安装最新的任何版本。
- 例如:
"example-package": "*"
安装最新版本的example-package
。
-
>=
,>
,<
,<=
- 这些符号用于指定一个版本的范围。
- 例如:
"example-package": ">=1.2.3"
表示安装大于等于1.2.3
的任何版本。
这些修饰符允许开发者在 package.json
中灵活地定义依赖版本的约束和更新规则,确保项目依赖的稳定性和兼容性。
18. 你讲一下 ant design view 里边这个表单组件有哪些常用的属性和事件?
19. 现在让你来脱离这个 and design view 组件库,让你开发一个 form 组件,你会给他怎么设计props?怎么设计这个相应的方法 method 或者events?
20. 你讲一下 HTTP 请求 post 和 get 有什么区别
21. 你能从底层传输的角度,甚至说从这个 HTTP 三次握手这个底层角度来讲一下 post 和 get 的区别吗?你刚才说的其实全部都是应用层的,或者说是属于这种网络顶层上层的,从底层它们有什么不同?或者说从服务端处理这两个请求的时候有什么不同?
GET 请求通常是幂等的,POST 请求通常是非幂等的
-
幂等性:GET 请求通常是幂等的,意味着多次执行相同的请求会得到相同的结果,不会对服务器的数据产生副作用。
-
非幂等性:POST 请求通常是非幂等的,每次请求可能会导致服务器上的数据发生变化(例如提交表单,导致数据存储在数据库中)。
POST有可能产生两个数据包,GET只会发送一个数据包
GET请求在任何情况下都会把 http header 和 data 一次性发送完成。
POST在部分情况下,(这取决于 浏览器/发送方 和它的版本),会产生两个数据包。第一个tcp发送header,确认服务器可以响应并且具备接受数据的能力,响应100。第二个TCP包在第一个请求成功(100后)才会发送。包含data。如果第一个请求(header)失败,则data不进行发送。
常规来说POST发送两次数据包在速度上会比GET慢,但其实在网络环境好的情况下这个速度影响微乎其微。但是在网络环境差的情况下。POST发送两次数据包可以很好的校验数据完整性。
并不是任何情况下POST都会有两个数据包,取决于 浏览器/发送方 和它的版本