有关小程序中uni-app框架常见面试题

uniapp 理解

UniApp是基于Vue.js开发的跨平台应用框架,通过一套代码实现在多个小程序平台上运行,包括微信小程序、支付宝小程序、百度小程序、字节跳动小程序等。它结合了Vue.js的开发特性和生态系统,提供了一种高效、快速的开发方式

使用UniApp开发小程序有以下优势和特点

  • 跨平台:一套代码可以在多个小程序平台上运行,大大减少了开发和维护的成本。
  • 开发效率高:基于Vue.js开发,使用Vue.js的开发特性和生态系统,提供了高效的开发体验。
  • 丰富的组件和插件库:UniApp提供了丰富的组件和插件库,方便快速搭建页面和实现各种功能。
  • 快速迭代和更新:UniApp支持快速发布新版本和更新,加速应用的迭代速度。

uniapp适用场景

  • 需要在多个小程序平台上发布和运行的项目;
  • 对开发效率和代码复用性有要求的项目;
  • 轻量级和快速迭代的小程序应用;

挑战和解决方法:

  • 平台差异:不同小程序平台有一些差异性,解决方法是使用条件编译,根据不同平台使用相应的API和样式。
  • 性能优化:因为UniApp是基于编译的方式实现跨平台,对性能的优化需要充分了解不同平台的特点和处理器能力,使用合适的优化技巧。
  • 第三方组件兼容性:一些第三方组件在不同平台上的兼容性可能存在问题,可以做一些适配性工作,或者寻找替代组件。

解决小程序平台差异的方法包括:

  • 使用条件编译,根据不同平台使用相应的API和样式。
  • 在编译时对不同平台的代码进行适配,如处理底部栏、导航栏等不同平台的差异性。
  • 使用平台特定的组件和插件、样式。

uniapp进行条件编译的两种方法

  1. #ifdef
    • 在 UniApp 中,#ifdef 指令用于检查当前代码运行的平台是否满足条件。如果条件为真,则编译下面的代码块;如果条件为假,则忽略下面的代码块。
    • 语法:#ifdef condition code_to_compile_if_true
  2. #ifndef
    • #ifndef 指令与 #ifdef 相反,在 UniApp 中用于检查当前代码运行的平台是否不满足条件。如果条件为假,则编译下面的代码块;如果条件为真,则忽略下面的代码块。
    • 语法:#ifndef condition code_to_compile_if_false
1
2
3
4
5
6
7
8
9
#ifdef APP-PLUS  
console.log('This code is running on a native app platform.');
#else
console.log('This code is not running on a native app platform.');
#endif

#ifndef H5
console.log('This code is not running on a H5 web platform.');
#endif

uniapp 上传文件API

1
2
3
4
5
6
7
8
uni.uploadFile({
url: 'https://',
fileType:'image',
filePath:'路径',
name:'name',
success: function(){},
console.log(res)
})

监听页面滚动

1
uni.onPageScroll()

如何让图片宽度不变,高度自动变化,保持原图宽高不变

给image 标签添加 mode=‘widthFix’

  • aspectFit:保持长宽比缩放图片,使图片的长边能完全显示出来
  • aspectFill:保持长宽比缩放图片,使图片的短边能完全覆盖容器
  • widthFix:宽度不变,高度自动变化,保持原图宽高比不变
  • heightFix:高度不变,宽度自动变化,保持原图宽高比不变
  • scaleToFill:拉伸图片,使图片填满容器
  • top:顶部对齐,不改变原始尺寸
  • bottom:底部对齐,不改变原始尺寸
  • left:左对齐,不改变原始尺寸
  • right:右对齐,不改变原始尺寸
  • center:居中,不改变原始尺寸
  • top left:上左对齐,不改变原始尺寸
  • top right:上右对齐,不改变原始尺寸
  • bottom left:下左对齐,不改变原始尺寸
  • bottom right:下右对齐,不改变原始尺寸

jquery,vue,小程序,uniapp本地数据存储

jquery

1
2
存: $.cookie('key', 'value')
取: $.cookie('key')

vue

1
2
存:localStorage.setItem('key', 'value')
取:localStorage.getItem('key')

wx小程序

1
2
存:wx.setStorage / wx.setStorageSync
取:wx.getStorage / wx.getStorageSync

uniapp

1
2
存:uni.setStorage({key:"属性名", data:"值"})
取:uni.getStorage({key:"属性名"})

uniApp中如何进行页面跳转?

  • 使用 uni.navigateTo 进行普通页面跳转:
1
2
3
uni.navigateTo({  
url: 'pages/newPage/newPage'
});
  • 使用 uni.redirectTo 进行页面重定向(替换当前页面):
1
2
3
uni.redirectTo({  
url: 'pages/newPage/newPage'
});
  • 使用 uni.switchTab 进行 Tab 切换:
1
2
3
uni.switchTab({  
url: 'pages/tabBar/tabBar'
});
  • 使用 uni.reLaunch 关闭所有页面并打开到应用内的某个页面:
1
2
3
uni.reLaunch({  
url: 'pages/index/index'
});

uniApp中小程序如何进行数据缓存?

小程序没有cookie,只有storage缓存

  • 使用 uni.setStorageSync 将数据存储到本地缓存中:
1
2
// 存储数据  
uni.setStorageSync('key', 'value');
  • 使用 uni.getStorageSync 从本地缓存中获取数据:
1
2
// 获取数据  
let data = uni.getStorageSync('key');

uniApp中如何实现下拉刷新和上拉加载更多? - 使用uniapp 生命周期函数

  • 实现下拉刷新功能:

在需要实现下拉刷新的页面中,添加 onPullDownRefresh 方法,并在该方法中编写下拉刷新的逻辑代码。例如:

1
2
3
4
5
6
7
8
export default {  
onPullDownRefresh() {
// 下拉刷新逻辑代码
console.log('下拉刷新');
// 停止下拉刷新动画
uni.stopPullDownRefresh();
}
}

在上述代码中,我们在 onPullDownRefresh 方法中编写了下拉刷新的逻辑代码,并使用 uni.stopPullDownRefresh() 方法停止下拉刷新动画。

  • 实现上拉加载更多功能:

在需要实现上拉加载更多的页面中,添加 onReachBottom 方法,并在该方法中编写上拉加载更多的逻辑代码。例如:

1
2
3
4
5
6
export default {  
onReachBottom() {
// 上拉加载更多逻辑代码
console.log('上拉加载更多');
}
}

需要注意的是,为了实现上拉加载更多功能,你需要在页面中添加一个 scroll-view 组件,并在该组件上绑定 scrolltolower 事件。例如:

1
2
3
<scroll-view scroll-y="true" style="height: 100%;" @scrolltolower="onReachBottom">  
<!-- 页面内容 -->
</scroll-view>

uniApp中如何获取用户地理位置信息?

  • 使用uni.getLocation
1
2
3
4
5
6
7
8
9
10
11
12
uni.getLocation({  
type: 'gcj02', // 返回坐标类型
altitude: true, // 是否返回高度信息
success: function (res) {
console.log('当前位置的经度:' + res.longitude);
console.log('当前位置的纬度:' + res.latitude);
console.log('当前位置的海拔:' + res.altitude);
},
fail: function (error) {
console.log('获取地理位置失败:' + error.message);
}
});

uniApp中如何获取设备信息?

  • 使用uni.getSystemInfo
1
2
3
4
5
6
7
8
9
10
11
12
uni.getSystemInfo({  
success: function (res) {
console.log('设备型号:' + res.model);
console.log('设备像素比:' + res.pixelRatio);
console.log('屏幕宽度:' + res.windowWidth);
console.log('屏幕高度:' + res.windowHeight);
// 其他设备信息...
},
fail: function (error) {
console.log('获取设备信息失败:' + error.message);
}
});

uniApp中如何实现表单的提交和验证?

  • 先将需要的正则匹配封装好,通过阻止<form>表的默认提交功能,先对数据进行正则匹配,成功后在进行提交
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<view>
<form @submit="submitForm">
<input v-model="formData.username" type="text" placeholder="用户名" />
<input v-model="formData.password" type="password" placeholder="密码" />
<button formType="submit">提交</button>
</form>
</view>
</template>

<script>
export default {
data() {
return {
formData: {
username: '',
password: ''
}
};
},
methods {
submitForm(e) {
e.mp.preventDefault(); // 阻止默认行为
// 进行表单验证逻辑,并处理提交操作
// 可以在这里编写验证逻辑,例如检查用户名和密码是否符合规则等
}
}
};
</script>

uniApp中如何实现页面的登录授权?

  • 用户登录页面设计:首先需要设计一个用户登录页面,包括输入用户名、密码等登录信息的表单元素。
  • 登录逻辑处理:在uniApp中,你可以通过调用后端接口来进行用户登录验证。一般情况下,用户在登录页面输入用户名和密码后,通过点击登录按钮触发相应的登录方法。
  • 登录状态管理:一旦用户成功登录,通常会将用户的登录状态保存在本地,比如使用 uni.setStorageSync 方法将用户信息存储在本地缓存中。
  • 权限控制:在需要授权的页面或操作中,可以通过判断用户的登录状态来确定是否具有权限进行相关操作。如果用户未登录或登录状态过期,则可以跳转至登录页面进行重新登录。

uniApp中如何实现页面的分享到朋友圈功能?

1
2
3
4
5
6
7
8
9
10
11
12
13
shareToTimeline() {  
uni.share({
provider: 'weixin',
scene: 'WXSenceTimeline', // 表示分享到朋友圈
type: 'image',
imageUrl: '/static/share-image.jpg', // 分享的图片地址,需替换为真实图片地址
success(res) {
console.log('分享成功');
},
fail(err) {
console.log('分享失败', err);
}
});

uniApp中如何实现图片预览功能?

  • 可以使用 uni.previewImage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>  
<view>
<image src="/static/image.jpg" @click="previewImage" />
</view>
</template>

<script>
export default {
methods: {
previewImage() {
uni.previewImage({
urls: ['/static/image.jpg'], // 需要预览的图片链接列表,可以是本地路径或网络路径
current: '/static/image.jpg', // 当前显示的图片链接,可选
success() {
console.log('预览图片成功');
},
fail(err) {
console.log('预览图片失败', err);
}
});
}
}
};
</script>

uniApp中如何实现页面间的数据传递

在uniApp中,你可以通过多种方式实现页面间的数据传递,以下是一些常用的方法:

  • 使用URL参数传递数据:在跳转页面时,可以通过URL参数将数据传递给目标页面。在源页面使用uni.navigateTouni.redirectTo等跳转方法时,可以在URL中携带参数,在目标页面通过this.$route.querythis.$mp.query来获取参数。
  • 使用全局变量:可以在App.vue中定义全局变量,然后在各个页面中通过this.$store.state.xxx来访问和修改全局变量的数值。
  • 使用Vuex状态管理:如果应用较为复杂,推荐使用Vuex进行状态管理。在需要传递数据的页面中,通过提交mutation或dispatch action的方式来改变状态,在目标页面通过计算属性或getter来获取状态。
  • . 使用本地缓存:可以使用uni.setStorageSyncuni.getStorageSync等方法将数据存储在本地缓存中,在不同页面中读取和修改这些数据
  • 事件总线(Event Bus):创建一个Vue实例作为事件总线,通过该实例的emit和on方法来实现不同组件间的通信。

在uniApp中,页面的生命周期包括应用生命周期和页面生命周期。

  • 应用生命周期

    • . onLaunch:应用初始化时触发,全局只触发一次。
    • . onShow:应用启动、从后台进入前台或重新进入应用时触发
    • . onHide:应用从前台进入后台时触发
    • . onError:应用发生脚本错误或 API 调用失败时触发
    • . onUniNViewMessage:监听来自原生 页面发送到 uni-page 的消息
  • 页面生命周期

  • . onLoad:页面加载时触发

  • . onShow:页面显示/切入前台时触发

  • . onReady:页面初次渲染完成时触发

  • onHide:页面隐藏/切入后台时触发

  • onUnload:页面卸载时触发

  • onPullDownRefresh:下拉时触发

  • . onReachBottom:上拉触底触发

  • . onShareAppMessage:用户点击转发时触发

  • . onPageScroll:页面滚动时触发

  • . onResize:页面尺寸改变时触发

实现微信登录 - uni.getUserProfile() / uni.login()

  • 引入uni-app的登录插件:uni-app提供了一个名为uni-login的插件,可以方便地实现微信登录功能。你可以在uni-app的插件市场中搜索并安装该插件。
  • 配置插件参数:在插件安装完成后,你需要在manifest.json文件中配置插件的参数。将微信开放平台注册的AppID填入uni-login插件的配置中。
  • 调用登录方法:在需要进行微信登录的页面中,你可以调用uni.login方法来触发微信登录操作。该方法会返回一个包含登录凭证的对象。
  • 获取用户信息:登录成功后,你可以使用uni.getUserInfo方法来获取用户的基本信息,如昵称、头像等。该方法也会返回一个包含用户信息的对象。
  • 处理登录回调:在登录成功后,你可以将登录凭证发送到后端服务器进行验证,并根据验证结果进行相应的操作,如保存用户信息、跳转到主页等。
1
2
3
4
5
6
7
8
9
10
11
12
uni.login({  
provider: 'weixin',
success(res) {
console.log('登录凭证:', res.code);
// 将登录凭证发送到后端服务器进行验证
// 处理登录回调
},
fail(err) {
console.log('登录失败', err);
}
});
const user = uni.getUserInfo()

button open-type 属性

在uniApp中,button组件的open-type属性用于指定按钮的开放类型,决定了按钮的行为。常用的open-type属性值包括:

  • getUserInfo:触发获取用户信息的行为,用户点击按钮后会弹出授权询问框,询问用户是否授权小程序获取用户信息。
  • getPhoneNumber:触发获取用户手机号的行为,用户点击按钮后会弹出授权询问框,询问用户是否授权小程序获取用户手机号。
  • contact:触发客服消息会话,打开客服会话界面。
  • share:触发小程序分享功能,打开分享界面。
  • launchApp:打开APP内部页面或小程序。

小程序支付和h5支付有什么区别

  • 小程序支付:小程序支付是指在微信小程序中进行的支付操作。开发者可以通过调用微信提供的 wx.requestPayment 接口来实现在小程序内进行支付。对于小程序支付,通常需要使用微信提供的商户号和证书来进行交易。
  • H5 支付:H5 支付是指在移动浏览器中进行的网页端支付操作。H5 支付通常使用传统的网页形式接入各个第三方平台(如微信、支付宝等)提供的接口,用户通过浏览器完成整个交易过程。

uniapp request 封装

在UniApp中可以使用封装好的网络请求库如uni.request来进行网络请求。通过调用相应的API方法,可以发送HTTP请求并处理返回的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
class Utils {
// 在类初始化上加上baseUrl属性, 添加请求根路径
constructor(){
this.baseUrl = 'http://159.75.169.224:7300/pz'
}

// 获取用户信息
getUserInfo() {
// 调用登录api
uni.login({
success: (res)=> {
console.log(res)
this.request({
url: '/auth/wxLogin',
data: {
code: res.code
},
success: res=>{
// console.log('login_success_res',res)
},
fail: res=>{
// console.log('login_fail_res',res)
}
})
}
})
}


// 请求封装
request(option={
showLoading: false
}){
// 判断是否有url
if(!option.url){
return false
}

if(option.showLoading){
showLoading()
}

uni.request({
url: this.baseUrl + option.url,
// https://apifox.com/apidoc/shared-50a14ca5-c1d1-47ca-99f0-315ecfa52706
// http://159.75.169.224:7300/pz
// option.data参数由外部传入
data: option.data ? option.data : {},
header: option.header ? option.header : {},
method: option.method ? option.method : 'GET',
// 请求接口成功处理
success:res=>{
uni.hideLoading()
// 后端返回异常
if(res.data.code != 10000){
if(option.fail && typeof option.fail == 'function'){
option.fail(res.data)
}
}else{
if(option.success && typeof option.success == 'function'){
option.success(res.data)
}
}
},
// 失败处理
fail:res=>{
uni.hideLoading()
option.fail(res.data)
}

})
}

// 接口请求前添加loading效果,不能放在接口请求中,防止有接口并发,添加多个loading,单独封装,loading有仅只有一个loading效果
showLoading(){
// 将状态记录缓存
const isShowLoading = uni.getStorageSync('isShowLoading')
// 让loading有仅只有一个loading效果
if(isShowLoading){
uni.hideLoading()
uni.setStorageSync('isShowLoading',false)
}
uni.showLoading({
title: '加载中...',
complete: function() {
uni.setStorageSync('isShowLoading',true)
},
fail: function() {
uni.setStorageSync('isShowLoading',false)
}
})
}
}

export default new Utils()

uniapp 跨域解决

  • 代理 - h5使用vite.config.js 设置代理,小程序可以在小程序的 app.json 文件中,可以配置小程序的域名白名单,允许小程序访问指定的接口域名,还可以使用小程序特有的 API 来请求数据,如 wx.request, wx.uploadFile 等。
  • 使用内置浏览器

UniApp是一个基于Vue.js开发跨平台应用的框架,它可以通过一套代码实现在多个平台上运行,包括小程序、H5、App等。UniApp具有的特点和优势包括:

  • 跨平台:一套代码编写多端运行,提高开发效率和代码复用性。
  • 性能优越:底层基于原生渲染,性能接近原生应用。
  • 开发便捷:使用Vue.js开发,具备Vue.js的开发特性和生态系统。
  • 社区活跃:拥有庞大的开发者社区和丰富的第三方组件库支持。

UniApp的跨平台原理

UniApp的跨平台原理是基于编译的方式实现的。通过将Vue.js的代码编译为各个平台所需的代码,然后在不同平台的渲染引擎中运行。UniApp根据不同的平台生成相应的代码,使其能够在小程序、H5、App等多个平台上运行。

打包和发布UniApp项目

打包和发布UniApp项目可以使用HBuilder X进行操作。在HBuilder X中选择相应的菜单和配置项来进行打包和发布,根据不同平台和发布渠道选择相应的选项和配置参数。

申请发布小程序的流程

  1. 开发小程序: 首先,您需要开发和完善小程序的功能和页面。可以使用开发工具进行调试和预览,确保小程序的功能和体验符合要求。
  2. 注册账号: 您需要在微信小程序官网上注册开发者账号,需要提供相关资料进行实名认证和注册。注册成功后,您可以登录小程序后台进行后续操作。
  3. 完善信息: 进入小程序后台,完善小程序的基本信息,包括小程序名称、图标、描述、类目等。确保信息准确、完整且符合微信的规定。
  4. 提交审核: 在小程序后台提交小程序审核申请,上传小程序代码包、填写版本更新内容等信息。审核会对小程序进行功能、界面、内容等方面的审核。
  5. 等待审核: 提交审核后,需等待微信小程序官方进行审核。审核周期一般为1-7个工作日,具体时间根据审核工作量而定。
  6. 审核结果: 审核通过后,您会收到审批通过的消息,可以在小程序后台进行发布和上线;若审核未通过,需根据反馈修改问题并重新提交审核。
  7. 相关域名: 服务器域名+ 业务域名(h5项目根目录下加上效验文件) + uploadFile合法域名 + downloadFile合法域名
  8. 提交代码
  9. 发布上线: 审核通过后,您可以在小程序后台进行版本发布上线操作。发布后,用户可以在微信中搜索和使用您的小程序了。

manifest.json

  1. 可以在UniApp的App.vue文件中通过<style>标签编写全局样式,也可以在common目录下的样式文件中定义全局样式。通过在manifest.json中的globalStyle字段配置全局样式文件。
  2. 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{  
"name": "MyUniApp", // 小程序名称
"description": "A UniApp mini program", // 小程序描述
"appid": "wx1234567890", // 小程序的AppID
"versionName": "1.0.0", // 版本名称
"versionCode": "1", // 版本号
"provider": "WeChat", // 提供商,即小程序平台
"uni": { // UniApp 特定配置
"webpackChain": {}, // 自定义Webpack配置
"mainfestVersion": "2.0", //Mainfest 版本
"optimizeMainPackage": false // 是否进行主包优化
},
"pages": [ // 小程序页面配置
{
"path": "pages/index/index", // 页面路径
"style": { // 页面样式配置
"app-plus": {
"navigationBarTitleText": "首页", // 在App端显示的页面标题
"navigationStyle": "custom" // 页面导航栏样式,custom表示自定义导航栏
}
}
},
{
"path": "pages/detail/detail",
"style": {
"app-plus": {
"navigationBarTitleText": "详情页",
"navigationStyle": "custom"
}
}
}
],
"tabBar": { // 底部TabBar配置
"color": "#666666", // 未选中文字颜色
"selectedColor": "#3cc51f", // 选中文字颜色
"backgroundColor": "#ffffff", // 背景颜色
"borderStyle": "black", // 边框样式
"list": [ // TabBar 列表
{
"pagePath": "pages/index/index", // 页面路径
"text": "首页", // Tab文字
"iconPath": "static/tabbar/home.png", // 未选中图标路径
"selectedIconPath": "static/tabbar/home_active.png" // 选中图标路径
},
{
"pagePath": "pages/cart/cart",
"text": "购物车",
"iconPath": "static/tabbar/cart.png",
"selectedIconPath": "static/tabbar/cart_active.png"
}
]
},
"networkTimeout": { // 网络请求超时时间配置
"request": 10000, // 请求超时时间
"connectSocket": 10000, // 连接 WebSocket 超时时间
"uploadFile": 10000, // 上传文件超时时间
"downloadFile": 10000 // 下载文件超时时间
},
"permission": { // 小程序权限配置
"scope.userLocation": { // 用户地理位置权限
"desc": "您的位置信息将用于定位服务"
},
"scope.userInfo": { // 用户个人信息权限
"desc": "您的个人信息将用于展示"
}
},
"sitemapLocation": "sitemap.json" // 小程序sitemap配置文件路径
}

vue

vue2和vue3的区别

  1. 性能优化

    • Vue 3 重写了虚拟 DOM 渲染器,引入了更高效的编译器和静态提升等技术,以提高整体性能。
    • 使用 Proxy 替代 Object.defineProperty 实现数据响应式,从而提供更快速和更直观的响应式系统。
  2. Composition API

    • 在 Vue 3 中引入了 Composition API,利用使用hook的方式引入vue API
  3. Teleport 组件

    • 引入了 Teleport 组件,在代码中可以方便地挂载子组件到任意 DOM 节点上。
  4. Fragment 标签

    • 引入了 Fragment 标签(<template> 上添加 v-ifv-for 等指令),使得模板中可以直接使用无需额外包裹根元素。
  5. Tree-shaking 支持

    • 对于常见打包工具如 webpack、rollup 等有很好支持,在构建时能够进行更全面有效的 Tree-shaking。
  6. ts 支持

    • 在 Vue 3 中使用 TypeScript 结合组合式API可以让开发者享受到更强大、便捷和安全性方面有所提升。

object.defineproperty如何监听基本数据类型,对象属性,数组,为什么无法获取数组变化

  • Object.defineProperty 监听基本数据类型:通过包装对象的方式来对基本数据类型进行监听,因为基本数据类型并不是对象,无法直接使用 Object.defineProperty 来进行属性定义。
  • Object.defineProperty 监听对象属性:给对象的属性添加getter 和 setter 函数,当给定的对象的属性被访问或修改时,可以触发相应的 getter 和 setter 函数。
  • Object.defineProperty 监听数组:使用 Object.defineProperty 监听数组时,会为数组的索引属性设置 getset 方法来实现监听数组元素的读取和修改操作。
  • 无法获取数组变化原因:操作数组时,并不会触发属性的 set 方法。因为修改的实际上是数组的内部结构,而不是修改数组对象的属性,无法被拦截监听到
  • 解决方法:为了监听数组的变化,Vue专门设计用于监听数组响应式操作的方法,如 $set$delete,这些方法可以拦截数组的操作。vue3使用 Proxy 可以更方便地监听数组的变化,因为它可以直接拦截数组的操作,而不需要为数组的每个索引属性都设置 getset 方法

Vue 中使用 data 包裹属性的原因

  1. 数据响应式:将数据定义在 data 对象中可以使这些数据变成响应式的。当数据发生变化时,Vue 能够检测到并自动更新相关的视图,从而实现数据和视图的同步更新。
  2. 组件作用域:将数据放在 data 对象中可以确保这些数据只在当前组件的作用域内有效,避免了数据命名冲突
  3. 方便管理:通过将所有数据都放在 data 对象中,可以更好地组织和管理组件内部的数据,使代码结构更清晰、易读和易维护。
  4. Vue 实例化时合并处理:Vue 在实例化组件时会将 data 对象进行合并处理,确保组件能够正确访问到其中定义的数据。

组件中data是函数不是对象为什么

组件data是函数,vue实例data既可以是对象也可以是函数;

组件中的 data 选项通常是一个函数而不是一个对象。这是因为 Vue 在实例化组件时会将组件的配置选项进行合并处理,如果 data 是一个对象,那么所有该组件实例共享同一份数据对象,可能会导致数据互相影响。

vue生命周期和组件间生命周期顺序

Vue 2 生命周期

Vue 2 的组件生命周期钩子如下:

  1. beforeCreate:实例被创建,数据观测和事件配置尚未开始。
  2. created:实例已完成创建,数据观测和事件配置已完成,$el 还未挂载。
  3. beforeMount:在挂载开始之前被调用,相关的 render 函数首次被调用。
  4. mounted:实例被挂载到 DOM 上,此时可以进行 DOM 操作。
  5. beforeUpdate:数据变化后,组件重新渲染之前被调用。
  6. updated:组件重新渲染后被调用。
  7. beforeDestroy:组件实例销毁之前被调用,可以进行清理工作。
  8. destroyed:组件实例被销毁后调用,再也不可使用。

Vue 3 生命周期

Vue 3 对生命周期钩子的命名方式进行了调整,同时引入了 Composition API。Vue 3 的组件生命周期钩子如下:

  1. setup
  2. beforeMount:同 Vue 2。
  3. mounted:同 Vue 2。
  4. beforeUpdate:同 Vue 2。
  5. updated:同 Vue 2。
  6. beforeUnmount:对应 Vue 2 的 beforeDestroy,组件实例销毁之前调用。
  7. unmounted:对应 Vue 2 的 destroyed,组件实例被销毁后调用。

组件间的生命周期顺序

  1. 父组件
    • beforeCreate
    • created
    • beforeMount
    • 子组件
      • beforeCreate
      • created
      • beforeMount
      • mounted
    • mounted
  2. 父组件更新时
    • beforeUpdate
    • 子组件
      • beforeUpdate
      • updated
    • updated
  3. 父组件销毁时
    • beforeUnmount
    • 子组件
      • beforeUnmount
      • unmounted
    • unmounted

computed和watch的区别和应用场景

computed和watch都是用来监听数据变化并执行相应操作的功能
computed适合用于计算衍生数据,而watch适合用于观察和响应数据的变化,执行比较复杂的逻辑操作。

  1. computed - 计算属性:
    • computed是Vue.js中的计算属性,它会根据依赖的数据动态地生成新的数值。
    • computed属性是基于它的依赖进行缓存的,只有依赖的数据发生改变时,computed属性才会重新计算。
    • computed适合用于基于已有的数据计算出新的数据的场景,如数据的转换、筛选、排序等操作。
1
2
3
4
5
6
// 示例  
computed: {
fullName() {
return this.firstName + ' ' + this.lastName;
}
}
  1. watch - 监听器:
    • watch用于观察和响应Vue实例上的数据变动,当数据变化时执行相应的操作。
    • watch可以监听某个特定数据的变化,执行一些异步或复杂逻辑,并且可以对数据的变化进行深度监听。
    • watch适合用于在数据变化时执行异步操作,或者对数据的变化进行更细粒度的控制。
1
2
3
4
5
6
// 示例  
watch: {
firstName(newVal, oldVal) {
// Do something when firstName changes
}
}

vue如何定义路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';  
import Home from '../components/Home.vue';
import About from '../components/About.vue';

const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: About,
},
];
// 通过createRouter创建路由实例
const router = createRouter({
// 设置路由模式 History 模式
history: createWebHistory(),
// 设置 hash模式
//history: createWebHashHistory(),
routes,
});

export default router;

hash和history 路由模式区别

URL 结构

  • Hash 模式

    • URL 中包含一个#符号,后面跟着路由路径。例如:

      1
      http://example.com/#/about  
    • # 符号后面的部分被称为 “hash”,它不会被浏览器发送到服务器。

  • History 模式

    • URL 中没有#符号,而是使用常规的路由路径。例如:

      1
      http://example.com/about  
    • 这种方式更符合 RESTful 风格的 URL。

浏览器支持

  • Hash 模式
    • 兼容所有浏览器,包括较老的版本,因为它使用了简易的 URL 结构。
  • History 模式
    • 需要现代浏览器的支持(大部分现代浏览器都支持 HTML5 的 History API)。老旧浏览器可能不支持。

服务器部署

  • Hash 模式
    • 不需要特别的服务器配置,因为所有哈希路由都在前端处理,服务器只需提供初始的 HTML 文件。
  • History 模式
    • 需要服务器配置以确保所有路由都指向同一个 HTML 文件。这通常涉及到重定向规则,保证用户访问的 URL 返回的是应用的前端代码。否则会出现404

并发处理

  • Hash 模式
    • 当 URL 改变时,浏览器只是改变了 hash 部分,这不会导致页面重新加载。
  • History 模式
    • URL 的变化会导致浏览器的状态变化,前进、后退按钮的处理也需要依赖于 History API。

v-show和v-if区别

  1. v-show是通过CSS的display属性来控制元素的显示和隐藏,而v-if是通过DOM操作来添加或删除元素来实现的。v-show的初始渲染比v-if快
  2. v-show适用于频繁切换显示和隐藏的元素,因为它只是简单地切换CSS属性,不会引起DOM的重新渲染。而v-if适用于不经常切换的元素,因为它会在DOM中添加或删除元素,会引起DOM的重新渲染。
  3. 如果当前组件在 created 中存在接口的调用,不销毁组件,重新获取,就会获取到错误的数据,通常建议使用v-if而不是v-show。
  4. v-show不支持
  5. 当组件需要在指定时机创建,在指定时机销毁时,需要使用 v-if。而 当组件仅需要创建一次时,则可以使用 v-show。

v-if和v-for的优先级

vue2:v-for的优先级高于v-if。会先确保在循环渲染列表时能够完全渲染,再进行条件判断条件判断

vue3:在 vue3 中 v-if 的优先级要高于 v-for,当v-if的条件为false时,Vue 3会跳过整个v-for循环,不会对列表中的每个元素进行渲染

v-for的key作用,v-for遍历数组中能否使用index作为key

可以,但不推荐,可能会引发一下问题

  1. 当数组数据发生变化时,可能会导致不必要的重新渲染。
  2. 如果数组中的元素位置发生变化,可能会导致错误的DOM元素被更新。
  3. 不利于Vue正确地跟踪每个元素的状态和身份。

vue组件通信方式

父子组件通信

  • Props
    • 父组件通过 props 向子组件传递数据。
  • $emit
    • 子组件通过 $emit 向父组件发送事件和数据。
  • Slots
    • 用于在父组件中定义内容,以便在子组件中使用。
    • 适合需要灵活插槽内容的场景。

兄弟组件通信

  • 通过父组件
    • 兄弟组件通过共同的父组件进行通信,父组件接收子组件的事件并再次传递给另一个子组件。
  • Event Bus
    • 可以创建一个事件总线(Event Bus)用于非父子关系组件之间的通信。

Provide/Inject

  • 用于祖先组件与后代组件之间的通信。
  • 适合深层嵌套组件的场景。

vuex/ pinia

  • 用于跨多个组件的复杂状态管理。
  • 适合中大型应用程序。

vue - router

vue-router 底层原理

Vue Router是Vue.js官方提供的路由管理器,用于实现单页应用(SPA)的路由功能。

它的底层原理主要涉及路由注册、路由匹配及导航等几个关键部分:

  1. 路由注册:
    • Vue Router通过调用Vue.use()方法来注册插件,将其安装到Vue实例上。在安装过程中,它会创建一个Router实例并注入到Vue根实例的options属性中,使得每个组件实例都可以通过this.option**s属性中,使得每个组件实例都可以通过this.router访问到Router实例。
  2. 路由匹配:
    • Vue Router通过创建一个路由映射表来实现路由匹配功能。这个映射表定义了路由路径与相应组件的对应关系。
    • 在创建Router实例时,可以通过配置选项(如routes)来定义映射表。映射表可以使用路由配置对象来表示,每个配置对象包含了路径(path)和组件(component)的对应关系。
    • 在路由匹配过程中,Vue Router会根据当前URL路径匹配映射表中的路径匹配规则,并确定要渲染的组件。
  3. 导航:
    • 导航是指用户在浏览器中输入URL、点击链接或调用编程式导航时触发的路由跳转行为。
    • 在导航时,Vue Router会根据当前URL路径匹配到的路由规则,找到匹配的组件,并调用它们进行渲染。
    • 在导航期间,Vue Router还提供了一些导航守卫(navigation guards)的机制,允许开发人员在导航的不同阶段(如路由切换前后)执行一些特定逻辑。

Vue Router是什么?它解决了什么问题?

  • Vue Router是Vue官方提供的路由管理器,用于实现单页应用的路由功能。它通过提供路由映射、路由切换、导航守卫等机制,帮助开发者实现页面之间的跳转和组件的动态渲染,从而构建SPA应用。

Vue Router中的路由模式有哪些?它们有什么区别?

  • Vue Router有两种常见的路由模式:hash模式和history模式。
  • 在hash模式下,URL中的哈希值(#)用于标识不同的路由,这种模式兼容性好,但URL中会有一个#号。
  • 在history模式下,使用浏览器的原生History API来管理路由,URL更加友好,但在不支持HTML5 History API的浏览器中需要做额外处理。

Vue Router的路由导航守卫有哪些?它们的作用是什么?

  • Vue Router提供了三个导航守卫:beforeEach、beforeRouteEnter和beforeRouteLeave。
  • beforeEach用于全局前置守卫,可以在路由切换之前执行一些逻辑操作,如验证登录状态。
  • beforeRouteEnter在路由渲染组件之前执行,可以访问组件实例,但无法获取组件的this对象。
  • beforeRouteLeave在路由离开组件之前执行,可以阻止离开或询问用户是否确定离开。

如何实现动态路由?

  • 在Vue Router中,可以通过使用动态路径参数来实现动态路由。例如,在路由配置中,使用冒号(:)来定义动态参数。
  • 例如:{ path: '/user/:id', component: User },其中:id是一个动态参数,可以匹配/user/1、/user/2等路径。

如何在Vue组件中进行路由跳转?

  • 在Vue组件中,可以使用this.$router.push()方法进行路由跳转。
  • 例如:this.$router.push('/home'),跳转到名为home的路由。

Vue Router中怎样实现路由懒加载(按需加载)?

  • Vue Router可以通过使用Webpack的动态import语法实现路由懒加载。
  • 通过在路由配置中使用component: () => import('@/views/Home')来实现按需加载。

vuex

vuex状态持久化解决方案

  1. 利用本地存储
  2. 安装一个vuex的插件 vuex-persistedstate

vuex的设计与实现

它通过存储、管理和同步应用的所有组件的状态来管理数据。

Vuex的设计主要包括以下几个核心概念:

  1. State(状态)
  2. Mutations(变化)
  3. Actions(操作)
  4. Getters(获取器)

pinia

创建Pinia实例 - Vue.js应用程序的入口文件(通常是main.js)中,创建一个Pinia实例并将其添加到Vue应用程序中

1
2
3
4
5
6
7
8
9
import { createApp } from 'vue'  
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.mount('#app')

定义状态 - 可以使用defineStore函数定义状态。创建一个新的.js文件,例如counter.js,并在其中定义一个计数器状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ./counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
},
decrement() {
this.count--
}
}
})

在组件中使用状态 - 可以使用useStore函数来访问和使用Pinia中定义的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>  
<div>
<p>Count: {{ counter.count }}</p>
<button @click="counter.increment()">Increment</button>
<button @click="counter.decrement()">Decrement</button>
</div>
</template>

<script>
import { useCounterStore } from './counter.js'

export default {
setup() {
const counter = useCounterStore()

return {
counter
}
}
}
</script>

双向数据绑定原理

在 Vue 2 中,双向数据绑定是通过 Object.defineProperty() 方法对数据对象的属性进行劫持实现的。当数据对象被创建时,Vue 2 会递归地将对象的属性转换为 getter 和 setter,并在属性被访问或修改时触发更新

在 Vue 3 中,采用了 Proxy 对象替代了 Object.defineProperty()。

v-model - vue2与vue3分别作用在普通元素和组件元素的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 当作用于普通元素时,v-model在vue2与vue3没有什么区别
<inpuy v-model="a" />
// 等价于
<input :value="a" @input="(e)=>{a=e.target.value}"/>

// 当v-model 用于自定义组件键上时
// vue2
<son v-model="a"/>
//等价于
<son :value="a" @input="(a)=>{a=e}"/>

// vue3
<son v-model="a"/>
//等价于
<son :modelValue="a" @update:modelValue="(e)=>{a=e}"/>
// 子组件可以直接用defindprops接受modelValue
// 全称为
<son v-model:modelValue="a"/>

nextTick作用

nextTick 是 Vue 中的一个异步方法,它的作用是在 DOM 更新之后执行回调函数。nextTick 方法接受一个回调函数作为参数,并在 DOM 更新完成后执行该回调函数。因为在页面渲染后执行,可以在vue中获取到最新的DOM元素

虚拟Dom

虚拟 DOM 是一个轻量级的 JavaScript 对象树,它是对真实 DOM 的抽象表示。当数据发生变化时,Vue 会通过比较新旧虚拟 DOM 树的差异,然后只更新需要变化的部分

虚拟Dom怎么生成

Vue 的虚拟 DOM 是通过模板编译器(Template Compiler)生成的。模板编译器会将模板解析成抽象语法树(AST),然后将 AST 转换为渲染函数(Render Function),最终生成虚拟 DOM。

前端大量数据展示

常见解决策略和技术

  1. 分页
  2. 虚拟滚动
  3. 按需加载
  4. 数据压缩
  5. 服务端过滤和排序
  6. 前端缓存
  7. 数据预取
  8. web workers
  9. 数据懒加载
  10. DOM优化

vue 项目SPA单页面 SEO优化

单页面应用打包后会只有一个index.html。vue通过实例绑定在index.html的app根组件。通过路由的方式渲染不同内容。搜索引擎是拿不到详细信息。导致vue单页面应用很难在SEO上下很大功夫

  1. SSR服务端渲染 - vue的ssr框架nuxt
  2. 预渲染 - Vue CLI 插件 prerender-spa-plugin 来预渲染应用程序的静态 HTML 页面。这个插件会在构建过程中访问应用程序的每个路由,并将生成的 HTML 文件保存在 dist 目录下,使得搜索引擎能够直接查看到页面内容。
  3. 设置页面元信息: 在 Vue 组件中使用 vue-meta 插件设置页面的 title、meta description 等元信息,以便搜索引擎正确抓取并展示页面内容
  4. 设置站点地图

React

react理解 和 特征

理解

  • react是构建用户界面的js库,提供了UI层面的解决方案
  • 遵循组件设计,声明式编程,函数式编程概念
  • 使用虚拟DOM来操纵实际DOM,减少性能开支
  • react将界面分割成独立小块,每一块为一个组件,通过组件嵌套组合构成整个页面

特性

  • JSX语法
  • 单项数据流
  • 虚拟DOM
  • 声明式编程,函数式编程
  • 组件化

MVC和MVVM

MVC是一种经典的软件架构模式,将应用程序划分为三个组件:模型(Model)、视图(View)和控制器(Controller)。其中,模型负责存储和处理数据,视图负责展示数据,而控制器负责处理用户交互,更新模型和视图之间的关系。
数据流的通常方向是单向的,即控制器更新模型的数据,并将更新后的数据传递给视图进行展示。

用户交互 -> 控制器 -> 视图 & 模型 (模型的改变并不会自动触发视图更改,当模型数据发生变化后,如果需要通知控制器更新视图)

MVVM是一种衍生于MVC的软件架构模式,将应用程序划分为三个组件:模型(Model)、视图(View)和视图模型(ViewModel)。其中,视图模型负责管理视图所需要的所有数据和行为,并通过数据绑定将模型中的数据绑定到视图中。
在MVVM中,数据流的通常方向是双向的,即视图模型可以将用户的输入直接更新到模型中,并且模型中的数据的变化也可以自动更新到视图上。

用户交互 -> 控制器 -> 模型 -> 视图 (更改了模型会自动同步到视图)

Flux是什么

什么是jsx

一种语言的扩展,简化了代码的开发。它利用了js语法和html标签相结合的语法。

1
2
3
4
5
6
7
8
9
10
11
12
// 使用react createElement创建元素
import React from 'react'
const reactElement = React.createElement(
h3,
{className:'name_vale'},
'Element Value'
)

//jsx 语法
const reactElement = (
<h3 className='name_value'>Element Value</h3>
)

react引入css方式

  1. 使用普通的 CSS 文件:
    • 创建一个 CSS 文件,例如 styles.css
    • 在需要引入 CSS 的组件中使用 import 语句引入 CSS 文件:import './styles.css';
    • React 会将该 CSS 文件添加到页面中,并应用于引入它的组件。
  2. 使用 CSS 模块化:
    • 在需要引入 CSS 的组件中创建一个 CSS 文件,例如 styles.module.css
    • 在组件中使用 import 语句引入 CSS 文件:import styles from './styles.module.css';
    • 在 JSX 中使用引入的 CSS 类名:<div className={styles.container}>Hello, React!</div>
    • React 会为每个组件生成一个独特的 CSS 类名,以确保样式的隔离性和可重用性。
  3. 使用 CSS-in-JS 库:
    • CSS-in-JS 是一种将 CSS 写入 JavaScript 文件的方法,通过 JavaScript 中的对象来定义样式。
    • 有许多流行的 CSS-in-JS 库可供选择,例如 Styled Components、Emotion、Material-UI 等。
    • 使用选定的 CSS-in-JS 库,将样式与组件逻辑相关联,然后将样式应用到组件

怎么实现react组件间过渡动画

  1. css代码:例如 transition 属性来处理渐变过渡或 transform 属性来处理平移过渡。
  2. 使用 React 动画库:如 React Transition Group、React Spring、Framer Motion

React 工作原理

React会创建一个虚拟DOM。 当一个组件的状态改变时,React首先会通过Diff算法来标记虚拟DOM中的改变,第二步是调节,会用diff的结构来更新DOM

react 事件机制

在React中,事件机制与原生的DOM事件机制有所不同。React事件系统是基于合成事件(SyntheticEvent)的,它是React封装的一种跨浏览器的事件系统,提供了一套统一的事件接口以解决不同浏览器之间的兼容性问题。

React事件机制的一些特点包括:

  1. 事件绑定:React使用驼峰命名的事件名称来绑定事件处理函数,例如onClickonChange等。
  2. 合成事件对象:React封装的合成事件对象SyntheticEvent对原生事件对象进行了封装,提供了与原生事件相同的属性和方法,同时提供了跨浏览器的兼容性。
  3. 事件代理:React通过事件代理的方式处理事件,将所有事件监听器注册在顶层容器上,然后通过事件冒泡机制来分发事件,这样可以提高性能并减少内存占用。
  4. 事件处理函数:事件处理函数在React中通常作为组件的方法来定义,需要谨慎处理函数内部的this指向,可以使用箭头函数或者在构造函数中绑定this来避免出现错误。
  5. 阻止事件冒泡和默认行为:可以通过合成事件对象的stopPropagation()方法来阻止事件冒泡,通过preventDefault()方法来阻止事件的默认行为。

React事件机制相比原生浏览器事件有更好的跨浏览器兼容性,提供了更统一的事件接口,并且采用事件代理的方式来提高性能。

react 绑定事件的方式有哪些,有什么区别

  1. 直接在JSX中使用箭头函数绑定事件处理函数:
    这种方式会导致每次渲染时都会创建一个新的函数,可能会影响性能。
1
<button onClick={() => this.handleClick()}>Click me</button>  
  1. 在构造函数中绑定this并将方法作为实例方法:
    通过在构造函数中绑定this,确保事件处理函数中的this指向组件实例,避免每次渲染都创建新的函数。
1
2
3
4
5
6
7
8
9
10
constructor(props) {  
super(props);
this.handleClick = this.handleClick.bind(this);
}
function handleClick(){
//click
}
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
  1. 在类的属性上使用实验性语法public class fields绑定实例方法:
    使用箭头函数作为类的属性可以直接绑定this,不需要在构造函数中单独绑定。
1
2
3
4
5
6
7
8
9
function fn(){
handleClick = () => {
// handle click
}

render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
  1. 使用bind方法绑定this并传递参数:
    可以使用bind方法将参数传递给事件处理函数。
1
2
3
4
5
6
7
handleClick(param) {  
// handle click with param
}

render() {
return <button onClick={this.handleClick.bind(this, param)}>Click me</button>;
}

React有什么优点

  • 模版引入JSX语法,使得组件的代码更加可读,也更容易看懂组件布局和组件间的引用
  • 支持服务端渲染,对SEO和性能进行改进
  • React 只关注View层,所以可以和其他任何框架一起使用

react中类组件和函数组件的理解

类组件

  1. 定义方式
  • 使用 ES6 的类语法定义,需继承自 React.Component
  • 通常具有生命周期方法(如 componentDidMountcomponentDidUpdatecomponentWillUnmount 等)。
  • 需要使用 this 关键字来访问和管理组件的状态和方法
  • 类组件调用方式需要对组件进行实例化,调用render方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyClassComponent extends React.Component {  
constructor(props) {
super(props);
this.state = { count: 0 };
}

increment = () => {
this.setState({ count: this.state.count + 1 });
};

render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
  1. 状态管理
    • 类组件使用 state 对象来管理组件的状态,并通过 this.setState 方法来更新状态。
  2. 生命周期方法
    • 类组件有完整的生命周期方法,可以在特定的阶段执行某些代码(如在组件挂载、更新和卸载时)。

函数式组件

  1. 定义方式
  • 使用 JavaScript 函数定义,通常是无状态的。
  • 从 React 16.8 开始,函数组件可以使用 Hooks(如 useStateuseEffect)来管理状态和副作用。
  • 不需要使用 this 关键字
  • 函数式组件调用即执行函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { useState } from 'react';  

const MyFunctionComponent = () => {
const [count, setCount] = useState(0);

const increment = () => {
setCount(count + 1);
};

return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
  1. 状态管理
    • 函数组件使用 useState Hook 来管理状态,更加灵活和简洁。
  2. 生命周期方法
    • 函数组件没有生命周期方法,但可以使用 useEffect Hook 来实现相同的效果,基于依赖数组控制副作用的执行时机。
区别 函数组件 类组件
是否有this 没有
是否有生命周期 没有
是否有状态state 没有

组件生命周期

React组件生命周期可以分为3个阶段 :(初始化 -)挂载 - 更新 - 销毁( - 错误捕获)

组件实例化并插入DOM的过程
这个阶段包括以下生命周期:

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

挂载阶段 - 详细

  1. constructor()
    • 构造函数,在组件创建时调用,用于初始化状态和绑定事件处理函数
    • 在构造函数中调用super(props) 来调用父类的构造函数,并将props传递给父类
    • 初始化组件的状态可以通过 this,stat = { } 实现
  2. static getDerivedStateFromProps()
    • 当组件接收新的porps时调用,在渲染之前执行。
    • 用于根据新的props计算并更新组件的状态
    • 应返回一个对象来更新状态,或者返回null来表示不需要更新状态
  3. render()
    • 必须得生命周期方法,用于渲染组件模板
    • 返回一个React元素,秒数组件的输出
    • 不能再这个方法中修改组件的状态或这行副作用操作,会导致死循环
  4. componentDidMount()
    • 在组件初始化渲染之后立即调用
    • 通常执行一些初始化操作,例如网络请求,获取初始数据
    • 这个方法只会在组件生命周期中调用一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React, {Component} from 'react'

class MyComponent extends Component {
console.log('this')
constructor(props){
console.log('constructor')
super(props)
this.state = {count:0}
}

static getDerivedStateFromProps(props, state){
console.log(getDerivedStateFromProps)
return null
}
// 组件挂载后执行的代码
conponentDidMount(){
console.log('conponentDidMount')
}

render(){
console.log('render')
return(
<>
<div>挂载阶段生命周期</div>
</>
)
}
}

函数组件代替

1
2
3
4
5
6
7
8
9
10
import React, { useEffect } from 'react';  

const MyFunctionComponent = ({ someValue }) => {
useEffect(() => {
// 组件更新后执行的代码
console.log('Component updated');
}, [someValue]); // 将某个值作为依赖,依赖项变化时执行

return <div>Hello, World!</div>;
};

组件props或state发生变化,导致组件重新渲染的过程
这个阶段包括以下生命周期:

  • static getDerivedStateFromProps()
  • shouldComponentUpdata()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

更新阶段生命周期 - 详细

  1. shouldComponentUpdate(nextProps, nextState)
    • 在组件接收到新的props或者state时调用,在渲染之前执行
    • 可以根据最新的props或者state决定是否需要重新渲染组件
    • 默认返回true,表示组件将会重新渲染,可以通过返回false来阻止重新渲染
  2. render()
    • 必须得生命周期方法,用于渲染组件模板
    • 返回一个React元素,秒数组件的输出
    • 不能再这个方法中修改组件的状态或这行副作用操作,会导致死循环
  3. getSnapshotBeforeUpdate(prevProps, prevState)
    • 在最新的渲染输出被提交到DOM之前调用
    • 用于获取更新前的DOM快照或执行一些DOM操作
    • 返回值将作为componentDidUpdate方法的第三个参数
  4. conponentDidUpdate(prevProps, prevState, snapshot)
    • 在更新组件更新完成后立即调用
    • 通常用于执行一些副作用操作,比如更新后的DOM操作,网络请求
    • 可以访问到更新之前的props,state。以及getSnapshotBeforeUpdate返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React, {Component} from 'react'

class MyComponent extends Component {
constructor(props){
console.log('constructor')
super(props)
this.state = {count:0}
}

shouldComponentUpdate(nextProps, nextState){
console.log('shouldComponentUpdate')
return true;
}

getSnapshotBeforeUpdate(prevProps, prevState){
console.log('getSnapshotBeforeUpdate')
return null;
}
// 组件更新后执行的代码
componentDidUpdate(prevProps, prevState, snapshot){
console.log('componentUpdate')
}

render(){
return(
<>
<div>更新生命周期</div>
<button onClick={()=>this.state.count += 1}>修改state</button>
</>
)
}

}

函数组件代替

1
2
3
4
5
6
7
8
9
10
import React, { useEffect } from 'react';  

const MyFunctionComponent = ({ someValue }) => {
useEffect(() => {
// 组件更新后执行的代码
console.log('Component updated');
}, [someValue]); // 将某个值作为依赖,依赖项变化时执行

return <div>Hello, World!</div>;
};

组件从DOM中移除的过程
这个阶段包括以下生命周期:

  • componentWillUnmount()

卸载生命周期 - 详细

  1. componentWillUnmount()
    • 在组件即将被销毁并从DOM中移除之前调用
    • 用于执行一些清理工作,比如取消订阅,清除定时器,监听器等
    • 在这个方法中不能调用setState,因为组件即将被销毁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, {Component} from 'react'

class MyComponent extends Component {
constructor(props){
console.log(constructor)
super(props);
this.state = {count:0}
}
// 组件卸载前执行的代码
componentWillUnnmount(){
console.log('ComponentWillUnmount')
}

render(){
return(
<>
<div>销毁组件生命周期</div>
</>
)
}
}

函数式组件代替

1
2
3
4
5
6
7
8
9
10
11
12
import React, { useEffect } from 'react';  

const MyFunctionComponent = () => {
useEffect(() => {
// 清理函数,在组件卸载前执行
return () => {
console.log('Component will unmount');
};
}, []); // 空数组将会在每次渲染后执行

return <div>Hello, World!</div>;
};
1
2
3
4
5
6
7
8
9
10
class MyClassComponent extends React.Component {  
componentDidCatch(error, info) {
// 处理错误
console.error("Error caught:", error);
}

render() {
return <div>Hello, World!</div>;
}
}

函数组件本身不能实现错误边界,但可以利用类组件来包装。

如何跟踪功能组件的卸载

在函数式组件中,useEffect可以返回一个清理函数,可以在清理函数中定义要清楚理的资源。在DOM组件删除之前调用,清理设置,防止内存泄漏。例如计时器,订阅,监听事件

react响应式原理

React的响应式原理是基于虚拟DOM和diffing算法,通过组件化、单向数据流等特性实现了响应式更新用户界面的能力。

  1. Virtual DOM:React使用虚拟DOM(Virtual DOM)来描述UI的状态,通过对比虚拟DOM的前后状态的差异,能够高效地更新真实DOM。通过创建虚拟DOM,React可以在内存中快速构建UI结构,减少了直接操作真实DOM带来的性能开销。
  2. diffing算法:React使用一种称为“reconciliation”(协调)的算法来比较前后两个版本的虚拟DOM树的差异,并且只更新真实DOM中发生变化的部分。这样可以避免不必要的DOM操作,提高页面性能。
  3. 组件化开发:React将UI拆分为组件,每个组件都有自己的状态(state)和属性(props)。当组件的状态或属性发生变化时,React会自动重新渲染该组件,从而实现了响应式更新。
  4. 单向数据流:React采用单向数据流的设计思想,父组件通过props向子组件传递数据,子组件通过回调函数将数据传递回父组件,使得数据变化时只需更新相关组件,避免了数据流混乱和难以追踪的问题。

render函数的理解和渲染过程

React的render函数是React组件的一个方法,它负责渲染组件的UI,并返回一个React元素(element)或一组元素。每次组件的状态或属性发生变化时,React会重新调用render函数来生成新的虚拟DOM树,并且通过diffing算法来对比前后两个虚拟DOM树的差异。

React的render函数的渲染过程:

  1. 组件的render函数被调用:当React组件需要重新渲染时,React会调用组件的render函数。render函数应该返回一个React元素或一组元素。
  2. 创建虚拟DOM(Virtual DOM):render函数内部会创建一个虚拟DOM树,用来描述UI的状态。
  3. diffing算法比较前后两个虚拟DOM的差异:React会将前后两个虚拟DOM进行比较,找出两个虚拟DOM之间的差异,并且记录下这些差异。
  4. 更新真实DOM:根据记录的差异,React会根据需要的操作来更新真实DOM。这些操作可能包括插入新的节点、移动节点、删除节点等。
  5. 提交更新:最后,React会将变化后的DOM提交给浏览器进行渲染。

hooks - useMemo和useCallback 区别

  1. useMemo:

    • useMemo用于优化性能,可以缓存计算结果,避免重复计算。

    • 语法:const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

    • useMemo接受一个函数作为第一个参数,这个函数返回一个值,还接受一个依赖数组作为第二个参数。只有依赖数组中的某些值发生变化时,才会重新计算值,并且把值缓存起来。

    • 适用场景:当有一些计算比较昂贵,但又不希望每次渲染都重新计算时,可以使用useMemo。

  2. useCallback:

    • useCallback用于优化性能,可以缓存一个函数,避免在每次渲染时都创建新的函数。

    • 语法:const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);

    • useCallback接受一个函数作为第一个参数,还接受一个依赖数组作为第二个参数。只有依赖数组中的某些值发生变化时,才会返回同一个函数实例,否则会返回新的函数实例。

    • 适用场景:当需要把一个回调函数传递给子组件时,并且该回调函数依赖了一些值,为了避免每次渲染都创建新的回调函数,可以使用useCallback。

  3. 总结:

    • useMemo用于缓存计算结果,避免重复计算,适用于缓存变量或复杂的计算操作。
    • useCallback用于缓存函数,避免在每次渲染时都创建新的函数,适用于传递给子组件的回调函数。

hooks - useState和useRef 区别

  1. useState:

    • useState用于在函数组件中保存和更新状态。

    • 语法:const [state, setState] = useState(initialState);

    • useState接受一个初始状态值作为参数,并返回一个数组,包含当前的状态值和一个用于更新状态值的函数。

    • 每次调用函数组件时,useState都会返回一个新的state和setState,通过调用setState可以改变状态值,并触发组件的重新渲染。

    • 适用场景:适用于保存和更新与组件渲染相关的状态值,例如表单输入的值、组件的可见性等。

  2. useRef:

    • useRef用于在函数组件中保存和访问变量,类似于在类组件中使用实例变量。

    • 语法:const refContainer = useRef(initialValue);

    • useRef接受一个初始值作为参数,并返回一个ref对象,该对象的.current属性指向初始值。

    • 不同于useState,在调用ref对象的.current属性时,不会触发组件的重新渲染。

    • 适用场景:适用于保存和访问需要在组件的多个渲染之间保持稳定的值,例如定时器的引用、获取DOM元素的引用等。

  3. 总结:

    • useState用于保存和更新与组件渲染相关的状态值,通过setState触发重新渲染。

    • useRef用于保存和访问需要在组件的多个渲染之间保持稳定的值,不会触发重新渲染。

useEffect依赖项只传一个依赖项或空数组或不传依赖项的区别

  1. 传递一个依赖项:
1
2
3
useEffect(() => {  
// effect函数,副作用操作
}, [dependency]);
  • 当依赖项发生变化时,effect函数会被调用,即只有在依赖项变化时,effect会执行。
  • 这种情况下,effect函数只会在依赖项发生变化时执行一次。
  1. 传递空数组:
1
2
3
useEffect(() => {  
// effect函数,副作用操作
}, []);
  • 当传递空数组作为依赖项时,effect函数只会在组件挂载时执行一次,类似于componentDidMount生命周期方法。
  • 这种情况下,effect函数只会在初始化渲染时执行一次。
  1. 不传递依赖项:
1
2
3
useEffect(() => {  
// effect函数,副作用操作
});
  • 当不传递依赖项时,effect函数会在每次组件渲染时都执行。
  • 这种情况下,无论什么状态发生变化,effect函数都会执行。

useEffect的return 函数在依赖项不同的情况下,有何区别

  1. 当依赖项发生改变:
1
2
3
4
5
6
7
useEffect(() => {  
// effect函数,副作用操作

return () => {
// 返回函数,用于清理副作用操作
};
}, [dependency]);
  • 当依赖项发生改变时,先执行之前返回的函数,然后再执行更新后的effect函数。
  • 这样做的好处是,在依赖项发生改变后,先执行清理函数,再重新进行副作用操作。
  1. 当依赖项为空数组:
1
2
3
4
5
6
7
useEffect(() => {  
// effect函数,副作用操作

return () => {
// 返回函数,用于清理副作用操作
};
}, []);
  • 当依赖项为空数组时,仅在组件被销毁时才会执行返回的函数,类似于componentWillUnmount生命周期方法。
  • 这种情况下,返回的函数只会在组件卸载时执行一次。
  1. 未传递依赖项:
1
2
3
4
5
6
7
useEffect(() => {  
// effect函数,副作用操作

return () => {
// 返回函数,用于清理副作用操作
};
});
  • 当没有传递依赖项时,每次组件重新渲染时都会先执行之前返回的函数,然后再执行更新后的effect函数。
  • 这表示返回的函数会在每次effect函数调用之前执行。

React Hooks底层原理,如何实现状态更新

React Hooks是React 16.8引入的一项新特性,使函数组件能够拥有类似于类组件中state和生命周期方法的能力。

  1. 类组件:

    • 在类组件中,状态更新是通过调度器(Scheduler)和渲染器(Renderer)之间的交互完成的。调度器负责管理何时执行或中断更新,并决定何时运行副作用。调度器会根据一定的优先级策略,安排哪些更新需要进行,以确保性能和优化。

    • 调度器会触发进行协调阶段,比较新旧虚拟DOM,找出需要更新的部分,然后触发重新渲染阶段。在重新渲染阶段,渲染器会负责将更新后的虚拟DOM转换为实际的DOM,并更新到页面上。

  2. 函数组件(使用Hooks):

    • 在函数组件中,React使用Fiber架构来进行组件的协调和渲染。Fiber是一种链表数据结构,用于表示组件的工作单元以及记录组件的状态、副作用和子节点。每个组件对应一个Fiber节点,通过这些Fiber节点组成链表,来进行高效的更新和调度。

    • 当触发状态更新时,React以类似协程的方式对Fiber链表进行遍历和更新,以保证更新的优先级和顺序。React会通过Fiber节点的优先级处理,决定哪些更新要先执行,哪些更新可以延后执行,以提高用户界面的响应性和性能。

怎么实现全局数据存储?

要实现全局数据存储,可以使用一些常用的方法,如React的Context API或第三方库( Redux,MobX)

  1. 使用React的Context API:
    • React的Context API允许我们创建一个全局的上下文对象,用于在组件树中共享数据。通过在Provider组件中提供值,可以使所有后代组件都能够访问到这些数据。
    • 在创建全局数据存储时,可以使用Context API创建一个Context对象,并将需要共享的数据放在Provider组件的value属性中。然后,使用Consumer或useContext钩子在组件中获取该数据。
  2. 使用Redux:
    • Redux是一个流行的状态管理库,通过单一的状态树(Store)来管理整个应用的状态。通过Redux的createStore函数创建一个全局Store,然后在需要共享数据的组件中使用Provider组件将其包裹。
    • 使用Redux Persist可以将Redux中的数据持久化存储到本地存储中,例如LocalStorage或AsyncStorage。Redux Persist提供了一个Store增强器,使得数据可以在刷新页面或重新加载应用后被还原。

vuex一样,redux中的状态会在刷新浏览器后状态又恢复到初始状态,有些数据想在浏览器刷新后仍然是在最新的状态,不会丢失,就需要借助一些插件实现。

父组件渲染如何让子组件不渲染,类组件和hooks方法

类组件

  • 条件渲染:在父组件的 render 方法中使用条件语句,根据特定条件决定是否渲染子组件。例如,可以使用 if 语句或三元表达式来控制子组件的渲染。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class ParentComponent extends React.Component {  
    render() {
    const shouldRenderChild = // 根据某个条件判断是否渲染子组件
    return (
    <div>
    {shouldRenderChild && <ChildComponent />}
    </div>
    );
    }
    }
  • 通过 props 控制:将一个布尔类型的 prop 传递给子组件,根据该 prop 的值决定子组件是否进行渲染。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class ParentComponent extends React.Component {  
    render() {
    const shouldRenderChild = // 根据某个条件判断是否渲染子组件
    return (
    <div>
    <ChildComponent shouldRender={shouldRenderChild} />
    </div>
    );
    }
    }

    class ChildComponent extends React.Component {
    render() {
    if (!this.props.shouldRender) {
    return null; // 不渲染子组件
    }
    return (
    // 子组件的渲染内容
    );
    }
    }

函数组件

  • 条件渲染:在函数组件中使用条件语句,根据特定条件决定是否返回子组件的 JSX。例如,可以使用 if 语句或三元表达式来控制子组件的渲染。
1
2
3
4
5
6
7
8
function ParentComponent() {  
const shouldRenderChild = // 根据某个条件判断是否渲染子组件
return (
<div>
{shouldRenderChild && <ChildComponent />}
</div>
);
}
  • 通过 props 控制:将一个布尔类型的 prop 传递给子组件,根据该 prop 的值决定是否返回子组件的 JSX。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function ParentComponent() {  
const shouldRenderChild = // 根据某个条件判断是否渲染子组件
return (
<div>
<ChildComponent shouldRender={shouldRenderChild} />
</div>
);
}

function ChildComponent({ shouldRender }) {
if (!shouldRender) {
return null; // 不渲染子组件
}
return (
// 子组件的渲染内容
);
}

state( 状态)和props( 属性)区别

一个组件状态可以由两个参数决定:内部数据state,外部数据props

state:是一种数据结构,用于组件挂载时所需数据的默认值。由内部定义,组件初始化时定义,需要通过setState进行修改
props:是组件的配置。一般由父组件或兄弟组件进行传递,在组件内部是不能被修改的,

super() 和 super(props)

super()super(props)都是用于在子类的构造函数中调用父类的构造函数。

  1. super(): 在子类的构造函数中调用super(),表示调用父类的构造函数,并将父类的属性和方法继承到子类中。这样子类可以使用父类的属性和方法,实现代码的复用。
  2. super(props): super(props)在子类的构造函数中表示调用父类的构造函数,并将子类的props传递给父类。这在使用props的情况下非常有用,可以确保在子类中使用props之前,父类的构造函数已经接收到了相应的props。

在React中,当子类继承自React.Component时,通常会在子类的构造函数中调用super(props),以确保正确地初始化父类。这样可以在子类中使用this.props来访问传递给组件的属性。

什么是props透传

props透传指的是通过多层嵌套的组件传递props的一个过程。为了方便开发和维护。一层一层传递props,不现实。
所以可以使用其他模式:使用上下文(Context/ useContext) 或状态管理库(Redux) )来代替porps透传。这种些方法不需要每个组件传递props

什么是React 上下文 - contest

react contest 是一项功能,它提供一种在组件数中传递数据的方法,而无需再每一层手动传递。它允许创建一个全局状态。被包裹的任何子孙组件都能访问该状态

Context API 由三部分组成:

  1. createContext:创建上下文
  2. Context.provider 该组件用于为上下文提供值,被该标签包裹的子组件可以获取value的值
  3. Context.Consumer 或useContext hooks: 获取从上下文中传递的值。

渲染数组元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Reacr from 'reacr'

export const listElement:React.FC = ()=>{
const list = [
'js',
'py',
'ts'
]

return(
<div>
<ul> {list.map((item, index, array)=><li>{item}</li> )}</ul>
</div>
)
}

为什么使用map() 时需要key

可以帮助React确定元素的更改,添加,或移动等操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Reacr from 'reacr'

export const listElement:React.FC = ()=>{
const list = {
{
id: 1,
lang: 'js'
},
{
id: 2,
lang: 'py'
},
{
id: 3,
lang: 'ts'
}
}

return(
<div>
<ul> {list.map((item, index, array)=>(
<li key={`${item.id}`}>{item.lang}</li> )}</ul>
</div>
)
}

受控组件和非受控组件

受控组件

受控组件是指在组件的状态与表单元素的值之间存在严格的联系的组件。

  • 表单元素的值由组件状态控制。
  • 通过事件处理程序(如 onChange)更新状态。
  • 组件对输入值的控制使得表单具有更强的可预测性和可控性。

非受控组件

非受控组件是指组件的状态不通过 React 的状态来管理,而是使用 React 的 ref 来直接访问 DOM 元素的值。

  • 表单元素的值直接在 DOM 中存储,而不是节点状态。
  • 可以通过 ref 来访问和获取输入值。
  • 更适合于表单逻辑较简单的场景或不需要对每次输入进行实时验证的情况。

如何实现移除定时器

在 React 中,移除定时器通常需要在组件卸载时进行操作。在类组件中你可以利用 React 提供的生命周期方法 componentWillUnmount 来实现移除定时器的功能。在函数组件中可以利用useEffect hooks 的回调函数进行移除

react 组件通信

父子组件通信

  • Props:
    • 父组件通过 props 向子组件传递数据。
  • 回调函数
    • 子组件通过调用父组件传递的回调函数来向父组件传递数据。

兄弟组件通信

  • 通过父组件
    • 兄弟组件通过共同的父组件进行通信,即父组件管理兄弟组件的状态。
  • Context API
    • 用于跨组件传递数据,而不需要通过每一级组件传递 props。

Event Emitters

  • 可以使用事件发射器模式来实现组件间的通信,尽管这在 React 中不太常见,通常还是倾向于使用 React 的内建机制。

Refs

  • 通过 ref 可以直接访问子组件的实例方法或属性,但这种方式不太符合 React 的数据流理念,需谨慎使用。

React hooks (自定义 hooks)

  • 创建自定义 hooks 以共享逻辑和状态。

hooks - useState有什么特点

useState 返回一个状态值和一个更新函数

在初始化渲染期间,返回的状态与初始匹配。使用更新函数进行状态更新时,采用新状态值作为参数,进行异步更新,重新渲染不会触发useState API。更新函数还可以接受函数返回值的方式。

react18,将所有事件都进行批处理,即多次setState会被合并为1次执行,提高了性能,在数据层,将多个状态更新合并成一次处理(在视图层,将多次渲染合并成一次渲染)

hooks - useEffect有什么特点

useEffect钩子允许在功能组件中执行副作用,,例如数据获取、订阅、手动操作 DOM 等。它使你能够在函数组件中处理这些副作用,同时保留组件的声明式架构。

  • 第一个参数是一个函数,执行副作用的代码。
  • 第二个参数是一个数组,指定了依赖项。只有当依赖项发生变化时,副作用才会重新执行。

作用的执行时机

useEffect 在组件的生命周期中执行的时机如下:

  • 组件挂载时:当组件首次渲染到 UI 上时。
  • 依赖项变化时:如果指定了依赖项数组,当数组中的某个值发生变化时,useEffect 会重新执行。
  • 组件卸载时:如果提供了清理函数,它会在组件卸载或依赖项变化时调用。

依赖数组

当依赖数组为空时,useEffect 只在组件挂载时执行一次,类似于 class 组件的 componentDidMount
如果依赖数组中包含状态或 props,副作用将在组件挂载和依赖项变化时执行。
如果不提供依赖数组,useEffect 将在每次渲染后执行,这可能导致性能问题。

hooks - useMemo用途?如何工作?

用于缓存计算结构。useMemo仅在任何依赖项的值发生变化时,才会重新计算记忆值。这种优化可以帮助一些复杂,消耗性能较高的计算
第一个参数,接受一个执行计算的回调
第二个参数,接受依赖关系数组,只有至少一个依赖关系发生变化时,才会触发重新执行

hooks - useCallback用途?如何工作?

hooks - useMemo和useCallback有什么区别

  1. useMemo: 用于缓存计算结果,而useCallback用于缓存函数本身
  2. useMemo: 如果依赖项未更改,则缓存计算值并在后续渲染时返回该值
  3. useCallback:缓存函数本身并返回相同实例,出非依赖项更改

hooks - useContext用途? 如何工作?

React应用程序中,数据是从父组件到子组件传递。如果较深层的组件需要使用到该porps,就会嵌套很多层props,过程过于繁琐。而Context上下文,提供了在组件之间共享数据的方法,无需明确的传递props到每一层。
当上下文值发生改变时,useContext组件总是会重新渲染。

hooks - useRef 用途?作用?

useRef返回一个可以修改的ref对象,即一个属性。该对象当前值有传递的参数进行初始化。返回对象将在整个组件生命周期中持续存在。

什么是React.memo()

React.memo() 是一个高阶组件。如果组件总是不变,渲染相同内容。可以在某些情况下将其封装在React.mome() 中调用,避免重新渲染以提高性能。如果React.memo() 组件中使用了useState,useReducer,useContext,那么当状态或上下文发生变化时,也会重新渲染

什么是React Fragment

从组件返回多个元素是React常见做法。片段允许形成子元素列表,而无需再DOM中创建不必要的节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react'
function frag(){

return(
<>
<Son1/>
<Son2/>
</>

<!-- or -->
<React.Fragment>
<Son1/>
<Son2/>
<React.Fragment/>
)
}

如何跟踪引用功能组件中对象字段的变化

使用useEffect钩子,将对象字段作为依赖数组传递,每次渲染后会重新获取对象字段

如何访问DOM元素

使用 React.createRef() 或 useRef(), 之后通过ref.current 获取元素属性

Redux 或其他状态管理库

  • 用于管理应用程序的全局状态,并在不同组件之间进行通信。
  • 适合中大型应用程序,具有复杂状态管理需求

react合成事件,为什么不使用浏览器原生事件,出发点是什么,考虑什么

  1. 跨浏览器兼容性

    • 不同浏览器对事件的实现和行为可能存在差异。通过合成事件,React 确保了在所有主流浏览器中事件处理的一致性,简化了开发者的工作。
  2. 性能优化

    • 合成事件在 React 内部通过池化(event pooling)机制来管理事件。即在事件处理后,合成事件对象会被重用,减少了内存的分配和垃圾回收,提高了性能。
  3. 统一的 API

    • 使用合成事件,React 提供了一个统一的事件处理接口,所有事件在 React 中的行为和属性是一致的。这对于开发者来说,提升了代码的可读性和可维护性。
  4. 事件生命周期管理

    • React 能够更好地控制事件的生命周期。这允许 React 在组件的卸载和更新过程中做出更智能的管理,避免了潜在的内存泄漏问题。
  5. 与 React 生态系统的集成

    • 合成事件使得事件处理和 React 的整体运行机制能够更紧密地集成。比如,React 能够在事件处理过程中进行一系列优化、调度和协调操作。
  6. 简化处理逻辑

    • 合成事件的 API 被设计得更易用,尤其是在处理高阶组件和函数组件时。合成事件使得这些情况的事件处理变得更简单。
  7. 支持新的功能

    • 通过合成事件,React 能够更灵活地支持一些新的功能,比如暂停和恢复事件的处理,或在切换到并发模式时更好地处理事件。

react18新特性

  • 去掉了对IE浏览器的支持
  • 事件自动批处理(Automatic Batching):React 18 中的自动批处理允许多个状态更新被智能地合并在一起,从而减少组件的重新渲染次数。这意味着即使在异步代码中,多个状态更新也会自动批处理,提高性能。
  • 开始并发模式(Concurrent Rendering):React 18 引入了并发特性,使得渲染可以被中断,以便于高优先级的更新。这样可以确保用户界面在处理复杂更新时保持响应性。
  • Suspense 组件的增强:React 18 扩展了 Suspense 的功能,使其不仅可以用于数据加载,还可以与并发特性结合使用。允许开发者在异步数据加载时更优雅地控制加载状态。
  • useTransition Hook:新增 useTransition API 来标记状态更新为过渡更新。这使得开发者可以在需要时为用户界面保留流畅性和响应性,尤其是在使用状态更新时。
  • useDeferredValue Hook:这个 Hook 允许开发者将一个状态值的更新延后一段时间,从而优化用户界面的响应。在输入框中输入文字时,可以实现更平滑的体验。
  • startTransition API:这个 API 允许开发者将某些更新标记为不紧急的,从而让 React 更加智能地管理其渲染优先级。
  • 全新的 createRoot API:React 18 引入了 createRoot 方法,用于挂载 React 应用的新根实例。这是采用并发特性的新推荐方式。
  • Concurrent Features的支持:React 18 允许开发者通过 useSuspense 结合使用小的惰性加载逻辑,提高应用的数据获取效率和界面的可交互性。
  • 新特性的无缝过渡:React 18 支持在现有代码中逐步过渡到新特性,降低了更新的复杂性。

React diff 算法介绍

  1. 虚拟 DOM

在 React 中,每个组件都有一个对应的虚拟 DOM 树。当状态改变时,React 会重新构建新的虚拟 DOM 树。这个虚拟 DOM 是一个轻量级的 JavaScript 对象,它包含了整个真实 DOM 树的映射关系。

  1. Diff 算法

在重新构建新的虚拟DOM树后,React 使用 diff 算法来比较新旧两棵虚拟 DOM 树,并找出最小操作来更新实际的页面DOM树。

  • 属性和事件处理函数变化

如果节点类型相同(比如都是 div 元素),但属性或事件处理函数发生了变化,则需要更新该节点上相应属性或事件处理函数。

  • 组件类型变化

如果节点由组件A变成了另一个不同类型的组件B,则旧节点需要被销毁并且新节点需要创建。

  • 列表元素遍历

Diff算法会遍历列表元素,准确找出哪些地方增加、删除、移动或更新了列表中的子元素。

  1. Diff 策略

    在进行Diff算法时,React采用以下策略:

    • 深度优先遍历:React使用深度优先策略来递归比较两棵虚拟DOM树。
    • 双端比较:从头部和尾部同时开始比较子元素列表,在某些情况下可以大幅减少移动操作次数。
    • Key属性:当为列表渲染添加唯一key属性时(通常使用数据中唯一ID),可以帮助React识别具体哪些项目被添加、删除、移动或更新。
    • 批量执行:将多次修改合并为一次性修改以提高性能。

React Fiber

React Fiber 是 React 库核心算法的重写版本。React Fiber 的引入使React更适合于处理大型、复杂应用程序,并提供更灵活、更高效的渲染方式。

React Fiber 的关键特性包括:

  1. 增量渲染:React Fiber 允许React将渲染工作分割为称为 fibers 的较小单元。这使得React能够优先安排和调度高优先级更新,从而实现更流畅的用户界面和更好的性能。
  2. 时间切片:时间切片是React Fiber中的一项功能,用于在多个渲染任务之间进行时间切片划分,以确保长时间运行的任务不会阻止用户界面响应。

React-router

React Router 是什么?

React Router 是基于 React 的官方路由库,用于在单页应用程序中管理页面导航和路由。

React Router 的主要特点是什么?

React Router 的主要特点包括:声明式路由配置、嵌套路由支持、参数传递和查询参数处理、动态路由匹配、历史记录管理等。

React Router 的核心组件是哪些?

React Router 的核心组件包括:BrowserRouter、HashRouter、Route、Switch、Link、NavLink等。

BrowserRouter 和 HashRouter 有什么区别?

BrowserRouter 使用 HTML5 History API,不带有 # 符号,URL 更加可读性高;HashRouter 使用 URL 中的哈希值实现路由,URL 带有 # 符号,支持在不同浏览器和服务器环境中运行。

如何在代码中定义路由?

使用 Route 组件来定义路由规则。将组件与路由路径相关联,以便在 URL 匹配时呈现相应的组件。

如何在 React Router 中传递参数?

可以在路由路径中使用占位符定义参数,并在组件内部通过 props.match.params 来访问它们。

如何导航到另一个路由?

可以使用 Link 或 NavLink 组件,在点击时导航到另一个路由,并携带相关参数。

如何在 React Router 中使用重定向?

可以使用 Redirect 组件来进行路由重定向。通过指定 from 和 to 属性,设置重定向的源路由和目标路由。

如何在 React Router 中处理嵌套路由?

可以使用嵌套的 Route 组件结合嵌套的路由路径来处理嵌套路由。

React Router 提供哪些导航守卫功能?

React Router 提供了三个导航守卫组件:BrowserRouter、HashRouter 和 MemoryRouter。它们可以用于在路由切换前执行某些操作,如权限验证或日志记录。

Redux

什么是Redux

Redux 是一个用于管理 JavaScript 应用程序状态的可预测状态容器。它可以帮助我们在应用程序中更有效地管理数据流,使状态更新变得可预测且易于调试。

Redux有什么优点

Redux 的优点包括:统一的数据流管理、可预测的状态变化、易于调试和跟踪、方便的中间件扩展、与 React 结合紧密、适用于复杂应用等。

Redux 遵循的三个原则是什么

Redux 遵循的三个原则是:

  • 单一数据源:应用程序中的状态被存储在一个单一的 JavaScript 对象中,称为 state。
  • 状态只读:状态是只读的,唯一改变状态的方式是派发一个 action。
  • 纯函数修改:使用纯函数(reducers)来描述状态的变化,根据旧的 state 和 action 来返回一个新的 state。

对 “单一事实来源” 有什么理解

这指的是 Redux 应用中的 state 被存储在一个单一对象树中。应用的所有组件共享此状态单一来源,并且只能通过派发 actions 来改变这个状态。这样可以使得应用程序的状态和行为更加容易追踪和管理。

列出 Redux 组件

常见的 Redux 组件有 Action、Reducer、Store 以及 Middleware。

数据如何通过 Redux 流动

数据通过 Redux 流动的方式是:组件派发 action,action 被发送到 reducer,reducer 处理 action 并更新 state,state 的更新通过订阅机制反馈到组件。

如何在Redux中定义Action

在 Redux 中,Action 是一个包含 type 属性的普通对象,用于描述某个事件的动作类型。定义 Action 通常包括创建一个 action 构造函数,返回一个包含 type 属性的对象。

解释Reducer的作用

Reducer 是一个纯函数,接收旧的 state 和 action,返回一个新的 state。Reducer 描述了应用程序状态的变化逻辑,根据传入的 action 来更新 state。

Store在Redux中的意义是什么

Store 是 Redux 应用中存储 state 的地方。它是一个包含整个应用程序状态树的对象,并负责维护和更新状态。通过 createStore 方法创建 Store,并可以使用 dispatch 方法派发 actions、订阅 state 的变化等。

Redux与Flux有什么不同

Redux 与 Flux 的主要不同之处在于数据流的单向性和状态管理的差异。Redux 通过 reducer 来管理状态变化,强调单一数据源,并提供了数据的不可变性。而 Flux 则是一种设计模式,可以有不同的实现方式,通常包含多个 Store,且数据流向复杂一些。

Redux 的核心概念包括 Store、Action、Reducer 和 Middleware。

  1. Store

Redux 中的 Store 是单一状态树,存储着整个应用的状态。可以通过 createStore 函数来创建一个 Store。

  1. Action

Action 是一个包含描述动作类型和负载数据的简单对象。它要求使用纯函数形式来描述对应操作,通常由 action creator 函数来创建。

  1. Reducer

Reducer 是一个纯函数,它接收先前的状态和 action,并返回新的状态。Reducer 定义了如何更新应用程序中相关部分的状态。

  1. Dispatch

Dispatch 是 store 对象提供的方法,用于触发 action 的执行并更新应用程序中相应部分的状态。

  1. Middleware

Middleware 允许你扩展 Redux 功能,在 action 触发到达 reducer 前进行额外处理或拦截操作。比如日志记录、异步操作等都可以通过 middleware 来实现。

工作流程

  1. 组件发起 Action:组件调用 dispatch 方法发送一个 action 到 store。
  2. Store 分发 Action:store 接收到 action 后将其传递给 reducer 进行处理。
  3. Reducer 更新 State:reducer 根据接收到的action类型以及负载数据来计算出新的 state。
  4. Store 更新订阅者:store 的 state 更新后会通知所有订阅者(比如 React 组件),从而重新渲染页面以反映最新 state。

redux数据不持久化怎么解决

  1. 使用浏览器本地存储(localStorage/sessionStorage):您可以将需要持久化的状态数据存储在浏览器的本地存储中,这样即使用户刷新或关闭浏览器,数据仍然会保留在本地存储中。您可以在应用初始化时从本地存储中读取数据,并在 Redux store 中重新加载数据。
  2. 使用第三方插件redux-persist 插件来实现Redux状态的持久化

Webpack

什么是 Webpack?

Webpack 是一个前端构建工具,它将不同类型的资源(JavaScript、CSS、图片等)视为模块,通过分析模块间的依赖关系,将它们打包成一个或多个静态文件,以提高Web应用的加载速度和性能。

Webpack 的核心概念有哪些?

  • 入口(Entry):指示 Webpack 从哪里开始构建依赖图,入口文件是应用的起点。
  • 输出(Output):定义 Webpack 打包后的文件存放位置和命名规则。
  • 加载器(Loader):允许 Webpack 处理非 JavaScript 文件(比如 CSS、图片、类型脚本等),将这些文件转换为模块。
  • 插件(Plugin):可以扩展 Webpack 的功能,用于执行范围更广的任务,如代码压缩、提取 CSS 等。
  • 模块(Module):Webpack 将文件视为模块,通过依赖管理实现模块化。

webpack 打包过程

  1. 解析入口文件:Webpack从入口文件开始,通过解析入口文件中的依赖关系来构建应用程序的依赖图。Webpack支持多种模块类型,包括CommonJS、ES6模块、AMD等。
  2. 加载模块:Webpack使用加载器(Loader)来处理不同类型的模块。加载器可以将模块转换为Webpack可识别的模块格式,并且可以在加载模块时执行一些额外的操作,如代码转换、压缩等。
  3. 生成代码块:Webpack将所有模块打包成一个或多个代码块(Chunk)。代码块是一组相关的模块,它们被组合在一起以便于加载和执行。Webpack使用代码分割(Code Splitting)技术来将代码块拆分成更小的块,以便于并行加载和执行。
  4. 代码块转换为资源文件:Webpack将代码块转换为静态资源文件,如JavaScript文件、CSS文件、图片等。Webpack使用插件(Plugin)来处理资源文件,如压缩、优化等。
  5. 输出打包文件:最后,Webpack将所有资源文件打包成一个或多个输出文件。输出文件可以是单个JavaScript文件、多个JavaScript文件、CSS文件、图片等。Webpack使用输出配置(Output Configuration)来指定输出文件的名称、路径等。

如何配置 Webpack 的输出选项?

webpack.config.js 中可以这样配置输出选项:

1
2
3
4
5
6
7
8
const path = require('path');  

module.exports = {
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};

什么是 Loader,举几个常见的例子?

Loader 是用于将不同类型的文件转换为模块的工具。常见的 Loader 包括:

  • babel-loader:将 ES6/ES7 等语法转换为 ES5。
  • css-loader:使 Webpack 能够理解 @importurl()
  • style-loader:将 CSS 插入 <script> 标签中。
  • file-loader:处理文件和图像,返回 URL。

什么是插件,如何使用它?

插件是用于扩展 Webpack 功能的插件。可以用来进行文件优化、代码压缩等。使用方式通常是在 webpack.config.js 中的 plugins 数组中引入。例如,使用 HtmlWebpackPlugin 插件:

1
2
3
4
5
6
7
8
9
const HtmlWebpackPlugin = require('html-webpack-plugin');  

module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html',
}),
],
};

Webpack 中的热模块替换(Hot Module Replacement, HMR)是什么?

HMR 是一种在运行时更新已被修改的模块,而不需要完全刷新页面的技术。这样可以大大提高开发效率。启用 HMR 通常需要配置 devServer,如下所示:

1
2
3
devServer: {  
hot: true,
},

如何实现代码分割?

代码分割可以通过使用动态导入(import())来实现,Webpack 会自动将模块拆分成多个文件。例如:

1
2
3
4
import(/* webpackChunkName: "my-chunk-name" */ './my-module')  
.then(module => {
// 使用模块
});

解释一下 Webpack 的四个生命周期钩子。

  • **initialize**:Webpack 初始化的阶段。
  • **emit**:在生成资源文件之前,Webpack 可以修改生成的文件。
  • **compile**:在构建过程中,模块准备被打包。
  • **done**:所有编译完成后的阶段,进行最终的回调操作。

如何在生产环境和开发环境中配置 Webpack?

通常可以创建两个配置文件 webpack.dev.jswebpack.prod.js,在开发阶段可以启用热模块替换和源代码映射,而生产阶段则可以启用压缩和优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// webpack.dev.js  
module.exports = {
mode: 'development',
devtool: 'source-map',
// 其他开发环境配置
};

// webpack.prod.js
module.exports = {
mode: 'production',
optimization: {
minimize: true,
},
// 其他生产环境配置
};

模块化 Commonjs,AMD,CMD,UMD,ES6 Module分别有什么区别

CommonJS

CommonJS 是一种模块化规范,主要用于服务器端的 JavaScript( Node环境)。它的特点是:

  • 使用 require() 方法加载模块。
  • 使用 module.exports 导出模块。
  • 同步加载模块,即在代码执行时立即加载模块。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
// 导入模块  
const math = require('./math');

// 导出模块
module.exports = {
add: function(a, b) {
return math.add(a, b);
},
subtract: function(a, b) {
return math.subtract(a, b);
}
};

AMD

AMD(Asynchronous Module Definition)是一种异步加载模块的规范,主要用于浏览器端的 JavaScript。它的特点是:

  • 使用 define() 方法定义模块。
  • 使用 require() 方法异步加载模块。
  • 支持在浏览器端异步加载模块,避免了阻塞页面加载的问题。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义模块  
define(['math'], function(math) {
return {
add: function(a, b) {
return math.add(a, b);
},
subtract: function(a, b) {
return math.subtract(a, b);
}
};
});

// 异步加载模块
require(['calculator'], function(calculator) {
console.log(calculator.add(1, 2));
});

CMD

CMD(Common Module Definition)是一种类似于 AMD 的模块化规范,主要用于浏览器端的 JavaScript。它的特点是:

  • 使用 define() 方法定义模块。
  • 使用 require() 方法异步加载模块。
  • 支持在浏览器端异步加载模块,避免了阻塞页面加载的问题。
  • 与 AMD 不同的是,CMD 是在需要使用模块时才加载模块,而不是在页面加载时就加载所有模块。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义模块  
define(function(require, exports, module) {
const math = require('math');
exports.add = function(a, b) {
return math.add(a, b);
};
exports.subtract = function(a, b) {
return math.subtract(a, b);
};
});

// 异步加载模块
require(['calculator'], function(calculator) {
console.log(calculator.add(1, 2));
});

UMD

UMD(Universal Module Definition)是一种通用的模块化规范,可以同时支持 CommonJS、AMD 和全局变量的方式。它的特点是:

  • 先判断是否支持 CommonJS,如果支持则使用 CommonJS 规范加载模块。
  • 如果不支持 CommonJS,则判断是否支持 AMD,如果支持则使用 AMD 规范加载模块。
  • 如果既不支持 CommonJS 也不支持 AMD,则将模块作为全局变量加载。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(function(root, factory) {  
if (typeof define === 'function' && define.amd) {
// AMD
define(['math'], factory);
} else if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('math'));
} else {
// 全局变量
root.calculator = factory(root.math);
}
}(this, function(math) {
return {
add: function(a, b) {
return math.add(a, b);
},
subtract: function(a, b) {
return math.subtract(a, b);
}
};
}));

ES6 Module

ES6 Module 是 ECMAScript 6 标准中新增的模块化规范,主要用于浏览器端和服务器端的 JavaScript。它的特点是:

  • 使用 importexport 关键字导入和导出模块。
  • 支持静态分析,可以在编译时确定模块的依赖关系。
  • 支持异步加载模块。

示例:

1
2
3
4
5
6
7
8
9
10
11
// 导入模块  
import math from './math.js';

// 导出模块
export function add(a, b) {
return math.add(a, b);
}

export function subtract(a, b) {
return math.subtract(a, b);
}

webpack能处理什么文件,不能处理什么文件

Webpack 能处理的文件类型

  1. JavaScript 文件:Webpack 可以处理 ES6、CommonJS、AMD 等模块化标准的 JavaScript 文件,并可以通过 babel-loader 转换 ES6+ 代码。
  2. CSS 文件:可以通过 css-loaderstyle-loader 处理 CSS 文件,还可以处理 SASS、LESS 等预处理样式。
  3. 图像和字体文件:通过 file-loaderurl-loader,Webpack 可以处理 JPEG、PNG、SVG 等图像文件,以及 TTF、WOFF、WOFF2 等字体文件。
  4. HTML 文件:通过 html-webpack-plugin 插件,Webpack 可以处理 HTML 文件,并自动插入生成的 JavaScript 和 CSS 文件。
  5. JSON 文件:Webpack 能够直接处理和导入 JSON 文件。
  6. 其他文件:可以使用特定的 loader 处理其他类型的文件,如 Markdown、XML、YAML 等。

Webpack 不能处理的文件类型

  1. 其他语言原生代码:如 C、C++ 等编写的代码,Webpack 并没有内置支持,需要借助额外工具来编译。
  2. 特定的非模块化文件:如某些二进制文件、视频文件等,Webpack 虽然可以通过 loader 处理,但并不适合直接用于打包。
  3. 大型项目中的超高复杂度文件:在某些情况下,大型项目中的文件结构过于复杂可能需要自定义解决方案,Webpack 可能不是最佳选择。

webpack 处理性能优化

代码分割(Code Splitting)

  • 动态导入:使用 import() 动态导入模块,按需加载,减少初始加载体积。
  • Entry Points:将应用程序的不同部分分成不同的入口点,以便每个入口点只加载其所需的依赖。
1
2
3
4
5
6
7
// 示例:动态导入  
const button = document.getElementById('my-button');
button.addEventListener('click', event => {
import('./module.js').then(module => {
module.doSomething();
});
});

Tree Shaking

  • Tree Shaking 是一种静态分析技术,旨在减少打包文件的大小。它的核心目标是从最终的捆绑结果中删除未被引用的模块或导出。通过这种方式,开发者可以避免在生产环境中载入多余的代码,从而提高性能和加载速度。

  • 工作原理

    Tree Shaking 依赖于 ES6 模块系统(即使用 importexport 语法),这是因为这种系统具有静态结构,能够在编译时进行更好的分析。以下是主要原因:

    • 静态分析:由于模块导入和导出在编译时是已知的,Webpack 和其他打包工具能够分析哪些模块被使用,哪些没有。
    • 明确的引用:与 CommonJS 这样的动态加载方式不同,ES6 语法明确声明了模块的依赖
  • 步骤

    1. 解析代码:打包工具解析应用程序的代码,构建模块的依赖图。
    2. 分析使用情况:查找哪些模块和导出实际被使用,哪些是未使用的“死代码”。
    3. 删除未使用的代码:在生成最终的捆绑文件之前,去除未被引用的模块和导出
  • 副作用

    一些代码可能会有副作用,例如修改全局变量、添加事件监听等。为了安全地使用 Tree Shaking,建议在 package.json 文件中明确指定哪些模块是有副作用的,或者通过 sideEffects: false 来声明整个库没有副作用。

减少打包体积

  • **使用 TerserPlugin**:压缩和优化 JS 文件。
  • 移除未使用的依赖:检查包并移除不必要的依赖。

使用 DllPlugin - 分包

  • 将不频繁变动的依赖这些库单独打包,后期只需要打包逻辑代码即可,从而提高整体打包速度。

图片和资源优化

  • 使用 image-webpack-loader 对图片进行压缩和优化,减少其体积,进而提高加载速度。
  • 通过 file-loaderurl-loader 根据文件大小进行合理的打包和加载。

CSS优化

  • 使用 MiniCssExtractPlugin 将 CSS 分离到单独的文件中,减少 JS 文件的体积,提高加载速度。
  • 避免在 CSS 中使用过大的背景图,考虑加载替代图像的方式。

使用预取和懒加载

  • 使用 Webpack 的 Prefetch 功能,让某些较大的资源在空闲时预加载。
1
2
// 示例:在 Webpack 中启用 Prefetch  
import(/* webpackPrefetch: true */ './module.js');

开启长缓存

  • 使用 contenthash 指定生成文件的名称,使文件在更新后才能发生变化,从而使 CDN 缓存更高效。
1
2
3
4
output: {  
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
},

打包分析

  • 使用 Webpack Bundle Analyzer 插件,分析打包后的文件,查看模块的体积,找出可以优化的部分。
1
npm install --save-dev webpack-bundle-analyzer  

开发模式优化

  • 在开发模式中使用 webpack-dev-serverwebpack-dev-middleware 提高热模块替换的效率,减少不必要的完整打包。

使用 HTTP/2

  • 如果你服务器支持 HTTP/2,可以并行加载多个小文件,建议保持较小的入口点。

减少请求数量

  • 将多个 CSS 或 JS 文件合并为一个,减少初始请求的数量。

使用 Service Workers

  • 可以将页面预加载和缓存逻辑实现到 Service Worker 中,进一步加快页面加载速度。

SPA首屏优化方案 - 减少白屏时间

  1. 资源优化

    • 静态资源优化压缩减少体积:对于首屏需要的静态资源(如CSS、JavaScript、图片等),可以进行优化,如压缩、合并、缓存等。使用压缩工具可以减小文件大小,合并多个文件可以减少请求数量,使用缓存可以减少网络请求。
    • 使用CDN:将静态资源部署到CDN上,提高资源访问效率
  2. 减少一次性请求的文件大小

    • 代码分割 - 按需加载/ 异步加载:使用Webpack等构建工具的代码分割功能,将应用程序的代码拆分成多个小块(chunks)。这样可以实现按需加载或异步加载,只加载当前页面所需的代码,减少首屏加载的大小和时间。
    • 预加载和懒加载:通过预加载和懒加载技术,可以在首屏加载完成后,异步加载其他页面所需的代码和资源。预加载可以在后台加载页面所需的资源,以提前准备好下一个页面的加载。懒加载可以延迟加载某些组件或模块,只在需要时才进行加载。
  3. 服务端渲染(SSR):将部分页面在服务器端进行渲染,生成静态HTML返回给客户端。这样可以加快首屏加载速度,因为客户端只需要展示已经渲染好的HTML,而不需要等待JavaScript代码的下载和执行。

  4. 优化网络请求

    • 减少请求数量
      • 合并css和js文件,减少http请求,雪碧图/精灵图
      • 使用http/2 ,多路复用请求
    • 缓存
      • 配置浏览器缓存策略:减少重复加载
      • 使用service worker进行资源缓存
  5. 骨架屏

    • 在页面加载时显示骨架屏,提升用户感知性能
  6. 优化渲染

    • 提前渲染
      • 提前渲染关键性能:确保关键内容在html中靠前位置,优先渲染
    • 渲染优化
      • 避免大型DOM操作,优化渲染性能
      • 使用虚拟滚动技术,优化长列表渲染
    • 使用现代构建工具和技术
      • 构建工具配置
      • Tree Sharking技术

webpack和vite区别

  • vite是基于ESM,浏览器原生支持;webpack是基于Commonjs,需要编译
  • viite开发环境基于esbuild,也就是基于go语言( 适合CUP密集型计算,适用多线程);webpack基于node,node特点有单线程

html+css

如何理解html,css,js

  1. HTML:HTML(HyperText Markup Language)是一种用于创建Web页面结构的标记语言。
  2. CSS:CSS(Cascading Style Sheets)是一种用于控制Web页面样式的语言
  3. JavaScript:JavaScript是一种用于实现Web页面交互的脚本语言

html代码第一行有什么作用

告诉浏览器采用哪种HTML版本解释页面内容。不同的HTML版本有不同的规范和特性,因此通过<!DOCTYPE>声明可以确保浏览器正确地呈现页面。

html的meta属性有哪些,都是什么作用

meta元素是HTML中用来提供关于文档的元数据(metadata)的标签。以下是一些常见的meta属性及其作用:

  1. charset: 指定文档使用的字符编码,例如UTF-8。
  2. name="viewport": 用于控制页面在移动设备上的显示,包括宽度、缩放等。
  3. name="description": 提供对文档内容的简短描述,通常被搜索引擎用于搜索结果中显示。
  4. name="keywords": 指定与文档相关的关键词,有助于搜索引擎索引。
  5. name="author": 用于指定文档的作者。
  6. http-equiv="refresh": 自动刷新页面或定时跳转到其他页面。

这些是meta元素中常见的属性,它们可以帮助优化网页内容、提高SEO

隐藏一个元素有哪些方法

  1. CSS display 属性:

    1
    2
    3
    4
    // 通过给元素添加 hidden 类,可以将其隐藏起来。
    .hidden {
    display: none;
    }
  2. CSS visibility 属性:

    1
    2
    3
    4
    // 通过给元素添加 hidden 类,可以使元素不可见但仍占据页面空间。
    .hidden {
    visibility: hidden;
    }
  3. 设置元素的 style.display 属性:

    1
    2
    // 通过 JavaScript 修改元素的 display 属性来隐藏它。
    document.getElementById('elementId').style.display = 'none';
  4. 设置元素的 style.visibility 属性:

    1
    2
    // 通过 JavaScript 修改元素的 visibility 属性来隐藏它。
    document.getElementById('elementId').style.visibility = 'hidden';

小程序和h5有什么区别

  • 运行环境 - 小程序是平台内部运行的轻量级应用,可以直接在平台运行;h5 用户需要通过浏览器访问网址来使用
  • 开发语言 - 小程序通常采用特定的框架和语言进行开发,例如微信小程序采用WXML、WXSS和JavaScript;H5 使用现代web前技术
  • 权限 - 小程序能够调用更多的系统API权限。 h5是基于浏览器,在安全方面着想,受限程度多。
  • 分发方式 - 小程序必须依赖该用户平台,而h5 则不用依赖于特定浏览器

盒子模型理解介绍

盒模型(Box Model)是用于构建和布局网页元素的基本概念之一,它描述了每个 HTML 元素在页面上所占空间的方式。在 CSS 中,每个元素都被表示为一个矩形盒子,该盒子包括内容区域、内边距(padding)、边框(border)和外边距(margin)四个部分

css的几种引入方式

  1. 内联样式(Inline Styles):
    使用内联样式的方式,直接在 HTML 元素的 style 属性中指定样式。

    1
    <div style="color: red; font-size: 16px;">这是一段红色字体的文本</div>  
  2. 内部样式表(Internal Stylesheet):
    在 HTML 文档的头部(head)标签内使用 <style> 标签定义样式表。

    1
    2
    3
    4
    5
    6
    7
    8
    <head>  
    <style>
    div {
    color: blue;
    font-size: 18px;
    }
    </style>
    </head>
  3. 外部样式表(External Stylesheet):
    将样式表单独保存为一个 .css 文件,并在 HTML 文档中通过 标签引入。

    1
    2
    3
    <head>  
    <link rel="stylesheet" type="text/css" href="styles.css">
    </head>
  4. @import 语句:
    在样式表中使用 @import 语句引入其他 CSS 文件。

    1
    2
    /* styles.css */  
    @import url("reset.css");
  5. 使用 @import 引入外部样式表:
    在样式表中使用 @import 语句来引入其他外部样式表。

    1
    2
    /* styles.css */  
    @import url("https://fonts.googleapis.com/css?family=Roboto");

css 布局方式有哪些

CSS中常见的布局方式有以下几种:

  1. 流动布局(Flow Layout):元素按照在HTML中出现的顺序依次排列,可以通过设置float属性实现左浮动或右浮动。
  2. 定位布局(Positioning Layout):通过设置position属性为absoluterelativefixed来控制元素的位置。
  3. 弹性盒子布局(Flexbox Layout):使用display: flexdisplay: inline-flex来创建灵活的布局,可以方便地控制元素的排列顺序、对齐方式等。
  4. 网格布局(Grid Layout):使用CSS网格布局可以将页面划分为行和列,通过定义网格容器和网格项来实现复杂的布局。
  5. 多列布局(Multiple Column Layout):通过设置column-countcolumn-width等属性,将文本内容分成多列显示。

讲一下弹性盒子布局

通过将容器设置为display: flexdisplay: inline-flex,容器内的子元素就会成为弹性项目弹性盒子布局的主要特点包括:

  • 主轴和交叉轴:主轴是弹性项目排列的方向,交叉轴与主轴垂直。开发者可以通过flex-direction属性控制主轴的方向。
  • 弹性项目的灵活性:弹性项目可以根据需要扩展、收缩,可以通过flex-growflex-shrinkflex-basis属性来控制。
  • 对齐方式:可以通过justify-contentalign-items等属性来控制弹性项目在主轴和交叉轴上的对齐方式。
  • 顺序控制:可以通过order属性调整弹性项目的排列顺序。

@开头的css属性

以 “@” 开头的部分在 CSS 中通常被称为 “at-rules”(规则),它们用来定义一些CSS规则和特性,而不是用来设置元素的样式

  1. @media:
    用于设定媒体查询,根据设备属性如屏幕尺寸、分辨率等来应用不同的样式。

    1
    2
    3
    4
    5
    @media screen and (max-width: 600px) {  
    body {
    font-size: 14px;
    }
    }
  2. @font-face:
    用于在CSS中嵌入自定义字体,让网页可以使用非标准字体。

    1
    2
    3
    4
    @font-face {  
    font-family: "CustomFont";
    src: url('customfont.woff') format('woff');
    }
  3. @keyframes:
    用于创建动画序列,定义动画的各个关键帧。

    1
    2
    3
    4
    @keyframes slidein {  
    from { margin-left: 100%; }
    to { margin-left: 0%; }
    }
  4. @import:
    用于引入其他的 CSS 文件。

    1
    @import url('otherstyles.css');  
  5. @supports:
    用于检查浏览器是否支持某些 CSS 属性或值。

    1
    2
    3
    @supports (display: grid) {  
    /* CSS rules for browsers that support CSS Grid */
    }
  6. @page:
    用于设置打印页面的样式。

    1
    2
    3
    @page {  
    size: A4;
    }

CSS长度单位

在 CSS 中,有多种长度单位用于指定元素的尺寸和位置。

  1. 像素(px):
    像素是相对于显示器上的物理像素(屏幕上的点)的单位。它是最常用的长度单位,通常用于指定固定尺寸。

    1
    2
    3
    4
    div {  
    width: 200px;
    height: 50px;
    }
  2. 百分比(%):
    百分比是相对于父元素的尺寸的单位,可以用于创建响应式布局。

    1
    2
    3
    4
    div {  
    width: 50%;
    padding: 10%;
    }
  3. em:
    em 是相对于元素自身的字体大小的单位。1em 等于元素的字体大小。

    1
    2
    3
    4
    div {  
    font-size: 1.2em;
    padding: 0.5em;
    }
  4. rem:
    rem 是相对于根元素(html 元素)的字体大小的单位。1rem 等于根元素的字体大小。

    1
    2
    3
    4
    div {  
    font-size: 1.5rem;
    margin: 2rem;
    }
  5. vw 和 vh:
    vw 和 vh 分别代表视口宽度(Viewport Width)和视口高度(Viewport Height)的百分比。

    1
    2
    3
    4
    div {  
    width: 50vw;
    height: 20vh;
    }
  6. cm、mm、in、pt、pc:
    这些单位分别代表厘米(cm)、毫米(mm)、英寸(inch)、点(point)和派卡(pica),在打印样式表中使用较多。

CSS 中,display 属性

display 属性用于控制元素的显示方式,一般用来做模型的灵活布局

  1. block:
    将元素显示为块级元素,会独占一行并自动换行,宽度默认为父元素的100%。

    1
    2
    3
    div {  
    display: block;
    }
  2. inline:
    将元素显示为内联元素,不会独占一行,宽度由内容决定,不会自动换行。

    1
    2
    3
    span {  
    display: inline;
    }
  3. inline-block:
    将元素显示为内联块级元素,可以设置宽高,不会独占一行,但会换行。

    1
    2
    3
    div {  
    display: inline-block;
    }
  4. none:
    将元素隐藏,不占用空间。

    1
    2
    3
    div {  
    display: none;
    }
  5. flex:
    使用 Flexbox 布局来布局元素,可以实现强大的布局效果。

    1
    2
    3
    div {  
    display: flex;
    }
  6. grid:
    使用网格布局来布局元素,提供了更灵活的网格布局效果。

    1
    2
    3
    div {  
    display: grid;
    }

css元素居中的4中写法

  1. 水平居中:

    • 对于块级元素,可以使用 margin: 0 auto; 来实现水平居中。
    • 对于行内元素,可以将父元素设置为 text-align: center; 并且将子元素设为 display: inline-block; 来实现水平居中。
  2. 垂直居中:

    • 使用 Flexbox 布局,可以通过将父容器设置为 display: flex; 并且使用 align-items: center; 来实现垂直居中。
    • 使用 Grid 布局,可以通过将父容器设置为 display: grid; place-items: center; 来实现垂直和水平同时居中。
  3. 水平和垂直都居中:

    • 使用绝对定位(position:absolute)和 transform 属性来使元素水平和垂直同时居中。例如:

      1
      2
      3
      4
      5
      6
      .centered {  
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      }
  4. 表格布局方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    .container {  
    display: table;
    width :100%; /*宽度设置*/
    height :100vh;/*高度设置*/
    }

    .centered {
    display :table-cell;
    vertical-align :middle;
    text-align:center;/*文本信息左右对齐的属性*/

    }

css绘制三角形

  1. 使用 border:

    设置一个矩形元素的边框,利用透明的边框来隐藏一部分,从而形成三角形的效果。具体步骤如下:

    • 创建一个具有宽度和高度为0的块元素。
    • 设置三个边框的样式,其中两个边框为透明,一个边框为所需颜色。
    • 最后设置一个矩形元素的宽度和高度为0,并确保三个边框的宽度满足绘制三角形的需求。
    1
    2
    3
    4
    5
    6
    7
    .triangle {  
    width: 0;
    height: 0;
    border-left: 50px solid transparent;
    border-right: 50px solid transparent;
    border-bottom: 50px solid red;
    }
  2. 使用 transform:

    绘制一个矩形元素,然后通过旋转这个矩形元素45度将下面的隐藏来形成一个等边三角形。

    • 创建一个矩形元素。
    • 设置三边的宽度和颜色来绘制一个等边三角形。
    • 使用 CSS 的 transform: rotate(45deg); 将矩形元素旋转45度,从而形成三角形。
    1
    2
    3
    4
    5
    6
    7
    8
    .triangle {  
    width: 0;
    height: 0;
    border-left: 50px solid transparent;
    border-right: 50px solid transparent;
    border-bottom: 50px solid red;
    transform: rotate(45deg);
    }
  3. 使用 clip-path:

    使用 CSS 的 clip-path 属性来定义一个多边形剪切路径,从而将矩形元素剪切成一个三角形

    • 创建一个矩形元素。
    • 使用 clip-path: polygon() 函数来定义一个三角形的多边形路径,路径的顶点坐标按照需要的形状来设置。
    • 这样设置后,矩形元素就会被剪切成一个三角形的形状。
    1
    2
    3
    4
    5
    6
    .triangle {  
    width: 0;
    height: 0;
    clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
    background-color: red;
    }

使用css设置0.5px的线条

在 CSS 中无法直接设置 0.5px 的线条宽度。可以使用缩放的方式

有两种方法可以尝试:

  1. 使用边框半透明
    设置一个像素单位的边框,并给它设置为半透明,可以模拟出一个看起来像是 0.5px 的线条。

    1
    2
    3
    4
    .halfpx-line {  
    height: 1px;
    background-color: rgba(0, 0, 0, 0.5); /* 半透明黑色 */
    }
  2. 使用 transform 缩放
    可以利用 CSS 的 transform 属性中的 scale() 函数,将一个细线条元素进行缩放来实现看似 0.5px 的效果。

    1
    2
    3
    4
    5
    .halfpx-line {  
    height: 1px;
    background-color: black;
    transform: scaleY(0.5);
    }

    css用 transformscaleY(0.5) 来将元素在垂直方向上缩放为原来的一半,从而实现看似是 0.5px 宽度的线条。

CSS3实现动画效果的属性

  1. transition
    transition 属性可以指定元素在状态变化时的过渡效果。通过指定过渡属性、持续时间、延迟时间和过渡函数来实现平滑的状态变化效果。
    可以用于对元素进行变换(如平移、旋转、缩放、倾斜等)。在 transform 属性中可以使用 translate()rotate() 函数来分别实现平移和旋转效果

    1
    2
    3
    div {  
    transition: width 0.5s ease-in-out;
    }
  2. animation
    animation 属性允许在指定的时间内通过在关键帧之间来循环显示动画。使用 @keyframes 规则定义动画的关键帧。

    1
    2
    3
    4
    5
    6
    7
    8
    @keyframes slidein {  
    from { margin-left: 100%; }
    to { margin-left: 0%; }
    }

    div {
    animation: slidein 2s infinite;
    }
  3. keyframes
    @keyframes 规则用于创建动画序列,定义不同关键帧下的元素样式。

  4. transition-property
    用于指定过渡效果的 CSS 属性名称,仅这些属性发生变化时才会应用过渡效果。

    1
    2
    3
    div {  
    transition-property: background-color, transform;
    }
  5. animation-timing-function
    用于指定动画过渡函数,控制动画变化速度。

    1
    2
    3
    div {  
    animation-timing-function: ease-in-out;
    }
  6. animation-duration
    用于指定动画执行的持续时间。

    1
    2
    3
    div {  
    animation-duration: 2s;
    }

css清除浮动

清除浮动(clearfix)是用来解决由浮动元素引起的父元素高度塌陷的常见问题。

  1. 使用 clear 属性
    在浮动元素的下方添加一个空的 div 元素,并为该 div 添加样式 clear: both;,这样可以清除浮动效果。

    1
    2
    3
    4
    5
    .clearfix::after {  
    content: "";
    display: block;
    clear: both;
    }

    在这个例子中,我们给添加了 .clearfix 类的元素的伪元素 ::after 中设置了 clear: both;,这样便清除了浮动。

  2. 使用overflow 属性
    通过为包含浮动元素的父元素添加 overflow: hidden;overflow: auto; 属性来清除浮动效果。

    1
    2
    3
    .parent {  
    overflow: hidden;
    }

    在这个例子中,我们给包含浮动元素的父元素添加了 overflow: hidden; 属性,这样也可以清除浮动。

  3. 使用伪元素
    通过设置父元素的 overflow: hidden; 属性,并同时为其添加一个清除浮动的伪元素。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    .parent {  
    overflow: hidden;
    }

    .parent::after {
    content: "";
    display: table;
    clear: both;
    }

    在这个例子中,我们在包含浮动元素的父元素中使用了伪元素 ::after 来清除浮动,并设置了 clear: both;

margin 坍塌

当两个相邻的元素之间的上(或下)外边距发生重叠时,就会发生边距坍塌(margin collapsing)的现象。坍塌后的边距取两者中的最大值,而不是简单相加起来

  1. 父子元素之间的边距坍塌
    如果父元素和子元素之间的边距发生坍塌,可以通过给父元素添加 paddingborder 来阻止边距坍塌。
  2. 兄弟元素之间的边距坍塌
    当相邻的兄弟元素中的上(或下)外边距相遇时也会发生边距坍塌。可以通过其中一个元素设置 paddingborder 来阻止边距坍塌。
  3. 空元素的边距坍塌
    如果两个相邻的空元素之间有边距,也可能发生坍塌。可以通过设置其中一个元素的高度或添加内容来避免边距坍塌。
  4. 使用 display: inline-block 时的边距坍塌
    使用 inline-block 的元素也可能出现边距坍塌,可以通过在元素之间插入空白字符或者设置 font-size: 0; 来解决。

BFC实现方法

BFC(块级格式化上下文)是 CSS 中的一个重要概念,它可以影响元素的布局和渲染。BFC 的主要作用是创建一个独立的渲染环境,使得元素的布局不受外部影响,从而避免一些常见的布局问题。

解决的问题:

  1. 清除浮动:当一个元素设置了浮动后,其父元素的高度将会塌陷,导致布局错乱。使用 BFC 可以触发父元素形成一个独立的渲染环境,使得其高度可以正确地被计算并包裹浮动元素。
  2. 避免外边距重叠:在普通流中相邻块级盒子的上外边距和下外边距有时会发生重叠。将这些盒子放入不同的 BFC 中可以避免外边距重叠。
  3. 自适应两栏布局:当需要实现两栏等高自适应布局时,可以利用 BFC 来清除浮动,并保证两栏等高。
  4. 垂直居中问题:利用 BFC 可以实现一些垂直居中效果,例如通过设置父元素为 BFC 并采用 flex 布局实现垂直居中。
  5. 阻止文字环绕:使用 BFC 可以防止文字环绕非浮动块级盒子。
  6. margin 重叠: 在普通流中,相邻的两个块级盒子的上下外边距会发生重叠,导致外边距的计算结果不符合预期

BFC 的实现方法有以下几种:

  1. float 属性:当元素设置了 float 属性时,会创建一个 BFC。这是因为 float 元素会脱离文档流,形成一个独立的渲染环境。
  2. position 属性:当元素设置了 position 属性为 absolute 或 fixed 时,会创建一个 BFC。这是因为绝对定位和固定定位的元素会脱离文档流,形成一个独立的渲染环境。
  3. display 属性:当元素设置了 display 属性为 inline-block、table-cell、table-caption、flex、inline-flex 或 grid 时,会创建一个 BFC。这是因为这些属性会使元素形成一个独立的渲染环境。
  4. overflow 属性:当元素设置了 overflow 属性为 auto、scroll 或 hidden 时,会创建一个 BFC。这是因为 overflow 属性可以控制元素的溢出内容,从而形成一个独立的渲染环境。

对IFC的理解

IFC 是 CSS 中的一种布局模式,IFC 是指内联格式化上下文,它是一种用于排列内联元素的 CSS 布局模式

IFC 的特点包括:

  • 内联元素会在水平方向上依次排列,直到排满一行,然后自动换行到下一行。
  • IFC 中的元素会在垂直方向上对齐,例如顶部对齐、底部对齐等。
  • IFC 中的元素会在水平方向上对齐,例如左对齐、右对齐等。
  • IFC 中的元素会根据自身的大小和内容自动调整位置和大小,以适应容器的大小和其他元素的位置。

flex

  1. 容器属性
    • display:设置容器为 flex 布局。
    • flex-direction:设置主轴的方向(row、row-reverse、column、column-reverse)。
    • flex-wrap:设置是否换行(nowrap、wrap、wrap-reverse)。
    • justify-content:设置主轴上的对齐方式(flex-start、flex-end、center、space-between、space-around)。
    • align-items:设置交叉轴上的对齐方式(flex-start、flex-end、center、baseline、stretch)。
    • align-content:设置多根轴线的对齐方式(flex-start、flex-end、center、space-between、space-around、stretch)。
  2. 项目属性
    • order:设置项目的排列顺序。
    • flex-grow:设置项目的放大比例。
    • flex-shrink:设置项目的缩小比例。
    • flex-basis:设置项目的基准大小。
    • flex:设置项目的放大比例、缩小比例和基准大小。
    • align-self:设置单个项目在交叉轴上的对齐方式。

媒体查询

媒体查询是一种用于网页设计的技术,它允许根据设备的特性(如屏幕大小、分辨率、设备方向等)来应用不同的样式表

css回流,重绘,合并图层,GPU 加速

减少回流和重绘:

  1. 使用 transform 属性来移动元素,而不是使用 top 或 left。
  2. 使用 requestAnimationFrame 来执行动画,这样可以将所有的 DOM 更新合并成一个,并在下一帧统一处理。
  3. 避免频繁修改样式属性,最好通过修改 CSS 类名的方式来批量改变元素样式。

合并图层:

  1. 避免创建大量复杂的 z-index 嵌套结构,这可能会导致浏览器创建大量图层。
  2. 将页面中不经常变化的部分放在单独的图层中,比如使用 will-change 属性或 translate3d 来提前启用 GPU 加速。

GPU 加速:

  1. 将需要使用 GPU 加速渲染的元素设为 3D 变换。例如:transform: translateZ(0) 或者 perspective: 1000px。
  2. 尽量避免过多地同时开启硬件加速,因为它也会消耗额外内存。

关于适配问题

  1. 普通移动端(H5)

    盒子可以采用rem单位( rem是针对html根元素的fontSize),可以通过动态获取屏幕可视宽度,来设置html的fontSize属性。从而达到适配

  2. uniapp 小程序 做适配

    使用 rpx单位,一般开发时,若设计图宽度一般为750px。例如测量一个元素宽度为300px。代码中直接写300rpx即可。

    早期uniapp是推荐使用upx,目前改成了 rpx。rpx是根据屏幕宽度自适应的动态单位,基准单位为750rpx。会进行px换算: 1rpx = 750*(设计稿元素宽度px / 设计稿基准宽度px)

    例如: 设计稿为640px,元素宽度为100px,则换算后为 117 rpx

    750*(100px/640px) = 117rpx

  3. 扩展 - px、em、rem、%、vh、vw

    • px 绝对单位,精确像素展示
    • em 相对单位,相对于父节点字体进行计算
    • rem 相对单位,相对根节点html的字体大小来计算
    • % 百分比,根据父节点的大小进行百分比计算
    • vh 百分比,根据可视高度进行百分比计算,1vh = 可视高度1%
    • vw 百分比,根据可视宽度进行百分比计算,1vh = 可视宽度1%

骨架屏原理

  1. 设计骨架屏:首先需要设计好网页的基本结构,并确定哪些元素会在页面加载时展示为骨架屏。这可能包括导航栏、列表、卡片等重要的页面结构元素。
  2. 创建骨架屏样式:利用CSS来定义骨架屏的样式,通常是使用灰色或者其他淡色调来绘制基本结构。你可以使用占位符元素、伪类选择器或者内联样式来实现这些效果。
  3. 控制显示与隐藏:借助JavaScript或者其他前端框架,在页面加载时将对应的骨架屏样式应用到页面上,并在实际内容加载完成后切换回真实内容。这可以通过动态添加/移除CSS类名、使用条件渲染(例如Vue.js中的v-if指令)等方式来实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>  
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
/* 配置骨架屏样式 */
.skeleton {
background-color: #f2f2f2;
border-radius: 4px;
margin-bottom: 10px;
}

/* 实际内容容器 */
.content {
display: none; /* 默认隐藏 */
}
</style>
</head>
<body>
<!-- 需要展示成为骨架屏的部分 -->
<div class="skeleton" id="skeleton1"></div>

<!-- 实际内容 -->
<div class="content" id="realContent">
<!-- 这里放置真正的网页内容 -->
...
</div>

<script>
// 模拟数据加载延迟
setTimeout(function() {
// 数据加载完成后显示真正内容
document.getElementById('realContent').style.display = 'block';
// 移除对应位置上原有的 skeleton 元素
var element = document.getElementById('skeleton1');
element.parentNode.removeChild(element);
}, 3000); // 模拟3秒钟后数据加载完毕
</script>

</body>
</html>

鼠标穿透属性

1
2
3
4
.popup{
// 可以将被该层的元素遮挡的鼠标事件穿透过去,不被遮挡
pointer-events: none
}

css动画

1
<div class="loader"></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.loader {  
width: 50px;
height: 50px;
border: 8px solid #f3f3f3;
border-top: 8px solid #3498db;
border-radius: 50%;
animation: spin 1s infinite linear;
}

@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

在这个案例中,一个名为 .loader<div> 元素被创建用作加载动画的容器。通过设置元素的边框样式,以及使用 border-top 属性指定一个特定的颜色,在视觉上形成一个带有旋转效果的圆形。关键帧动画 spin 定义了从运动开始到结束的过渡效果。

在 CSS 中,通过 @keyframes 规则定义了动画的关键帧。在这个案例中,spin 动画从初始状态(0%)开始,没有旋转(0deg),到最终状态(100%),完整旋转一圈(360deg)。animation 属性将该动画应用于 .loader 元素,并设置动画的持续时间为 1 秒,并且设置为无限循环播放。

其他属性:

  1. translate(): 平移元素,根据给定的距离在水平和垂直方向上移动元素。例如,translate(50px, 100px) 会将元素向右移动 50px,向下移动 100px。
  2. rotate(): 旋转元素,在给定的角度下沿顺时针方向旋转元素。角度单位可以是度数(deg)或弧度(rad)。例如,rotate(45deg) 会将元素顺时针旋转 45 度。
  3. scale(): 缩放元素,根据给定的比例在水平和垂直方向上放大或缩小元素。例如,scale(2, 1.5) 会将元素在水平方向上放大 2 倍,在垂直方向上放大 1.5 倍。
  4. skew(): 倾斜元素,通过给定的角度值将元素沿着 X 和 Y 轴进行倾斜。角度单位可以是度数(deg)或弧度(rad)。例如,skew(30deg, -10deg) 会将元素在 X 轴方向上逆时针倾斜 30 度,在 Y 轴方向上顺时针倾斜 10 度。
  5. matrix(): 使用 2D 变换矩阵变换元素。可以通过指定 6 个值来进行矩阵变换。矩阵变换可以实现多种复杂的变换效果。

SCSS

  1. SCSS 与 CSS 的区别:要求应聘者解释 SCSS 与纯 CSS 之间的区别,以及为什么使用 SCSS 的优势。这可能涉及到 SCSS 提供的变量、嵌套、混合、继承等功能,以及它们对代码组织和可维护性的影响。
  2. SCSS 中的变量:询问应聘者如何在 SCSS 中声明和使用变量,并要求举例说明如何使用变量提高样式表的可维护性。
  3. 嵌套:考察应聘者对于在 SCSS 中如何利用嵌套来组织样式规则,并讨论其优点和缺点。也可以问一些关于嵌套过度导致选择器权重过高或产生不必要复杂性等情况下可能遇到的问题。
  4. 混合(Mixins):询问应聘者如何创建和使用 SCSS 混合,以及混合与函数之间有什么不同之处。
  5. 继承(Inheritance):了解应聘者对于在 SCSS 中如何利用继承来减少重复代码并提高样式表可读性方面所持有观点。
  6. 函数与指令(Functions and Directives): 考察应聘者是否熟悉 SassScript 函数以及编写自定义指令时需要注意哪些事项。
  7. 导入其他文件:询问候选人他们是如何在项目中管理多个 SCSS 文件,并探讨他们认为最佳实践是什么?
  8. SCSS 的工作原理:考虑向候选人提出关于编译器将 SCSS 转换为纯 CSS 的工作原理相关问题。

js

说说js中的数据类型,基本数据类型和引用数据类型区别,以及存储上的差别

JavaScript中的数据类型分为两大类:基本数据类型(原始数据类型)和引用数据类型。

基本数据类型

基本数据类型在内存中存储的是值,包含以下几种:

  1. Undefined:表示一个未定义的值,默认值为undefined
  2. Null:表示一个空值,意为“无”。
  3. Boolean:布尔值,只有两个值:truefalse
  4. Number:表示数字,包括整数和浮点数,所有数字都采用64位浮点数表示。
  5. BigInt:表示可以表示任意大小的整数,超出Number的安全范围(Number.MAX_SAFE_INTEGER)。
  6. String:表示字符序列,用于存储文本。
  7. Symbol:表示唯一且不可变的值,在ES6引入,用于对象属性的唯一性。

引用数据类型

引用数据类型存储的是对象的引用或指针,而不是对象本身。主要包括:

  1. Object:最基本的引用数据类型,可以用来存放键值对。
  2. Array:特殊的对象,用于存放有序列表,可以通过索引访问。
  3. Function:JavaScript中的函数也是对象,因此也是引用数据类型。
  4. DateRegExp等:这些都是特定类型的对象。

存储上的差别

  1. 存储方式
    • 基本数据类型:直接存储在栈(Stack)内存中。由于是固定大小,访问速度快。
    • 引用数据类型:存储在堆(Heap)内存中,变量保存的是引用地址。访问速度相对较慢。
  2. 复制方式
    • 基本数据类型:赋值时,会创建一个新的副本,例如:let a = 10; let b = a;,此时ab是两个独立的值。
    • 引用数据类型:赋值时,实际上是复制了引用,比如:let obj1 = {name: 'Alice'}; let obj2 = obj1;,此时obj1obj2指向同一个对象,修改其中一个会影响另一个。
  3. 类型检查
    • 基本数据类型直接用typeof来判断。
    • 引用数据类型在判断时需要特别注意,因为数组和对象在typeof下都是object,可以使用Array.isArray()来判断数组。

隐式转换

将一种数据类型自动转换为另一种数据类型而不需要显式地调用转换函数。

  • 字符串连接

    1
    2
    3
    let a = 10;  
    let b = "20";
    console.log(a + b); // 输出 "1020",此时数字 10 隐式转换为字符串 "10",然后与字符串 "20" 连接。
  • 比较操作符

    1
    2
    3
    4
    5
    let x = 10;  
    //因为使用了双等号进行比较时,JavaScript 会进行隐式转换使得数字 10 转化为字符串 "10"。
    if (x == "10") {
    console.log("相等");
    }
  • 数学运算

    1
    2
    let c = 5 - '3';   
    console.log(c); // 输出2, '3'被隐式转化成了数字3
  • 布尔值上下文

    1
    let d= !!"hello"; // true, !! 可以将任何值强制隐式地变成布尔值。

==(相等运算符)和 ===(严格相等运算符)

== ,在比较之前,会进行类型转换(隐式转换),即使两个值的类型不同,JavaScript 也会尝试将它们转换为相同的类型进行比较

===,不进行类型转换,只有当两个值的类型相同且值相等时,才返回 true,可以避免潜在的错误

数组方法

常用方法:

  1. push():在数组末尾添加一个或多个元素,并返回新的长度。
  2. pop():删除并返回数组删除的元素。
  3. shift():删除并返回数组删除的元素。
  4. unshift():在数组开头添加一个或多个元素,并返回新的长度。
  5. concat():连接两个或更多数组,并返回新数组。
  6. slice(start, end):从现有数组中返回选定的元素。

特殊方法:

  1. map(callback):

    • 这个方法会对数组中的每个元素都执行回调函数,并将回调函数返回的结果组成一个新的数组返回。
    • 示例:
    1
    2
    3
    const numbers = [1, 2, 3, 4];  
    const doubledNumbers = numbers.map(num => num * 2);
    // doubledNumbers 现在是 [2, 4, 6, 8]
  2. filter(callback):

    • 这个方法会根据指定函数过滤原始数据,通过回调函数的boolean,得到符合条件数据组成新数组返回。
    • 示例:
    1
    2
    3
    const numbers = [1, 2, 3, 4];  
    const evenNumbers = numbers.filter(num => num % 2 ===0);
    // evenNumbers 现在是 [2,4]
  3. reduce(callback [, initialValue]):

    • 这个方法通过提供一个函数来累积所有值以生成单个值。
    • 它接受一个回调函数和一个初始值作为参数,并且这个回调函数接收了上一次调用得到 的结果(或者初始值)和当前元素作为参数。
    • 示例:
    1
    2
    3
    const numbers = [1, 2 ,3 ,4];  
    const sum = numbers.reduce((accumulater,currentValue)=> accumulater+currentValue ,0 );
    // sum现在等于10

数组扁平化和数组去重

数组扁平化:

数组扁平化是将多维数组转换为一维数组的过程。这在处理嵌套数组时非常有用。以下是一些实现方式:

  1. 使用 flat() 方法:flat() 方法可以将嵌套数组展开指定的层数。
1
2
3
const nestedArray = [1, [2, 3], [4, [5]]];  
const flattenedArray = nestedArray.flat(Infinity);
// flattenedArray 现在是 [1, 2, 3, 4, 5]
  1. 使用递归函数:可以编写一个递归函数来处理嵌套数组。
1
2
3
4
5
6
7
function flattenArray(arr) {  
return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenArray(val)) : acc.concat(val), []);
}

const nestedArray = [1, [2, 3], [4, [5]]];
const flattenedArray = flattenArray(nestedArray);
// flattenedArray 现在是 [1, 2, 3, 4, 5]

数组去重:

数组去重是指从数组中移除重复的元素,只保留唯一的元素。以下是一些实现方式:

  1. 使用 Set:可以使用 Set 数据结构来创建一个不重复的数组。
1
2
3
const array = [1, 2, 2, 3, 4, 4, 5];  
const uniqueArray = Array.from(new Set(array));
// uniqueArray 现在是 [1, 2, 3, 4, 5]
  1. 使用 filter()indexOf():可以使用 filter() 方法结合 indexOf() 方法来过滤重复的元素。
1
2
3
const array = [1, 2, 2, 3, 4, 4, 5];  
const uniqueArray = array.filter((value, index, self) => self.indexOf(value) === index);
// uniqueArray 现在是 [1, 2, 3, 4, 5]

forEach 和 map 区别

forEachmap 都是用于对数组进行遍历和处理的方法

  • 如果只需要循环遍历并处理每个元素而不需要生成新的数据,则可以使用 forEach
  • 如果希望对原始数据进行映射或转换,并且获得一个新的处理后数据集合,则可以使用 map

forEach:

  • forEach 方法是数组的原生方法,它接受一个函数作为参数,并对数组中的每个元素执行该函数。
  • 它没有返回值(或者说返回值为 undefined),因此主要用于遍历数组并对其中的元素执行一些操作。
  • 由于没有返回值,无法通过 forEach 直接得到一个新的数组。

示例:

1
2
3
4
5
const array = [1, 2, 3, 4];  
array.forEach((element) => {
console.log(element);
});
// 输出:1、2、3、4

map:

  • map 方法也是数组的原生方法,它接受一个函数作为参数,并对数组中的每个元素执行该函数。不同之处在于,它会将处理后得到的结果组成一个新数组并返回。
  • 可以使用 map 方法将原始数组映射/转换为新的数组。
  • 对原始数据进行映射或转换时非常有用。

示例:

1
2
3
const array = [1, 2, 3, 4];  
const doubledArray = array.map((element) => element * 2);
// doubledArray 现在是 [2, 4, 6, 8]

字符串常用方法

  1. length:返回字符串的长度。
1
2
const str = "Hello";  
const length = str.length; // length 现在等于 5
  1. charAt(index) 或者 [index]:返回指定索引位置的字符。
1
2
3
4
const str = "Hello";  
const firstChar = str.charAt(0); // firstChar 现在等于 "H"
// 或者
const firstChar2 = str[0]; // firstChar2 现在等于 "H"
  1. toUpperCase()toLowerCase():将字符串中的字母转换为大写或小写。
1
2
3
4
5
const str1 = "hello";  
const upperCaseStr1 = str1.toUpperCase(); // upperCaseStr1 现在等于 "HELLO"

const str2 = "WORLD";
const lowerCaseStr2 = str2.toLowerCase(); // lowerCaseStr2 现在等于 "world"
  1. indexOf(substring)lastIndexOf(substring):查找子串第一次出现和最后一次出现的位置(索引)。
1
2
3
const sentence = 'The quick brown fox jumps over the lazy dog';  
console.log(sentence.indexOf('fox')); // 输出 16
console.log(sentence.lastIndexOf('the')); // 输出 35
  1. replace(searchValue, replaceValue):替换字符串中某个子串为新值。
1
2
3
4
5
let message ='I am learning JavaScript';  

let newMessage= message.replace('JavaScript', 'Python');

console.log(newMessage);//输出: I am learning Python
  1. split(separator)拆分一个字符串成数组,通过指定分隔符。
1
2
3
4
5
const sentence="Welcome to our website";  

const words=sentence.split(" ");

console.log(words);//输出["Welcome", "to", "our", "website"]

JavaScript 中常用的对象方法

  1. Object.keys(obj):返回一个包含对象自身属性(不包括继承属性)的键的数组。
  2. Object.values(obj):返回一个包含对象自身属性值(不包括继承属性)的值的数组。
  3. Object.entries(obj):返回一个包含对象自身属性键值对(不包括继承属性)的数组。
  4. Object.assign(target, source):将一个或多个源对象的所有可枚举属性复制到目标对象,并返回目标对象。
  5. Object.create(proto):使用指定的原型对象创建一个新对象。
  6. Object.defineProperty(obj, prop, descriptor):定义一个新的属性或修改一个已经存在的属性,并返回该对象。
  7. Object.getOwnPropertyDescriptor(obj, prop):返回指定对象上一个自有属性对应的属性描述符。
  8. Object.entries(obj):返回一个给定对象自身可枚举属性的键值对数组。
  9. Object.freeze(obj):冻结一个对象,使其属性不可修改。
  10. Object.fromEntries(iterable):将键值对列表转换为一个对象。
  11. Object.getOwnPropertyNames(obj):返回指定对象的所有自身属性的属性名。
  12. Object.prototype.hasOwnProperty(prop):判断对象是否具有指定属性。

typeof和instanceof 区别

  • **typeof**: 在 JavaScript 中,原生数据类型(如字符串、数字、布尔值等)使用typeof 或 === 来判断
    • 返回一个字符串,表示变量的类型。
    • 适用于基本数据类型和函数类型的检查。
    • 对于对象和数组返回的都是 "object",不会提供细粒度的信息。
  • **instanceof**:对于非原生数据类型,例如对象、数组、函数等,可以使用 instanceof 运算符来判断其类型。instanceof 运算符可以检查一个对象是否是某个构造函数或类的实例。
    • 返回一个布尔值,表示对象是否是特定构造函数的实例。
    • 适用于所有对象和自定义类型的检查,能够准确检测对象类型。

typeof null 返回的是 “object”,为什么

这是因为在 JavaScript 中,null 被认为是一个空的对象引用。它的实际类型是原始值类型,但是 typeof null 返回 “object” 是出于历史原因造成的。

js原型?原型链?有什么特点?

原型(prototype)

  • 原型:JavaScript 中的原型(prototype)是一种对象间的关联机制,是实现继承和属性共享的基础。
  • 特点:
    1. 原型是对象与对象之间的关系,本质上是一种引用关系。
    2. 能够实现对象属性和方法的共享,提高代码的重用性和效率。
    3. 当访问一个对象的属性或方法时,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法。

原型链(prototype chain)

  • 原型链:原型链是由对象的原型与对象组成的链式结构,直到到达原型链的顶端(即 Object.prototype
  • 特点:
    1. 每个对象都有一个指向其原型的链接,即 __proto__ 属性。
    2. 当访问一个对象的属性或方法时,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法所在的原型对象。
    3. 原型链会一直追溯下去直至 Object.prototype 对象 :person1 —> Person.prototype —> Object.prototype —> null

Function.prototype

在 JavaScript 中,Function.prototype 是一个特殊的对象,它是所有函数的原型。当我们创建一个函数时,该函数会自动关联到 Function.prototype。这意味着所有函数对象都可以访问 Function.prototype 上的方法和属性。
Function.prototype 指向的是一个包含常用函数方法的空函数对象,这个对象是所有函数对象的原型,用于存放和共享函数对象所需的方法和属性。

作用域和作用域链的理解

作用域

JavaScript 中的作用域主要分为以下几种:

  1. 全局作用域(Global Scope):全局作用域是指在代码中任何地方都能访问的作用域。在浏览器环境中,全局作用域是指 window 对象。
  2. 函数作用域(Function Scope):函数作用域是指在函数内部声明的变量只能在该函数内部访问。函数外部无法直接访问函数内部的变量。
  3. 块级作用域(Block Scope):块级作用域是指由一对花括号 {} 定义的范围内有效的作用域。在 ES6 之前,JavaScript 中没有块级作用域,变量的作用域只限于函数内部。但是在 ES6 引入了 letconst 关键字,使得可以在块级作用域内声明变量。
  4. 词法作用域(Lexical Scope):词法作用域是指变量的作用域由代码中变量声明的位置决定,与调用位置无关。JavaScript 使用词法作用域,也就是静态作用域。
  5. 闭包(Closure):闭包是指函数和其词法环境的组合,可以访问外部函数中定义的变量。通过闭包

作用域链

当在一个函数中查找变量时,JavaScript 引擎会首先在当前函数作用域中查找。如果找不到,它会向外部作用域(外皮函数或全局作用域)查找,直到找到该变量或确定该变量未定义为止。这个查找过程形成了一个链式结构,称为“作用域链”。

作用域链式js查找函数和变量的一种机制,作用域链是由当前执行环境的变量对象和父级执行环境的变量对像组成的

作用域链查找过程

  1. 当前执行的代码块(内层作用域)。
  2. 外层函数的作用域(依次向上查找)。
  3. 全局作用域。

普通函数和箭头函数的区别

  1. 语法形式:普通函数使用function关键字定义,箭头函数使用箭头符号(=>)定义。
  2. this指向:箭头函数没有自己的this,它会继承外部作用域的this。而普通函数的this是在运行时绑定的,取决于函数的调用方式。
  3. arguments对象:箭头函数没有自己的arguments对象,而是继承外部作用域的arguments。普通函数有arguments对象。
  4. new关键字:普通函数可以作为构造函数使用,通过new关键字创建实例对象。而箭头函数不能被用作构造函数。
  5. 返回值:箭头函数对于只有一行返回语句的函数,可以省略{}和return关键字。普通函数没有这种简写形式。

谈谈对this的理解

  • this 是一个特殊的关键字,它并不指向函数本身,而是指向调用该函数的上下文(即函数被调用的位置)。
  • 在不同的上下文中,this 的值会有所不同,这取决于函数被调用的方式。
  • 全局上下文:在全局执行环境中,this 指向全局对象(在浏览器中是 window 对象,在 Node.js 中是 global 对象)

this 的值的确定方式

  • 函数调用:在普通函数中,this 指向调用该函数的对象。如果是直接调用,this 会指向全局对象(非严格模式下)。
  • 对象的方法:当一个函数作为对象的方法被调用时,this 指向该对象。
  • 构造函数:在构造函数中,this 指向新创建的实例。
  • ES6 箭头函数:箭头函数不具备自己的 this,它继承自外部函数的 this
  • 手动绑定:可以使用 callapplybind 方法来手动指定 this 的值。

常见的陷阱

  • 严格模式:在严格模式下,未绑定的 this 会是 undefined 而不是全局对象。
  • 事件处理函数:在事件处理程序中,this 通常指向触发事件的元素。
  • 不适合作为构造函数:箭头函数没有自己的 this 值,不能使用 new 关键字来调用,也不能作为构造函数来创建实例对象。
  • 不适合在对象方法中:箭头函数的 this 始终指向定义箭头函数时的外层作用域的 this 值,而不是调用时的对象。因此,如果需要在对象方法中使用 this,应该使用普通函数。
  • 不适合在需要动态 this 值的场景:箭头函数无法改变其 this 的指向,因此不适合在需要动态绑定 this 值的情况下使用。

callapplybind 方法的区别

方法 功能 参数传递方式 返回值 立即执行
call 调用函数并指定 this 依次传递独立的参数
apply 调用函数并指定 this 以数组传递参数
bind 创建一个新函数,指定 this 和参数 指定的参数 返回新函数
  • bind()
1
2
3
4
5
6
7
8
9
10
const obj = {  
name: 'Alice'
};

function sayHello() {
console.log(`Hello, ${this.name}`);
}

const boundFunc = sayHello.bind(obj);
boundFunc(); // 输出:Hello, Alice
  • apply() / call()
1
2
3
4
5
6
7
8
9
10
const obj = {  
name: 'Alice'
};

function sayHello() {
console.log(`Hello, ${this.name}`);
}

sayHello.call(obj); // 输出:Hello, Alice
sayHello.apply(obj); // 输出:Hello, Alice

其他修改this指向的方法

  • 使用 ES6 的解构赋值
1
2
3
4
5
6
7
8
9
const obj = {  
name: 'Alice',
sayHello() {
console.log(`Hello, ${this.name}`);
}
};

const { sayHello } = obj;
sayHello(); // 输出:Hello, Alice

ajax的原理

AJAX(Asynchronous JavaScript and XML)是一种用于在不重新加载整个网页的情况下向服务器发送和接收数据的技术。使用 AJAX,可以通过 JavaScript 发送异步请求,从而使用户能够与页面交互而无需等待整个页面刷新

  1. 使用 XMLHttpRequest 对象:JavaScript 中的 XMLHttpRequest 对象是实现 AJAX 的基础。通过创建一个新的 XMLHttpRequest 对象,可以发送 HTTP 请求到服务器,并在后台处理响应。
  2. 异步通信:AJAX 使用异步方式发送和接收数据,这意味着浏览器会在不阻塞其他操作的情况下进行数据通信。这样用户可以继续与页面交互,同时浏览器会在后台进行数据传输和处理。
  3. 服务器端处理请求:当客户端发送一个 AJAX 请求时,服务器会接受并处理该请求。通常情况下,服务器将返回 JSON、XML 或纯文本等格式化数据作为响应。
  4. 更新页面内容:一旦客户端收到来自服务器的响应数据,在获取到结果后就能够使用 JavaScript 动态地更新网页上对应的部分内容或执行特定操作。
  5. 回调函数机制:由于异步请求是非阻塞式执行, 因此在获取到响应之前无法直接得知结果。因此,在发起异步请求时需要注册回调函数,并当请求完成时执行相应回调函数以处理返回结果。

axios的原理

Axios 是一个基于 Promise 的 HTTP 客户端,它提供了一种简单、易用、灵活和强大的方式来发送 HTTP 请求和处理响应。

  1. 基于 XMLHttpRequest 对象:Axios 使用 XMLHttpRequest 对象来发送 HTTP 请求和接收响应。它提供了一种简单、易用、灵活和强大的方式来处理 HTTP 请求和响应。
  2. 支持 Promise API:Axios 基于 Promise API,可以使用 Promise 的 then() 和 catch() 方法来处理异步请求和响应。这使得代码更加简洁易读,并且可以避免回调地狱的问题。
  3. 支持拦截器:Axios 支持请求和响应拦截器,可以在请求和响应被发送或接收之前或之后对它们进行处理。这使得可以在请求和响应中添加通用的处理逻辑,例如添加请求头、处理错误等。
  4. 支持取消请求:Axios 支持取消请求,可以在请求被发送之前或之后取消它们。这使得可以在需要时取消不必要的请求,从而提高性能和用户体验。要取消请求,只需调用 source.cancel() 方法
  5. 支持并发请求:Axios 支持并发请求,可以同时发送多个请求,并在所有请求完成后处理它们的响应。这使得可以在需要时同时发送多个请求,从而提高性能和用户体验。基于pomise.all() ;Axios使用 axios.all()axios.spread() 方法来发送并发请求。

如何判断一个元素在可视区域内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 判断一个元素是否在可视区域内的方法  
function isInViewport(element) {
// 获取元素相对于视口的位置信息
const rect = element.getBoundingClientRect();

// 判断元素的四个边界是否都在视口内,如果是则返回 true,否则返回 false
return (
rect.top >= 0 && // 元素顶部在视口内(大于等于0)
rect.left >= 0 && // 元素左边在视口内(大于等于0)
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && // 元素底部在视口内(小于等于视口高度)
rect.right <= (window.innerWidth || document.documentElement.clientWidth) // 元素右边在视口内(小于等于视口宽度)
);
}

// 使用示例
const element = document.getElementById('yourElementId'); // 获取需要检测的元素
if (isInViewport(element)) { // 调用 isInViewport 函数判断该元素是否在可见区域内,并进行相应处理
console.log('Element is in the viewport'); // 如果元素在可见区域内,则输出提示信息到控制台
} else {
console.log('Element is not in the viewport'); // 如果元素除了可见区域外,则输出提示信息到控制台
}

函数柯里化

柯里化的主要思想是,将一个多参数的函数转换为一系列只接受单个参数的函数,这些函数通过闭包的方式“记住”之前传递的参数,最后再返回一个值或执行某些操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function add(x) {  
return function(y) {
return x + y;
};
}

// 使用柯里化的add函数
const add5 = add(5);
console.log(add5(2)); // 输出:7

// 也可以一次性传入所有参数
console.log(add(5)(2)); // 输出:7
/*
add 是一个接受一个参数 x 的函数,返回一个接受参数 y 的函数。通过柯里化,我们可以先传入部分参数创建一个新的函数(如 add5 = add(5)),然后再传入剩余的参数来调用这个函数。

函数柯里化在函数式编程中有很多应用,比如可以用来简化函数的调用方式、方便数据处理和组合等。通过柯里化,我们可以更灵活地使用函数和处理数据。
*/

for…in… (可枚举属性)和 for…of… 区别(迭代器)

  1. for…in
    • 适用于遍历对象(Object)的属性名。
    • 在遍历时会包括对象自身的可枚举属性以及继承得到的可枚举属性。性能开支会比for…of…大
    • 通常不建议直接使用 for...in 来遍历数组,因为它可能会遍历到数组原型链上的其他属性。
1
2
3
4
5
6
7
8
const person = {  
name: 'Alice',
age: 30
};

for (let key in person) {
console.log(key, person[key]);
}
  1. for…of
    • for...of 循环是 ES6 引入的用于迭代可迭代对象(如数组、字符串、Set、Map 等)的语法。
    • 主要用于循环可迭代对象(iterable objects),比如数组、字符串、Map、Set 等。不支持普通对象(对象没有迭代器)
    • 在循环时会访问到对象中具体的值,而不是键或索引。
1
2
3
4
5
const colors = ['red', 'green', 'blue'];  

for (let color of colors) {
console.log(color);
}

js DOM事件模型

事件

  • click:鼠标点击事件
  • mouseover:鼠标悬停事件
  • keydown:键盘按下事件
  • submit:表单提交事件

事件监听器

  • addEventListener 方法来注册事件监听器

事件对象

  • 当事件发生时,浏览器会创建一个事件对象(event),该对象包含事件的相关信息(如事件类型、目标元素、鼠标位置等)。

事件冒泡与事件捕获

可以通过 addEventListener 的第三个参数来控制事件处理的阶段
element.addEventListener(‘click’, ()=>{}, true);如果设置为 true,则使用捕获模式;如果设置为 false 反之
默认是 false,即采用事件冒泡模式。

  • 事件捕获:事件从外部元素向内部元素传递(从文档根节点到目标元素)。
  • 事件冒泡:事件从目标元素向外部元素传递(从目标元素到文档根节点)。

事件委托

利用事件冒泡机制, 允许将事件处理程序添加到父元素上。将事件监听器绑定到父元素。

场景:1. 列表或菜单 2. 动态生成内容 3. 多个表单元素

防止默认行为

阻止事件的默认行为(例如,防止表单提交)。可以通过调用事件对象的 preventDefault() 方法来实现

停止事件传播

通过调用事件对象的 stopPropagation() 方法来停止事件的传播,防止事件冒泡到父元素。

创建对象有哪些方法

  1. 使用对象字面量:这是最简单的创建对象的方法,通过花括号 {} 来定义对象,并填入属性和方法。
1
2
3
4
5
6
7
const person = {  
name: 'Alice',
age: 25,
greet() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
};
  1. 使用构造函数:可以使用构造函数来创建对象。通过 new 关键字和构造函数来实例化新的对象。
1
2
3
4
5
6
7
8
9
10
function Person(name, age) {  
this.name = name;
this.age = age;

this.greet = function() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
};
}

const person1 = new Person('Bob', 30);
  1. 使用 Object 构造函数:可以使用内置的 Object 构造函数来创建空对象,然后逐个添加属性和方法。
1
2
3
4
5
6
const person = new Object();  
person.name = 'Charlie';
person.age = 22;
person.greet = function() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
};
  1. 使用原型:通过定义一个原型(prototype)并将其与一个构造函数相关联,我们可以共享属性和方法,并且每个实例都有对它们的引用。
1
2
3
4
5
6
7
8
9
10
function Person(name, age) {  
this.name = name;
this.age=age;
}

Person.prototype.greet=function(){
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
};

let person=new Person('David',28);
  1. ES6 中的类 :ES6 引入了类(class)语法糖,更容易地定义并继承 JavaScript 中的类。
1
2
3
4
5
6
7
8
9
10
11
12
class Person{  
constructor(name,age){
this.name=name,
this.age=age
}

greet(){
console.log(`Hello, my name is ${this._name} and I'm ${this._age}`)
}
}

let p1=new Person('Eve',26)

new 操作符具体操作

  1. 创建一个空对象,这个对象会成为函数的实例。
  2. 将这个空对象的原型指向构造函数的原型对象。
  3. 将构造函数中的 this 绑定到这个新的实例对象。
  4. 执行构造函数内部的代码。

什么数据存在对象中,什么数据存在prototype中

  • 存在对象本身中的数据:通常是实例特有的数据,每个对象实例都有自己的一份数据副本。这些数据可以通过对象的属性来访问和修改,属于对象的内部状态。
  • 存在 prototype 中的数据:通常是对象共享的数据,被所有该类型对象实例共享。这些数据一般是方法或共享的属性,定义在对象的原型上,可以被所有该类型的实例访问。当我们访问一个对象的属性时,如果该属性不在对象本身中,则会去原型链上查找。

如何判断a是不是b的实例

在 JavaScript 中,可以使用 instanceof 运算符来判断一个对象是否是另一个对象的实例。

1
console.log(person1 instanceof Person); // 输出 true

深拷贝和浅拷贝区别

浅拷贝

浅拷贝是指创建一个新对象,普通数据类型属性会被拷贝,但引用类型变量将指向同一个引用,且这种方式只复制一层的属性。

常用方法: Object.assign(), 使用扩展运算符, 数组的 slice()concat()

深拷贝

深拷贝是指创建一个新对象,这个新对象完全复制原始对象的所有属性,包括嵌套对象的属性。深拷贝的结果是新对象与原始对象之间没有任何引用关系,彼此独立

常用方法:

  • 使用 JSON.stringify()JSON.parse()(有局限性)

    • 使用 JSON.parse(JSON.stringify()) - 序列化

      深拷贝时,可能会丢失以下信息:

      • 不支持 函数、正则表达式、日期对象、undefined、symbol等。
      • 如果对象之间存在引用关系会导致无法正确的序列化和反序列化。
  • 手动递归拷贝

  • 使用第三方库,如 Lodash 的 _.cloneDeep()

  • 对数组的深拷贝还可以使用 扩展运算符

json.stringify() 是干什么的,什么时候会使用到

JSON.stringify() 方法是用于将 JavaScript 对象或值转换为 JSON 字符串的方法。它接受一个对象或数组作为参数,然后返回对应的 JSON 字符串表示。

  1. 数据传输: 在客户端和服务器之间进行数据传输时,通常需要将 JavaScript 对象转换为 JSON 字符串进行传输。这样可以方便地在不同系统之间交换数据。
  2. 本地存储: 在浏览器端使用 localStoragesessionStorage 存储数据时,通常需要将 JavaScript 对象转换为 JSON 字符串再存储,以便在取出时重新解析为对象。
  3. 日志记录: 在记录日志或调试信息时,有时会将对象转换为 JSON 格式以便于查看和分析。
  4. 与后端交互: 在前端向后端发送数据时,常常会将 JavaScript 对象转换为 JSON 字符串,以便后端能够正确解析和处理数据。

防抖和节流,及应用场景

防抖和节流都是用于控制函数执行频率的技术,它们的应用场景如下:

  1. 防抖(Debouncing):防抖是指在一段时间内,多次触发同一个函数,只执行最后一次触发的函数。防抖通常用于处理用户输入,比如搜索框输入、滚动事件等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function debounce(func, delay) {  
let timer;
return function() {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(context, args);
}, delay);
};
}

const searchInput = document.querySelector('#search-input');
searchInput.addEventListener('input', debounce(handleSearch, 500));
  1. 节流(Throttling):节流是指在一段时间内,多次触发同一个函数,只执行一次函数。节流通常用于处理高频事件,比如鼠标移动、窗口调整等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function throttle(func, delay) {  
let timer;
return function() {
const context = this;
const args = arguments;
if (!timer) {
timer = setTimeout(() => {
func.apply(context, args);
timer = null;
}, delay);
}
};
}

window.addEventListener('resize', throttle(handleResize, 500));

大量加载数据怎么优化,怎么避免滚动条滚动

  1. 分页加载:将数据分成多个页面,只在用户需要时加载当前页面的数据。这样可以减少一次性加载大量数据带来的性能问题。
  2. 虚拟滚动:使用虚拟滚动技术,只渲染可见区域内的部分数据,而不是全部数据。这种方式可以减少 DOM 元素数量,提高页面渲染性能。
  3. 懒加载:在用户向下滚动时才进行数据加载,而不是一开始就加载所有内容。这样可以延迟实际需要显示的内容的加载时间。
  4. 增量式加载:根据用户操作实时请求新的数据,并将它们添加到已有列表中,而不是重新获取整个列表。
  5. 前端缓存:如果可能,在前端进行缓存以避免重复请求相同资源,并且优先从缓存中获取所需信息。
  6. 后端优化:对于大型数据库查询或跨网络请求,请确保后端服务具备高效率和合理响应时间。
  7. 索引和分片(针对数据库): 如果你使用数据库,则确保表上有适当地索引以提高检索速度,并且考虑将大表切割成小片段以加快查询速度。
  8. 压缩传输: 如果你从后端服务器获得了大量静态资源(如图片、视频等),请确保它们被适当地压缩和传输到客户端,以减小网络负载和加快下载速度。

图片懒加载和列表虚拟滚动怎么实现

图片懒加载

  1. 将页面上的图片的 src 属性设置为占位符,例如一个空白的透明图片。
  2. 监听页面滚动事件,或者使用 Intersection Observer API 监听图片元素是否进入可视区域。
  3. 当图片进入可视区域时,将图片的 src 属性设置为实际的图片地址,从而触发图片加载。

列表虚拟滚动

  1. 获取列表的总高度和每个列表项的高度。
  2. 根据可视区域的高度计算出可见的列表项数量。
  3. 根据滚动位置计算出当前可见的列表项的起始索引。
  4. 只渲染当前可见的列表项,而不渲染整个列表。
  5. 随着用户滚动列表,动态更新可见列表项的内容。

闭包

闭包的核心概念是函数和其词法环境的组合。在函数内部定义函数,函数内部引用外部作用域的变量,形成闭包。
应用场景:1. 保存状态 2.封装私有变量 3. 延迟执行 3.模块化

原生js创建自定义事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建一个名为 "customEvent" 的自定义事件  
const customEvent = new Event('customEvent');

// 在需要监听该事件的地方添加监听器
document.addEventListener('customEvent', function(event) {
console.log('Custom event triggered');
});

// 触发自定义事件
document.dispatchEvent(customEvent);

//还可以使用 CustomEvent 构造函数来创建包含更多信息(如数据)的更复杂的自定义事件:
const eventData = { message: 'Hello, this is a custom event' };
const customEvent = new CustomEvent('customEvent', { detail: eventData });

document.addEventListener('customEvent', function(event) {
console.log(`Custom event triggered with message: ${event.detail.message}`);
});

document.dispatchEvent(customEvent);

文件上传怎么做预览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<html>
<body>
<input type="file" id="fileInput" accept="image/*">
<div id="preview"></div>
</body>
<script>
// 获取文件输入元素和预览容器
const fileInput = document.getElementById('fileInput');
const preview = document.getElementById('preview');

// 监听文件输入元素的变化事件
fileInput.addEventListener('change', function() {
// 获取用户选择的文件
const file = fileInput.files[0];
// 创建 FileReader 对象
const reader = new FileReader();

// 当读取完成时触发 onload 事件
reader.onload = function(e) {
// 创建新的 Image 对象
const img = new Image();
// 将读取的文件内容设置为图片的 src 属性
img.src = e.target.result;
// 清空预览容器并添加图片
preview.innerHTML = '';
preview.appendChild(img);
};

// 如果用户选择了文件,则读取文件内容并生成预览
if (file) {
reader.readAsDataURL(file);
}
});
</script>
</html>

文件秒传怎么实现

文件秒传是一种常见的文件上传优化技术,可以在用户重复上传相同文件时,通过检查文件的哈希值或其他标识,实现直接跳过上传步骤,从而大幅提升用户体验。

切片上传

  • 触发input上传事件
  • 获取上传文件对象
  • 创建分片数据
  • 文件内容创建hash
  • 调用发送分片接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<html>  
<body>
<input id="upload" type="file" />
</body>
<script src='https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.min.js'></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.7.2/axios.min.js"></script>
<script>
// 获取元素节点
const input = document.getElementById("upload");
// 监听input
input.addEventListener("change", async function(event) {
// 获取文件信息
const file = event.target.files[0];
const fileName = event.target.files[0].fileName;
// 创建分片 以50M 为一个分片
const chunks = createChunk(file, 50*1024*1024);
// 为上传文件创建hash
const res = await hash(chunks);
// 调用上传接口
uploadChunk(chunks, hash, fileName);
});

// 分片存储
function createChunk(file, chunkSize){
const result = []
for (let i = 0; i<file.size;i += chunkSize){
result.push(file.slice(i, i + chunkSize))
}
return result;
}

// 创建hash值
function hash(chunks){
new Promise((res)=>{
let spark = new SparkMD5()
function _read(index){
if(i >= chunks.length){
resolve(spark.end())
return
}

let blob = chunks[index]
let reader = new FileReader()
reader.onload = e=>{
var byters = e.target.result;
spark.append(bytes)
_read(i+1)
}
reader.readAsArrayBuffer(blob)
}
_read(0)
})
}

// 分片上传
function uploadChunk(chunks, hash, fileName){
// 遍历上传
let taskArr=[];
chunk.forEash((chunk, index)=>{
let formdata = new FromData();
formdata.append('chunk',chunk);
formdata.append('chunkName', `${hash}-${index}-${fileName}`)
formdata.append('fileName',fileName)
let task = axios.post('https://127.0.0.1:3000/', fromdata, {
headers:{
'Content-Type': 'multipart/form-data'
}
})
taskArr.push(task)
})
Promise.all(taskArr)
.then(res=>{
console.log("通知后端,上传完毕,合并数据")
})
.catch(res=>{

})
}
</script>
</html>

一个10g文件怎么进行分片,怎么确定合适的分片大小,分片hash过程怎么优化

实现步骤:

  1. 确定分片大小:分片大小应该根据网络带宽、服务器性能和文件大小等因素进行调整。通常情况下,分片大小应该在 1MB 到 10MB 之间。如果分片过小,会增加网络请求的次数,导致上传或下载速度变慢;如果分片过大,会增加服务器的负担,导致上传或下载失败。
  2. 计算分片数量:根据文件大小和分片大小计算出需要分成多少个分片。
  3. 分片上传或下载:将文件分成多个分片,分别上传或下载。在上传或下载过程中,需要记录每个分片的状态,以便在上传或下载失败时进行重试。
  4. 合并分片:在所有分片上传或下载完成后,将所有分片合并成一个完整的文件。

断点续传

断点续传是指在上传或下载文件时,如果因为网络中断或其他原因导致上传或下载失败,可以从上次失败的位置继续上传或下载,而不需要重新开始。

实现步骤:

  1. 在上传或下载过程中,记录已经上传或下载的字节数。
  2. 如果上传或下载失败,可以使用已经上传或下载的字节数来计算出需要继续上传或下载的位置。
  3. 从上次失败的位置继续上传或下载。
  4. 在上传或下载完成后,将所有分片合并成一个完整的文件。

上拉刷新,下拉加载

使用scroll事件,您可以实现以下功能:

  1. 监听滚动位置:通过scroll事件可以获取滚动条的位置信息,例如滚动条的垂直滚动位置(scrollTop)或水平滚动位置(scrollLeft)。
  2. 实现无限滚动:通过监听scroll事件,可以在滚动到页面底部时自动加载更多内容,实现无限滚动效果。这在需要展示大量数据的网页或应用中很常见。
  3. 实现懒加载:通过监听scroll事件,可以判断元素是否进入可视区域,从而实现图片或其他资源的懒加载。当用户滚动到包含懒加载元素的位置时,再加载相应的内容,以提高页面加载速度和性能。
  4. 实现滚动动画:通过监听scroll事件,可以根据滚动条的位置实现各种滚动动画效果,例如导航栏的固定、滚动时的渐变效果等。

上拉刷新:

  • 当用户向上滑动页面时,检测到滑动距离达到一定阈值时,触发数据的刷新操作。
  • 可以使用JavaScript监听scroll事件来监测页面滚动位置,并在特定条件下执行数据更新操作。
  • 也可以借助第三方插件或框架(如React Native中的FlatList组件)提供的上拉刷新功能。

下拉加载:

  • 当用户向下滑动页面至底部时,触发加载更多内容的操作。
  • 同样可以利用JavaScript监听scroll事件并结合页面高度、滚动位置等信息来实现。
  • 也可以使用第三方库或框架提供的下拉加载功能(比如Vue.js中的Infinite Scroll插件)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<!DOCTYPE html>  
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>上拉刷新、下拉加载示例</title>
<style>
/* 样式用于显示loading状态 */
.loading {
text-align: center;
padding: 20px;
font-weight: bold;
}
</style>
</head>
<body>

<div id="container" style="height:500px; overflow-y:auto;">
<!-- 动态数据内容 -->
<div id="content">
<!-- 这里是动态数据列表 -->
<!-- ... -->
<!-- 加载更多提示信息 -->
<div id="loadMore" class="loading" style="display:none;">向上滑动以加载更多内容...</div>
</div>

</div>

<script type='text/javascript'>
// 模拟的动态数据
let data = ['Item1', 'Item2', 'Item3'];
let currentPage = 1;

// 下拉加载更多
document.getElementById('container').addEventListener('scroll', function() {
if (this.scrollTop + this.clientHeight >= this.scrollHeight) {
loadMore();
}
});

function loadMore() {
document.getElementById('loadMore').style.display = "block";
// 模拟异步请求新数据(假设通过API获取新数据)
setTimeout(function() {
let newData = ['New Item ' + (currentPage * data.length + i) for (i in Array(5))];
// 将新增数据添加到页面中
let contentDiv = document.getElementById('content');
newData.forEach(item => {
let newItemNode = document.createElement("DIV");
newItemNode.innerText = item;
contentDiv.appendChild(newItemNode);
});

currentPage++;

document.getElementById('loadMore').style.display = "none";
}, 1000); // 模拟延时请求
}
</script>

</body>
</html>

web常见攻击方式,如何预防

SQL注入、跨站脚本(XSS)、跨站请求伪造(CSRF)、点击劫持等。

  1. SQL注入:避免使用动态拼接SQL语句,而是使用参数化查询或者ORM框架来处理数据库操作;对用户输入进行严格验证和过滤,确保不含恶意代码。
  2. 跨站脚本(XSS):对用户输入进行HTML编码或转义,以防止恶意脚本在页面中执行;设置安全的HTTP头部来减少XSS风险。
  3. 跨站请求伪造(CSRF):使用随机生成的令牌来验证表单提交和重要操作的合法性;通过SameSite Cookie属性限制Cookie在跨域请求中发送。
  4. 点击劫持:通过设置X-Frame-Options头部或Content Security Policy (CSP) 来防止网页被嵌入到iframe中,并显示一个透明图层以遮挡可能存在于该页面上方其他内容。
  5. 授权和身份验证控制:强化用户认证流程、实施多因素身份验证,并且采用最小权限原则,只给予用户必要的权限。

js执行上下文和执行栈

  • 执行上下文 是代码运行时的环境,可以是全局的或函数的,每个上下文都有自己的变量环境、词法环境和 this 值。
  • 执行栈 是管理执行上下文的结构,负责跟踪当前正在执行的上下文,以及控制函数调用的顺序。

执行上下文

代表了在某个特定地方运行的代码环境。每当 JavaScript 代码运行时,都会创建一个新的执行上下文。

  • 三个主要部分
    • 变量环境
    • 词法环境
    • this
  • 类型
    • 全局执行上下文
    • 函数执行上下文
    • eval 执行上下文

执行栈

  • 工作原理
    • 入栈
    • 出栈
    • 当前上下文

函数缓存

是一种优化技术,用于存储函数计算结果,从而避免重复计算。类似于框架中的计算属性。

应用场景

  1. 计算密集型函数: 耗时的计算
  2. 数据处理或转化:比如格式化日期、字符串处理等,可以缓存已经处理的结果,减少重复处理的开销。
  3. 组件渲染

js数据结构的了解

JavaScript 中常用的数据结构主要包括数组、对象、Set、Map、WeakSet 和 WeakMap

  1. 数组 (Array)
  • 描述:有序的元素集合,可以存储任意类型的值。

  • 特性

    • 动态大小,可以根据需要增加或减少元素。
    • 支持多种数组方法,如 pushpopshiftunshiftmapfilter等。
    • 数组元素的索引从 0 开始。
  • 使用场景:适合用于需要有序存储的数据,例如列表、队列等。

  1. 对象 (Object)
  • 描述:无序的键值对集合,用于存储相关的数据。

  • 特性

    • 键是字符串(ES6 引入了 Symbol 类型作为键),值可以是任意类型。
    • 对象的属性是动态的,可以随时添加、修改或删除。
  • 使用场景:适合用于存储和组织与某个实体相关的信息,例如用户信息、配置选项等。

  1. Set
  • 描述:代表一组唯一值的集合。

  • 特性

    • 不允许重复值,自动去重。
    • 支持基本的集合操作:添加、删除、判断是否存在。
    • 迭代时元素的顺序为插入顺序。
  • 使用场景:适用于需要保证唯一性的数据集合,例如用户活动记录、标签等。

  1. Map
  • 描述:一个键值对的集合,其中键可以是任意类型(不局限于字符串)。

  • 特性

    • 保持插入的顺序。
    • 支持任何类型的值作为键。
    • 提供了 setgethasdelete 等方法来操作键值对。
  • 使用场景:适合用于需要快速查找和更新的场景,例如存储频繁更新的配置或数据。

  1. WeakSet
  • 描述:与 Set 类似,但其元素是弱引用。

  • 特性

    • 只能存储对象类型的值。
    • 当对象没有引用时,它们可能会被垃圾回收。
    • 没有常规的遍历操作。
  • 使用场景:适用于需要存储某些对象的集合,但不想阻止其被垃圾回收的场景。

  1. WeakMap
  • 描述:与 Map 类似,但其键是弱引用。

  • 特性

    • 键只能是对象,值可以是任意类型。
    • 当对象没有其他引用时,可以被垃圾回收。
    • 也没有常规的遍历操作。
  • 使用场景:适用于需要关联某些对象数据,但又不希望阻止这些键对象被垃圾回收的场合。

DOM/BOM常见操作

DOM 提供了访问和操作 HTML 和 XML 文档的标准接口
而 BOM 提供了与浏览器进行交互的方法和接口。

常见 DOM 操作

  1. 选择元素

    • 通过 ID 选择单个元素。
    1
    const element = document.getElementById('myId');  
    • 通过类名选择多个元素。
    1
    const elements = document.getElementsByClassName('myClass');  
    • 通过标签名选择多个元素。
    1
    const elements = document.getElementsByTagName('div');  
    • 通过 CSS 选择器选择单个元素。
    1
    const element = document.querySelector('.myClass');  
    • 通过 CSS 选择器选择多个元素。
    1
    const elements = document.querySelectorAll('div.myClass');  
  2. 创建和添加元素

    • 创建新元素:

      1
      const newElement = document.createElement('div');  
    • 添加子元素:

      1
      2
      const parentElement = document.getElementById('parent');  
      parentElement.appendChild(newElement);
  3. 修改元素内容和属性

    • 修改文本内容:

      1
      element.textContent = '新内容';  
    • 修改 HTML 内容:

      1
      element.innerHTML = '<span>新内容</span>';  
    • 修改属性:

      1
      2
      element.setAttribute('class', 'newClass');  
      element.style.color = 'red'; // 修改样式
  4. 删除元素

    • 删除子元素:

      1
      parentElement.removeChild(childElement);  
    • 使用remove()方法删除元素:

      1
      element.remove();  
  5. 事件处理

    • 添加事件监听器:

      1
      2
      3
      element.addEventListener('click', function() {  
      alert('元素被点击了');
      });
    • 移除事件监听器:

      1
      element.removeEventListener('click', callback);  

常见 BOM 操作

  1. 访问浏览器信息

    • 获取浏览器的用户代理:

      1
      console.log(navigator.userAgent);  
  2. 控制浏览器窗口

    • 打开新窗口:

      1
      window.open('https://www.example.com', '_blank');  
    • 关闭当前窗口:

      1
      window.close();  
  3. 定时器

    • 启动定时器:

      1
      2
      3
      setTimeout(function() {  
      alert('定时器到期');
      }, 2000); // 2秒后弹出提示
    • 清除定时器:

      1
      clearTimeout(timerId);  
  4. 处理浏览器历史

    • 前进到历史的下一个页面:

      1
      window.history.forward();  
    • 返回到历史的上一个页面:

      1
      window.history.back();  
  5. 操作 URL

    • 获取当前页面的 URL:

      1
      console.log(window.location.href);  
    • 跳转到新的 URL:

      1
      window.location.href = 'https://www.example.com';  

浏览器缓存机制 - http缓存

  1. 强缓存:
    • 强缓存是指浏览器在第一次请求资源时,根据响应头中的缓存策略将资源缓存到本地,并在有效期内直接使用缓存,而不向服务器发送请求。
    • 强缓存可以通过设置响应头中的Cache-Control和Expires来实现:
      • Cache-Control:通过设置max-age指令来指定缓存的有效期,单位是秒。例如,Cache-Control: max-age=3600 表示资源在客户端缓存1小时。
      • Expires:指定资源的过期时间,是一个GMT格式的时间字符串。例如,Expires: Tue, 15 Nov 2022 12:00:00 GMT 表示资源在指定时间之后过期。
    • 如果资源命中强缓存,浏览器直接从本地缓存中加载资源,而不会向服务器发送请求,从而提高加载速度。
  2. 协商缓存:
    • 当资源未命中强缓存或强制缓存过期时,浏览器会向服务器发送请求,服务器通过某种方式验证资源是否有更新。
    • 常见的协商缓存机制有:
      • Last-Modified / If-Modified-Since:服务器在响应头中返回资源的最后修改时间,浏览器在后续请求时通过发送If-Modified-Since头将该时间发送给服务器进行验证。如果服务器判断资源未修改,则返回状态码304 Not Modified,并且浏览器使用本地缓存。
      • ETag / If-None-Match:服务器在响应头中返回资源的唯一标识符(ETag),浏览器在后续请求时通过发送If-None-Match头将该标识符发送给服务器进行验证。如果服务器判断资源未修改,则返回状态码304 Not Modified,并且浏览器使用本地缓存。
  1. Cookie
    • 存储大小:每个cookie的大小通常限制在4KB左右,且每个域名下的cookie数量也有限制。
    • 生命周期:可以设置cookie的过期时间,可以是会话级的(浏览器关闭后失效)或永久的。
    • 作用域:cookie在同源的所有页面之间共享,包括子域名。
    • 用途:主要用于在客户端和服务器之间传递信息,如身份验证、跟踪用户会话等。
  2. sessionStorage
    • 存储大小:通常限制在5MB左右。
    • 生命周期:数据在当前会话期间有效,即在当前会话窗口或标签页关闭前有效。
    • 作用域:每个sessionStorage对象都是独立的,不同页面之间无法共享数据。
    • 用途:适合存储临时数据,如表单数据、临时状态等。
  3. localStorage
    • 存储大小:通常限制在5MB或更大。
    • 生命周期:数据永久存储在浏览器中,除非用户清除或网站代码删除。
    • 作用域:每个localStorage对象都是独立的,不同页面之间无法共享数据。
    • 用途:适合长期存储的数据,如用户偏好设置、本地缓存等。

事件循环机制

在浏览器中,事件循环机制通常基于HTML5规范中定义的Event Loop模型。其主要包括以下几个部分:

  1. 调用栈:所有的同步任务都会被放入调用栈(call stack)中依次执行,形成一个执行上下文堆栈。
  2. 消息队列:当异步任务完成后或者事件发生时,会将对应的回调函数放入消息队列(message queue)中等待被执行。
  3. 事件循环:当调用栈为空时,会从消息队列中取出一个回调函数放入调用栈进行执行;如果消息队列为空,则等待新的回调函数加入。这样不断地将来自消息队列的任务推送到调用栈执行,这个过程就是所谓的“事件循环”。
  4. 宏任务与微任务:为了更细致地控制异步操作,在每次完成一个宏观任务后都有可能需要去清空微观任务列表。因此对于每一轮循环而言,在取出并执行完一个宏观任务之后还需要检查是否有微观任务需要清空。
    • 宏任务:包括整体代码块、setTimeout、setInterval、I/O 操作等。宏任务在每次事件循环中只能执行一个,执行完毕后才会执行下一个宏任务。
    • 微任务:包括 Promise、MutationObserver 等。微任务在每个宏任务执行完毕后立即执行,且在下一个宏任务之前执行完毕。

事件循环机制的执行顺序可以总结为:

  1. 执行当前执行栈中的同步代码。微任务,宏任务加入消息队列
  2. 检查微任务队列,依次执行所有微任务。微任务,宏任务加入消息队列
  3. 更新渲染(如果需要)。
  4. 执行宏任务队列中的一个任务。微任务,宏任务加入消息队列
  5. 重复上述步骤。

宏任务之间的优先级

宏任务是指由浏览器提供的任务队列中的任务,例如定时器回调、用户交互事件处理、网络请求等。

在事件循环中,宏任务按照以下优先级依次执行:(各个浏览器厂商可能也有自己特定实现细节。)

  1. 用户交互相关的宏任务(例如鼠标点击、键盘输入等):这些任务通常具有最高优先级,因为用户交互响应性对于用户体验非常重要。
  2. 定时器和计时器相关的宏任务:这些包括 setTimeoutsetInterval 等函数创建的异步操作。
  3. I/O 与网络请求相关的宏任务:例如异步加载资源或发送网络请求所产生的回调。
  4. 渲染:更新页面渲染所需要执行的宏任务。

<a>标签下载、通过接口获取文件缓存中下载有什么区别

  • <a>标签下载:
    通过在页面上创建一个 <a> 标签,设置其 href 属性为文件的下载地址,再通过 JavaScript 触发点击事件,即可实现文件的下载。
    如果是一个预览文件,则可以通过后端响应头上添加文件表示,达到下载目的。或者添加 download属性告诉<a>标签点击执行下载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- 1. 使用<a>标签下载文件 -->  
    <!-- 创建一个<a>标签,设置href属性为文件的下载地址,并通过JavaScript触发点击事件来实现文件下载 -->
    <a id="downloadLink" href="https://example.com/file.pdf" download="filename.pdf">点击下载文件</a>

    <script>
    // 获取下载链接元素
    const downloadLink = document.getElementById('downloadLink');
    // 触发点击事件下载文件
    downloadLink.click();
    </script>
  • 通过接口获取文件缓存中下载:
    可以通过后端接口返回文件的字节流,客户端收到文件数据后将其转换为Blob对象,再通过URL.createObjectURL()生成文件URL用于下载。(当文件太大时会阻塞)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 2. 通过接口获取文件缓存中下载  
    // 通过后端接口返回文件的字节流,转换为Blob对象,并生成文件URL用于下载
    fetch('https://example.com/api/download', {
    method: 'GET',
    })
    .then(response => response.blob()) // 将response转换为blob对象
    .then(blob => {
    // 生成文件URL
    const url = URL.createObjectURL(blob);
    // 创建<a>标签
    const a = document.createElement('a');
    a.href = url;
    a.download = 'filename.pdf'; // 设置下载文件名
    document.body.appendChild(a);
    a.click(); // 触发下载
    window.URL.revokeObjectURL(url);
    });

什么是流式下载

流式下载可通过实现分段请求获取大文件的方式,减轻服务器和客户端的性能压力。客户端会依次请求文件的不同部分,合并成完整文件。而不会将一个大文件完整缓存下来,导致阻塞

步骤

  1. 发送HTTP请求获取文件的元信息

在发起文件下载请求之前,首先需要发送一个HTTP请求,获取文件的元信息,如文件大小和文件类型等。获取文件大小后,可以根据文件大小和设置的数据块大小进行分块下载。

  1. 设置数据块大小和起始位置

一般来说,在文件下载过程中设置数据块的大小是很重要的,数据块的大小越大,下载速度越快,但内存占用也会更多。可以根据数据块的大小和文件大小计算出下载的起始位置和数据块的结束位置。

  1. 发送HTTP请求下载文件数据块

根据起始位置和结束位置,使用HTTP请求下载文件的数据块。请求头中需要设置Range字段,表示下载的数据块的范围。

  1. 处理下载完成的数据块

当下载完成后,需要对下载的数据块进行处理,例如解压缩、合并、处理数据等。

  1. 将数据块写入文件或进行处理

在将数据块写入文件或进行其他处理之前,根据需要进行处理。

  1. 重复步骤2-5,直到下载完成

根据数据块的起始位置和数据块的结束位置,不断重复步骤2-5,直到所有的数据块都下载并处理完成。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 发起GET请求,流式下载大文件  
fetch('https://example.com/api/streamDownload', {
method: 'GET',
})
.then(response => {
// 读取文件的内容信息,包括文件长度
const reader = response.body.getReader();
const contentLength = response.headers.get('Content-Length');

// 创建Uint8Array,用于存储完整的文件
const file = new Uint8Array(parseInt(contentLength));
let offset = 0; // 起始偏移量

return new Promise((resolve, reject) => {
function read() {
// ReadableStream提供的read()方法,可以读取到一个数据chunk,返回一个Promise对象
reader.read().then(({ done, value }) => {
if (done) {
// 所有数据读取完毕,完成流式下载
resolve();
return;
}

// 将读取到的数据块追加到Uint8Array中
file.set(value, offset);
offset += value.length;

// 递归读取数据块
read();
}).catch(reject);
}

// 读取第一个数据块,启动读取流程
read();
})
.then(() => {
// 将Uint8Array转换为Blob对象
const blob = new Blob([file], { type: 'application/pdf' });

// 生成Blob URL,并模拟点击下载链接,进行下载
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'filename.pdf';
document.body.appendChild(a);
a.click();

//释放Blob URL,避免内存泄漏
window.URL.revokeObjectURL(url);
});
});

js垃圾回收机制

JavaScript的垃圾回收机制是一种自动管理内存的机制,它负责在运行时自动回收不再使用的内存,防止内存泄漏和资源浪费。

JavaScript的垃圾回收机制主要有以下几种策略:

  1. 标记-清除(Mark-and-sweep):
    • 标记-清除是JavaScript最常用的垃圾回收算法。它通过标记所有能够从根部访问到的对象,然后清除未标记的对象来实现垃圾回收。这个“根”通常指的是全局对象(window对象)以及被全局对象引用的对象。所有不能从根部访问到的对象都将被标记为无法访问,然后进行清除。
  2. 引用计数(Reference counting):
    • 引用计数的垃圾回收策略是通过对每个对象维护一个引用计数器。当对象被引用时,引用计数器加1;当引用关系被释放时,引用计数器减1。当引用计数为0时,表示对象不再被使用,可以进行回收。不过,引用计数算法难以处理循环引用的情况,因此在实际中较少被使用。

内存泄漏是怎么产生的

内存泄漏是指程序在运行过程中因为错误的内存管理而导致已经不再使用的内存无法被及时释放,从而造成系统内存占用逐渐增加,最终可能导致系统性能下降甚至崩溃。

  1. 未释放不再使用的内存:
    • 当程序中分配了内存(例如使用new操作符创建对象或数组),但在不再需要这块内存时,没有及时释放(例如通过delete操作符或者释放对对象的引用)。这可能发生在某些特定的代码路径上,例如异常情况下未执行到释放内存的逻辑。
  2. 循环引用:
    • 当两个或多个对象之间存在相互引用,且这些引用之间形成一个循环时,如果这些对象不再被其他代码访问,但由于存在循环引用,垃圾回收机制无法识别并回收这些对象。这样就会导致内存泄漏。
  3. 未关闭资源或断开连接:
    • 在使用一些系统资源(如文件、网络连接、数据库连接等)时,如果在使用完毕后未正确关闭资源或断开连接,就可能导致资源未释放,从而造成内存泄漏。
  4. 定时器和事件处理器未正确释放:
    • 在JavaScript等语言中,如果使用定时器或者绑定事件处理器,但在不再需要时未取消定时器或者解绑事件处理器,就会导致这些资源无法被及时释放,从而造成内存泄漏。

sql调优

SQL 调优是优化 SQL 查询性能的过程,旨在提高查询的执行效率和响应时间

  1. 确保正确的索引:在频繁用于查询条件或连接的列上创建索引,可以大大提高查询效率。
  2. 优化查询语句:避免使用通配符操作符(如 %)开头的模糊查询,尽量使用前缀索引、范围查询等更高效的方式。
  3. 减少返回的数据量:只选择需要的列,避免 SELECT *,限制返回的行数,利用 LIMIT 子句进行分页等。
  4. 消除冗余查询:尽量避免重复查询相同的数据,利用子查询或者 JOIN 语句合并查询。
  5. 使用合适的连接方式:根据查询逻辑选择正确的连接方式,如 INNER JOIN、LEFT JOIN、UNION 等。
  6. 避免使用函数和操作符:在 WHERE 子句中避免使用函数和操作符操作查询列,以免影响索引的使用。
  7. 优化 SQL 查询顺序:优化 WHERE 子句的顺序,将可过滤掉大量数据的条件放在前面,以尽早筛选不符合条件的数据。
  8. 分析查询执行计划:通过 EXPLAIN 命令或其他数据库工具,分析查询执行计划,找出潜在的性能瓶颈和优化点。
  9. 增加缓存和缓冲区:合理设置数据库的缓存和缓冲区大小,减少磁盘访问,提高数据读取速度。

webworker多线程可以避免阻塞,为什么不把js内容放到webworker运行

将 JavaScript 代码放到 Web Worker 中运行可以避免阻塞主线程,因为 Web Worker 在单独的线程中运行,不会影响主线程的执行。这对于一些耗时的计算、大量数据处理和复杂的逻辑运算非常有用,可以提高页面的响应性能和用户体验。

但并不是所有的 JavaScript 代码都适合放到 Web Worker 中运行,主要原因有以下几点:

  1. Web Worker 无法访问 DOM:由于 Web Worker 运行在一个独立的线程中,无法直接访问 DOM 元素,因此无法进行 DOM 操作。如果 JavaScript 代码需要操作页面的 DOM 结构,就无法放到 Web Worker 中运行。
  2. 传递数据复杂:与主线程相比,Web Worker 与主线程之间的通信比较复杂,需要通过 postMessage 进行数据传递。如果需要频繁地传递大量数据,可能会带来额外的开销。
  3. 内存消耗问题:每个 Web Worker 都需要一定的内存开销,过多的 Web Worker 可能会导致内存消耗过大,影响页面的性能。
  4. 浏览器支持限制:虽然现代浏览器都支持 Web Worker,但并不是所有的浏览器都支持,特别是一些旧版本的浏览器可能存在兼容性问题。

SSR服务端渲染

SSR是一种用于在服务器上渲染网页,将完全渲染后的页面发送给客户端显示的一种技术。它允许服务器生成完整的html标签,包括动态内容。

SSR在服务器已经渲染好网页,发送给客户端,可以改善客户端初始加载时间。完整的html信息可见性,有利于搜索引擎。减少客户端需要下载的数据量。因此有利于带宽低,延迟高的用户

SSR会存在跨域问题吗

跨域问题通常是指浏览器的同源策略导致的限制,在浏览器中运行的前端代码访问其他域下的资源时可能会受到限制。

对于服务器端渲染(SSR),由于是在服务器端进行页面渲染并直接返回给客户端,一般不会存在跨域问题;如果在服务器端渲染过程中有一些数据需要通过 AJAX 请求获取,那么这些异步数据的请求依然会存在跨域问题。

原因:当使用服务器端渲染时,浏览器向服务器请求页面时,服务器会处理模板和数据,最终返回一段已经渲染好的 HTML 页面。这个 HTML 页面中的所有资源都来自于同一个域名,不会涉及到跨域请求。

前端与后端之间的实时通信的方式

轮询,SSE(长连接), websocket

  1. 轮询(Polling):轮询是一种简单粗暴的实时通信方式,客户端定时发送请求询问服务器是否有新的数据。虽然实现简单,但效率低下且会导致无谓的网络流量和服务器负担。
  2. SSE(Server-Sent Events,服务器推送事件):SSE 是一种基于单向事件流的实时通信机制,允许服务器向客户端推送数据。与轮询相比,SSE更加高效,无需频繁地发送请求,能够实现服务器主动向客户端推送数据。
  3. WebSocket:WebSocket 是一种全双工通信协议,可以在客户端和服务器之间建立持久性的连接,实现实时的双向通信。与HTTP和SSE不同,WebSocket连接一旦建立,客户端和服务器可以随时发送消息而不需要经过频繁的握手,适用于需要高度实时性和频繁交互的场景。

讲讲websocket怎么使用,怎么实现

WebSocket是一种在单个TCP连接上进行全双工通信的协议,特点是可以实现实时的数据传输

  1. 创建WebSocket连接: 首先,通过JavaScript创建一个WebSocket对象并指定要连接的服务器URL。

    1
    const socket = new WebSocket('ws://localhost:8080');
  2. 监听WebSocket事件:WebSocket对象提供了多种事件钩子,可以监听连接打开、消息接收、连接关闭等事件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    socket.onopen = () => {  
    console.log('WebSocket连接已打开');
    };

    socket.onmessage = (event) => {
    console.log('收到消息:', event.data);
    };

    socket.onclose = () => {
    console.log('WebSocket连接已关闭');
    };

    socket.onerror = () => {
    console.error('WebSocket连接出现错误');
    };
  3. 发送和接收消息: 可以使用send()方法向服务器发送消息,并用onmessage事件处理服务器返回的消息。

    1
    2
    3
    4
    5
    6
    7
    // 发送消息  
    socket.send('Hello, server!');

    // 接收消息,在onmessage事件处理
    socket.onmessage = (event) => {
    console.log('收到消息:', event.data);
    };
  4. 关闭WebSocket连接: 当不再需要连接时,可以使用close()方法来关闭WebSocket连接

    1
    socket.close();

案例:

  1. 心跳检测:
    心跳检测是通过定期向服务器发送一个特定的消息来检测连接状态。如果服务器接收到了心跳消息,就说明连接是活动的,如果一段时间内没有收到心跳消息,服务器可以认为连接已断开。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 定时发送心跳消息  
    const heartCheck = {
    timeout: 5000, // 心跳间隔时间,单位毫秒
    timer: null,
    start() {
    this.timer = setInterval(() => {
    socket.send('Heartbeat');
    }, this.timeout);
    },
    stop() {
    clearInterval(this.timer);
    }
    };

    // 在连接打开时开始心跳检测
    socket.onopen = () => {
    heartCheck.start();
    };

    // 接收到消息时重置心跳检测
    socket.onmessage = () => {
    heartCheck.stop();
    heartCheck.start();
    };
  2. 断线重连:
    断线重连是在检测到连接断开后,尝试重新建立连接以保持通信的功能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 重连函数  
    function reConnect() {
    if (socket.readyState !== WebSocket.OPEN) {
    setTimeout(() => {
    socket = new WebSocket('ws://localhost:8080');
    // 重新绑定事件监听
    socket.onopen = () => {
    console.log('WebSocket连接已重新建立');
    heartCheck.start(); // 重新开始心跳检测
    };
    socket.onclose = () => {
    console.log('WebSocket连接已关闭');
    reConnect(); // 断线重连
    };
    }, 3000); // 3秒后尝试重连
    }
    }

    // 在连接关闭时尝试重连
    socket.onclose = () => {
    console.log('WebSocket连接已关闭');
    reConnect();
    };

JWT认证过程

JWT 的认证过程基于 Token,其中包含了被加密的用户信息和签名,使得服务器端无需在每次请求中都查询数据库或者使用 Session 来进行身份验证。客户端使用 JWT 值来标识和验证用户的身份,从而完成认证和权限控制。

认证过程如下:

  1. 客户端发送登录请求(通常是用户名和密码)到服务器端。
  2. 服务器端验证用户的身份和密码是否正确。
  3. 如果验证通过,服务器端生成一个 JWT,并将其发送回客户端。
  4. 客户端收到 JWT 后,将其保存在本地(通常是在 localStorage 或者 Cookie 中)。
  5. 客户端在每次请求需要认证的资源时,将 JWT 放入请求头(通常是在 Authorization 头部字段中)一同发送到服务器端。
  6. 服务器端收到请求后,从请求头中提取 JWT。
  7. 服务器端对 JWT 进行验证和解析,检查签名是否合法、是否过期等。
  8. 如果验证通过,服务器端根据 JWT 中的信息进行相应的操作,并返回资源或执行对应的业务逻辑。

数据加密

使用 HTTPS 协议:确保数据传输过程中是加密的。HTTPS 可以保护数据在客户端和服务器之间的传输安全,并且在网络抓包时无法直接查看明文数据。

前端对密码进行加密:在用户输入密码后,前端可以对密码进行加密处理(例如使用哈希函数),然后将加密后的密码发送到服务器。这样即使接口被拦截,在网络请求中也只能看到经过加密处理的数据而不是明文密码。

讲解一下promise

Promise 是 JavaScript 中的一种异步编程解决方案,解决了回调函数执行异步操作的回调地狱问题。它可以让我们更方便地处理异步操作。Promise 有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。

Promise 处于 pending 状态时,可以通过调用 resolve 函数将其状态改为 fulfilled,或者通过调用 reject 函数将其状态改为 rejected。

当 Promise 状态改变后,就会执行对应的回调函数,即 then 方法中的第一个参数或 catch 方法中的参数。

promise后面如果有多个值,.then() 怎么传值

在 Promise 中,可以通过使用链式调用的方式,多次调用 .then() 方法来处理多个值。每个 .then() 方法可以接收前一个 Promise 的结果,并返回一个新的 Promise,从而可以实现值的传递。

async await理解

async/await 是 ES8(ECMAScript 2017)引入的异步编程模型,是建立在 Promise 之上的语法糖,让异步代码看起来更像同步代码,更易于理解和编写。async 函数用于定义一个异步函数,await 关键字用于等待一个 Promise 对象(通常是一个异步操作),等待完成后再执行后续的代码。

async 函数声明: 使用 async 关键字声明一个函数时,该函数会自动返回一个 Promise 对象。

await 表达式:async 函数中,可以使用 await 关键字来暂停函数的执行,等待 Promise 对象的状态改变。

错误处理: 使用 try/catch 结构可以捕获 async/await 中的异常。在 try 代码块中放置可能抛出异常的代码,而在 catch 代码块中处理异常情况。

async await比promise有什么优势

  1. 更直观的代码结构: async/await 使异步代码看起来更像同步代码,使得代码结构更加直观和易于理解。通过使用 await 关键字,可以在代码中明确地指定等待异步操作的结果,而不需要使用回调函数或链式调用。
  2. 更容易处理错误: 在使用 async/await 时,可以使用 try/catch 结构来捕获和处理异常。这使得错误处理更加简洁和可读,而不需要在每个 Promise 链中都使用 .catch() 方法来处理错误。
  3. 更好的错误堆栈追踪: async/await 可以提供更好的错误堆栈追踪,使得在出现错误时更容易定位问题所在。相比于 Promise 的链式调用,async/await 可以在出错的地方直接抛出异常,而不需要在每个 .then() 方法中手动抛出错误。
  4. 更灵活的控制流: async/await 允许使用常规的控制流语句(如条件语句和循环语句),使得在异步操作之间进行逻辑控制更加灵活。这使得编写复杂的异步代码变得更加容易。

async await的底层原理

底层原理其实是基于 Promise 和生成器(Generator)的。

  1. async 函数本质上是返回一个 Promise 对象,这个 Promise 对象的状态和值由 async 函数内部的代码决定。
  2. async 函数内部使用 await 关键字时,其后的表达式会被封装为一个 Promise 对象,并等待该 Promise 对象的状态改变。
  3. await 后面的 Promise 对象变为 resolved(已解决)状态时,await 表达式会返回 Promise 对象的解决值,然后程序会继续执行 await 后面的代码。
  4. 如果 await 后面的 Promise 对象变为 rejected(拒绝)状态,则会抛出一个异常,可以使用 try/catch 来捕获并处理异常。
  5. 生成器函数允许函数在执行过程中暂停,并在需要时回复。async/await 在底层会使用生成器函数来实现异步操作的暂停和回复。

generator的原理

Generator 是 JavaScript 中的一种特殊函数,它可以在执行过程中暂停并恢复。Generator 函数使用 function* 关键字进行声明,并使用 yield 关键字来定义暂停点。

Generator 函数的原理是通过迭代器(Iterator)的概念来实现的。

1
2
3
4
5
6
7
8
9
10
11
12
function* myGenerator() {  
yield 1;
yield 2;
yield 3;
}

const iterator = myGenerator();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

pnpm软连接和硬链接区别

软链接(Symbolic Link)和硬链接(Hard Link)是在文件系统中用于创建链接到文件或目录的两种不同方式。

软链接(Symbolic Link):

  1. 软链接是一个独立的文件,其中包含一个指向另一个文件或目录的路径(绝对路径或相对路径)。
  2. 创建软链接时,源文件和链接之间是两个不同的文件,源文件的内容不会被复制到链接文件中,只是通过路径关联。
  3. 软链接可以跨文件系统,即可以跨越不同的物理硬盘或分区。
  4. 当源文件被删除或移动,软链接仍然保留,但软链接失效。
  5. 软链接可以用来创建指向文件或目录的符号链接。

硬链接(Hard Link):

  1. 硬链接是文件系统中对同一个文件或目录的多个链接,它们共享相同的数据块。
  2. 创建硬链接时,不会创建新的文件,只是在文件系统中创建一个新的目录条目指向相同的 inode 节点。
  3. 硬链接只能在同一个文件系统中创建,即源文件和硬链接需要在同一文件系统内。
  4. 当所有硬链接都被删除后,文件才会被完全删除,并释放磁盘空间。
  5. 硬链接不能指向目录,只能指向文件。

pnpm hoist机制

pnpm 的 hoist 机制是一种优化依赖结构的方式,旨在减少存储空间和提高性能。
在传统的 npm 或者 Yarn 安装方式中,每个项目都会在其根目录下创建一个 node_modules 目录,并将所有的依赖包都安装在其中。这样的结果是,不同的项目会包含相同的依赖包的多个副本,造成存储空间的浪费。
而 pnpm 的 hoist 机制则将多个项目的依赖包按照相同的版本整理到一个公共的存储位置(称为 store)。具体来说,当一个包被多个项目依赖时,pnpm 会自动将这个包安装到存储位置,并创建软链接到各个项目的 node_modules 目录下。

流程:

  1. 安装过程中,当 pnmp 发现某个依赖包在多个项目中重复出现时,它将该依赖包安装到公共存储位置(store)中,并为每个项目创建一个软链接到依赖包。
  2. 对于每个项目,pnpm 会在其根目录下创建一个特殊的 .pnpm 目录,该目录下包含一个 packages 软链接文件夹,其中存储了所有项目所依赖的包的软链接。
  3. 当项目需要使用某个依赖包时,pnpm 会根据软链接的路径找到对应的包。

优点:

  • 节省存储空间:依赖包只会被下载一次,多个项目共享一个副本。
  • 提高性能:在安装和更新依赖时,减少了 I/O 操作,更快地完成配置。

缺点:

pnpm hoist 机制可能会在某些情况下引发依赖冲突问题。在这种情况下,可以使用 pnmp 提供的工具来解决冲突,如 pnpm recursive list 可以列出所有项目的依赖关系树,并帮助找到冲突的依赖包版本。

pnpm包的结构,三层寻址

pnpm 是一个基于 npm 的包管理工具

它的包结构和寻址分为以下几层:

  1. 根目录层:根目录层是指项目的根目录,也是 package.json 文件所在的目录。这一层主要包含一个 package.json 文件,用来记录项目的依赖关系和其他相关配置信息。
  2. 存储目录层:存储目录层是指存放依赖包代码的目录。pnpm 的存储目录层并不是像 npm 那样将所有依赖包放在一个统一的目录下,而是根据依赖包的名称、版本和 hash 值来生成不同的路径存放不同的依赖包。这样的做法可以有效减少存储空间的使用,同时也保证了依赖包的唯一性。
  3. 软链接层:软链接层是指通过软链接将依赖包连接到项目的 node_modules 目录下。当代码需要引用依赖包时,实际上是通过软链接层来找到依赖包的代码。pnpm 使用软链接的方式可以减少重复的依赖包拷贝,同时提供了更快更节省空间的安装和运行速度。

三层寻址

三层寻址的过程如下:

  1. 从根目录的 package.json 文件中读取项目的依赖关系。
  2. 根据依赖包的名称和版本等信息,在存储目录层中查找对应的依赖包。
  3. 将找到的依赖包通过软链接层连接到项目的 node_modules 目录下。

通过三层寻址,可以在保证依赖包唯一性的同时,提高包的安装和运行效率。

什么事幽灵依赖,会引发什么问题

幽灵依赖是指在项目中存在但未在 package.json 文件中明确声明的依赖包或依赖关系。这种情况通常发生在项目代码中引入了某些依赖包,但没有及时在 package.json 文件中添加对应的依赖项。

会引发以下问题:

  1. 不稳定的构建:由于幽灵依赖没能被明确标识在 package.json 文件中,项目的构建可能会出现不稳定性。每次安装依赖包时,依赖关系可能会发生变化,导致构建结果不一致。
  2. 版本不一致:没有在 package.json 中明确声明依赖关系可能会导致依赖包的版本不一致。在不同的环境中,可能会出现依赖包的不同版本被使用,从而导致代码在不同环境下表现不一致或出现功能错误。
  3. 安全隐患:幽灵依赖可能会带来安全隐患。如果项目中存在未声明的依赖包,且这些依赖包存在安全漏洞,攻击者可能会利用这些漏洞对项目进行攻击或注入恶意代码。
  4. 维护困难:在项目中存在大量幽灵依赖会增加代码的维护难度。随着项目的变大,难以追踪项目实际使用的依赖包,导致代码可读性和可维护性降低

package A 和package B 依赖同一个包,却不同版本,如何统一版本

  1. 升级包:查看 A、B 两个包所依赖的包 C 分别的版本范围,如果范围存在交集,可以尝试将 A、B 中所依赖的包 C 版本范围升级到一个兼容的、相同的版本。这可以通过修改 A、B 的 package.json 文件中对包 C 的版本范围进行修改来实现。然后重新安装依赖并测试,确保项目能够正常运行。
  2. 更新依赖:如果升级包 C 的版本并不可行,可以尝试升级 A、B 包本身,以便它们能依赖同一个包 C 的相同版本。这可以通过修改 A、B 的 package.json 文件中对自身的版本范围进行修改来实现。然后重新安装依赖并测试,确保项目能够正常运行。
  3. 分别安装依赖包:如果无法升级包 C 的版本或者升级包 A、B 的版本也不可行,可以尝试分别安装依赖包。也就是说,分别为包 A 和包 B 创建独立的依赖环境,让它们各自安装自己所需要的依赖包版本。然后在使用它们的地方通过注入或者模块引入来分别使用对应的依赖包。
  4. 重构代码:如果无法统一版本,也无法分别安装依赖包,可能就需要进行代码重构了。通过重构代码,考虑使用兼容不同版本的 API 或者替代性的包来解决依赖不一致的问题。

关于浏览器渲染原理,重排

重排(reflow)是浏览器在绘制页面时的一个重要概念。当页面发生布局变化时(如改变元素宽高、位置、字体大小等),浏览器需要重新计算元素的布局信息,并重新绘制页面。这个过程就是重排。

重排的代价很高,会导致页面的性能下降,因此我们应尽量减少重排的次数。
常见引起重排的操作有:

  1. 修改 DOM 结构:如增删节点、修改节点顺序等;
  2. 改变元素位置和尺寸:如改变元素的位置、大小、边距、内填充等;
  3. 修改样式:如改变元素的背景颜色、字体大小、文字颜色等;
  4. 查询 DOM 元素的位置、尺寸等信息:如调用 offsetWidth、offsetHeight、scrollTop、scrollLeft 等属性。

为了尽量减少重排,我们可以采取以下措施:

  1. 将多次重排操作合并为一次:比如通过改变元素的 class 来一次性修改多个样式;
  2. 避免读取布局信息:尽量避免频繁读取 DOM 元素的位置和尺寸信息;
  3. 使用文档片段进行离线处理:将需要生成大量 DOM 元素的操作暂时放入文档片段中,完成后再一次性插入到页面中;
  4. 使用绝对定位的元素:需要频繁改变位置的元素最好采用绝对定位,避免引起其他元素的重排。

组件库怎么做,怎么实现

如何看待框架,相比原生解决了什么问题

VDOM是什么,相比原生对比为什么要使用VDOM

虚拟DOM,相比起直接操作真实的 DOM,使用 VDOM 的方式有以下几个优点:

  1. 性能优化:直接操作 DOM 可能会导致频繁的重排和重绘,而 VDOM 通过在内存中构建一颗虚拟的 DOM 树,并将其与真实 DOM 进行比较,只更新有变化的部分,从而减少了不必要的 DOM 操作,提高了性能。
  2. 跨平台兼容性:VDOM 是对 DOM 结构的抽象,不依赖于具体的平台或浏览器实现,因此可以在不同平台上保持一致性,并提供良好的跨浏览器兼容性。
  3. 简化开发流程:使用 VDOM 可以更方便地进行组件化开发,并提供了一种数据驱动的方式来更新视图,代码更加清晰、易于理解和维护。
  4. 方便的状态管理:VDOM 结合了框架本身的状态管理机制,可以实现方便的状态管理和组件间的通信,提供了更好的代码组织和复用能力。

但也有一些缺点,比如会引入一定的性能损耗,以及需要额外的代码来处理 VDOM 的生成和比较等。

AST是什么

AST(Abstract Syntax Tree)是一种数据结构,用于表示程序代码的抽象语法结构。AST 可以帮助开发者理解和分析代码,以及进行静态分析、代码转换和优化等操作。

BST是什么

BST(Binary Search Tree)是一种常见的数据结构,用于存储和组织数据。BST 是一种二叉树,其中每个节点最多有两个子节点。BST 通常用于实现搜索、插入和删除操作的高效数据结构。

使用BST实现 包括insert,search,delete

ES6+,HTML5,CSS3 新特性

ES6+

  1. let 和 const 声明
    • 引入块级作用域的 let 和不可变的常量 const,替代 var 关键字。
  2. 箭头函数
    • 使用箭头(=>)定义匿名函数,简化了函数的写法。
  3. 默认参数值
    • 在定义函数时可以给参数提供默认值。
  4. 解构赋值
    • 可以从数组或对象中进行解构赋值,方便地提取数据。
  5. 扩展运算符
    • 使用三个点(…)可以将一个数组扩展为另一个数组中的元素或将对象的属性复制到另一个对象中。
  6. 类和继承
    • 引入了 class 关键字,支持了面向对象编程的语法糖。
  7. 模块化
    • 引入了 import 和 export 关键字,支持了模块化的语法。
  8. Promise
    • 引入了Promise对象,用于更加方便地处理异步操作。
  9. 简化的对象字面量语法
    • 允许在对象中直接使用变量名字作为属性名。
  10. 模板字符串
    • 使用反引号(`)包裹的字符串,支持了字符串内插和多行字符串。

HTML5

  1. 语义化元素:
    • 引入了一系列语义化的标签,如header、footer、nav、section等,用于更好地描述页面结构,提升可读性和可访问性。
  2. 媒体支持:
    • 引入了新的媒体元素,如
  3. Canvas绘图:
    • 提供了标签和相应的JavaScript API,可以使用JavaScript在网页上绘制图形、动画和游戏等。
  4. SVG图形:
    • 引入了对可缩放矢量图形(SVG)的原生支持,可以在网页上使用SVG格式的图形进行渲染和交互。
  5. WebSocket和WebRTC:
    • 引入了WebSocket和WebRTC API,用于实现在客户端和服务器之间进行实时双向通信和音视频通信的功能。
  6. 本地存储:
    • 引入了localStorage和sessionStorage API,可以在客户端存储数据,支持长期保存和会话期间使用。
  7. 地理定位:
    • 提供了Geolocation API,可以获取用户的地理位置信息,用于实现位置相关的应用和服务。
  8. 表单增强:
    • 引入了一些新的表单类型和属性,如input的type属性新增了email、number等类型,表单验证和表单自动完成等功能得到改进。
  9. 拖放:
    • 提供了拖放API,可以通过JavaScript实现元素的拖放操作。
  10. 多线程和Web Workers:
    • 引入了Web Workers API,允许浏览器在后台创建独立的工作线程,可以执行复杂的计算任务,提升性能和响应能力。

CSS3

  1. 选择器:
    • 引入了更多的CSS选择器,如属性选择器、伪类选择器等,提供了更多样的选择元素的方式。
  2. 盒模型:
    • 引入了box-sizing属性,可以更方便地控制盒子的尺寸计算方式,包括content-box和border-box。
  3. 背景和边框:
    • 提供了更多的背景属性,如背景渐变、背景尺寸控制等,以及更多的边框样式和边框阴影效果。
  4. 字体:
    • 引入了@font-face规则,允许网页设计师使用自定义字体,并且支持Web字体格式。
  5. 2D/3D转换和动画:
    • 引入了transform属性和transition属性,可以通过CSS进行元素的旋转、缩放、倾斜等2D和3D变换,以及实现过渡和动画效果。
  6. Flexbox布局:
    • 引入了弹性盒子布局(Flexbox),用于实现灵活的布局和对齐方式,适应各种屏幕尺寸和设备。
  7. Grid布局:
    • 提供了网格布局(Grid Layout),可以更方便地实现复杂的网格化布局,支持多列和多行的网格化设计。
  8. 媒体查询:
    • 引入了媒体查询(Media Queries),可以根据设备屏幕宽度、高度、方向等特性,为不同的设备提供不同的样式。
  9. 过渡效果(transitions):
    • 允许开发者在状态变化时实现平滑的过渡效果,比如鼠标滑过后颜色渐变。
  10. 伸缩容器(Sizing Units):
    • 引入了更多新的长度单位,如vw、vh、vmin、vmax,使得元素的大小可以相对于视口的尺寸来计算。

Electron

Electron软件自动更新操作

步骤:

  1. 创建一个更新服务器:首先需要在服务器上托管您的应用程序安装文件和更新文件。您可以选择使用自己的服务器或第三方服务如GitHub Releases等。
  2. 配置应用程序:在Electron应用程序中,您需要配置自动更新功能。您可以使用Electron提供的自动更新模块autoUpdater来实现。
  3. 添加更新逻辑:在应用程序启动时,可以检查是否有新版本可用。您可以从服务器获取最新版本的信息,并与当前应用程序的版本进行比较。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const { app, autoUpdater } = require('electron');  

autoUpdater.setFeedURL({
url: 'https://example.com/releases',
provider: 'github'
});

autoUpdater.on('update-available', () => {
// 有新版本可用
});

autoUpdater.on('update-downloaded', () => {
// 新版本已下载完成
autoUpdater.quitAndInstall();
});

app.on('ready', () => {
autoUpdater.checkForUpdates();
});

请介绍一下你对Electron的理解以及你的工作经验。

Electron是由GitHub开发的开源框架,用于构建跨平台的桌面应用程序。它使用Web技术(HTML、CSS和JavaScript)来构建应用程序,并将其封装在一个独立的容器中,以便在桌面操作系统(如Windows、Mac和Linux)上运行。

Electron 优缺点

优点:

  1. 跨平台能力:Electron可以让开发者使用一套代码构建在多个操作系统上运行的桌面应用程序,大大简化了跨平台开发的复杂性。
  2. 使用Web技术:Electron基于Web技术,如HTML、CSS和JavaScript,开发者可以使用熟悉的工具和技术栈构建应用程序,减少了学习成本和开发周期。
  3. 强大的生态系统:Electron拥有庞大的开发者社区和丰富的第三方库,提供了丰富的资源和解决方案,方便开发者快速构建和定制应用程序。
  4. 系统级访问:Electron提供了访问操作系统特定功能的API,如文件系统、网络请求、系统通知、窗口管理等,可以满足应用程序的各种功能需求。
  5. 自定义界面:Electron允许开发者完全自定义应用程序的用户界面,从窗口样式到菜单、工具栏等,可以实现个性化的用户体验。
  6. 自动更新功能:Electron提供了自动更新功能,方便开发者向用户提供新版本和修复程序,保持应用程序的稳定性和安全性。

缺点:

  1. 资源占用较高:由于Electron封装了Chromium和Node.js,应用程序会占用较高的系统资源,包括内存和存储空间。
  2. 文件大小较大:由于Electron打包了整个Web运行环境,应用程序的安装包较大,可能会增加用户下载和安装的时间。
  3. 性能问题:尽管Electron在桌面环境下具有很好的性能,但与原生应用程序相比,仍然存在一定程度的性能差距。
  4. 安全性问题:由于使用Web技术开发,Electron的应用程序可能面临一些Web安全问题,如跨站脚本攻击等,需要开发者在设计和开发过程中重视安全性。

什么是Electron(原子)框架?它是如何工作的?

Electron框架的工作原理如下:

  1. 渲染进程(Renderer Process):在Electron应用程序中,每个窗口被称为一个渲染进程,它运行在Chromium浏览器的渲染进程中。这些渲染进程负责加载和显示应用程序的界面,以及处理与用户界面交互的逻辑。
  2. 主进程(Main Process):除了渲染进程外,Electron应用程序还有一个主进程,它是应用程序的控制中心,负责管理应用程序的整体生命周期、窗口管理、系统级访问等功能。
  3. 渲染进程与主进程通信:渲染进程和主进程之间可以通过Electron提供的IPC(Inter-Process Communication)机制进行通信,以实现不同进程之间的数据交换和功能调用。
  4. Node.js运行时环境:Electron集成了Node.js运行时环境,使开发者可以在应用程序中使用Node.js的模块和功能,如访问文件系统、网络请求、进程管理等。
  5. 框架提供的API:Electron提供了丰富的API,开发者可以使用这些API访问操作系统特定功能,如文件系统、系统通知、原生UI组件等,从而实现更强大和功能丰富的应用程序。

Electron与传统Web开发的区别是什么?它的优势和适用场景有哪些?

Electron与传统Web开发的区别主要在于应用程序的目标平台和功能需求:

  1. 目标平台:传统Web开发主要是针对在浏览器中运行的Web应用程序,而Electron是用于构建桌面应用程序的开发框架,可以在Windows、Mac和Linux等操作系统上运行。
  2. 功能需求:传统Web应用通常是运行在浏览器中的网页应用,功能受限于浏览器的安全策略和功能限制。而Electron应用程序可以利用Node.js和Electron提供的API,访问操作系统的底层功能,实现更丰富的桌面应用功能,如文件系统访问、系统通知、本地数据库等。

Electron的优势和适用场景包括:

  1. 跨平台开发:Electron可以让开发者使用一套代码构建在多个操作系统上运行的桌面应用程序,提高开发效率,节省维护成本。
  2. 使用熟悉的Web技术:开发者可以使用熟悉的Web技术(如HTML、CSS和JavaScript)来构建桌面应用程序,无需学习新的语言和技术栈,降低了开发难度。
  3. 强大的生态系统:Electron拥有庞大的开发者社区和丰富的第三方库,提供了丰富的资源和解决方案,方便开发者快速构建和定制应用程序。
  4. 系统级访问:Electron提供访问操作系统特定功能的API,如文件系统、网络请求、系统通知、窗口管理等,开发者可以实现更丰富和功能强大的应用程序。
  5. 自定义界面:Electron允许开发者完全自定义应用程序的用户界面,从窗口样式到菜单、工具栏等,可以实现独特和个性化的用户体验。

请说明一下Electron的主要组成部分(主进程、渲染进程)、通信方式以及如何进行进程间的通信?

Electron的主要组成部分包括主进程和渲染进程

  1. 主进程(Main Process):主进程是Electron应用程序的控制中心,负责管理应用程序的整体生命周期、窗口管理、系统级访问等功能。主进程在运行时可以创建多个窗口,每个窗口都对应一个渲染进程。
  2. 渲染进程(Renderer Process):渲染进程在Electron应用程序中负责加载和显示界面以及处理与用户交互的逻辑。每个窗口对应一个渲染进程,运行在Chromium浏览器的渲染进程中。

进程间通信(IPC)的常见方式:

  1. 使用主进程和渲染进程之间的模块:在Electron应用程序中,主进程和渲染进程可以共享同一个Node.js环境,它们可以直接通过require命令来引入相同的模块,并共享数据和函数。
  2. 使用消息机制:Electron提供了ipcRenderer(在渲染进程中使用)和ipcMain(在主进程中使用)模块,用于实现进程之间的异步通信。通过发送和接收消息可以进行双向的进程间通信。
  3. 使用远程通信:Electron还提供了remote模块,允许渲染进程直接调用主进程中的API。通过远程调用,渲染进程可以访问主进程的功能,使得跨进程通信更加简化。

如何使用Electron构建桌面应用程序(Desktop Application)?请描述一下整个开发流程和注意事项。

步骤:

  1. 环境搭建:首先需要确保开发环境中已经安装了Node.js和npm(Node.js的包管理器)。
  2. 创建Electron项目:使用命令行工具,在项目目录中运行npm init命令来创建一个新的npm项目,并在package.json文件中添加Electron作为依赖项。
  3. 编写主进程代码:在项目中创建主进程的JavaScript文件,该文件负责创建Electron应用程序的窗口,以及管理应用程序的生命周期和系统级操作等。
  4. 编写渲染进程代码:在项目中创建渲染进程的HTML、CSS和JavaScript文件,该文件负责显示应用程序的用户界面,并处理与用户的交互逻辑。
  5. 打包和分发:使用Electron提供的打包工具(如Electron Forge、electron-packager等)将应用程序打包成可执行文件,并根据目标平台进行适配和分发。

请谈谈你对Electron打包工具(如electron-builder、electron-packager)的了解和使用经验,有哪些注意事项和优化方法?

  1. electron-builder:electron-builder是一个功能强大且易于使用的Electron打包工具,支持自动更新、多平台打包、代码签名等功能。通过配置文件可以定制化各种构建选项,例如设置应用程序图标、生成安装包等。
  2. electron-packager:electron-packager是另一个常用的Electron打包工具,它可以将Electron应用程序打包成适用于不同平台的可执行文件。使用electron-packager可以轻松地将Electron应用程序打包成各种格式的安装包,如Windows的exe、Mac的dmg等。

注意事项和优化方法:

  1. 配置文件设置:根据项目需求,合理配置打包工具的参数和选项,比如指定应用程序图标、版本号、作者信息等,以及设置适配不同操作系统的参数。
  2. 优化资源和打包大小:对Electron应用程序进行资源优化,尽量减小可执行文件的大小,如移除不必要的依赖、压缩图片和代码等。可以通过调整构建选项和排除不必要的文件来实现。
  3. 多平台适配:要确保打包工具能够正确适配各个平台,包括处理每个平台的特定需求和配置,如Windows的安装程序、Mac的DMG文件等。
  4. 自动更新配置:如果需要实现自动更新功能,建议使用electron-builder的自动更新功能,配置Updater,以实现应用程序的自动更新功能。
  5. CI/CD集成:将Electron应用程序的打包流程集成到持续集成和持续交付(CI/CD)系统中,实现自动构建、测试和部署,提高开发效率。

介绍一下Electron开发中常用的一些模块或库,以及它们的作用和使用场景。

常用的Electron模块或库及其作用和使用场景:

  1. electron模块:Electron的核心模块,提供Electron应用程序的构建和运行环境,包括主进程和渲染进程的管理、窗口创建、系统级访问等功能。
  2. electron-builder:用于打包和发布Electron应用程序的工具,支持生成多个平台的安装程序、自动更新、发布到不同渠道等功能。
  3. electron-packager:类似electron-builder,可以将Electron应用程序打包成可执行文件,并适配不同操作系统的安装包格式(如exe、dmg、deb等)。
  4. Electron Forge:一个用于快速搭建Electron应用程序项目结构的工具,提供了项目模板、自动化工作流等功能,简化了Electron应用程序的开发和打包过程。
  5. electron-store:用于在Electron应用程序中保存和获取持久化数据的模块,可以方便地管理应用程序的配置信息、用户偏好设置等数据。
  6. electron-updater:用于实现Electron应用程序的自动更新功能的模块,可以自动检查新版本、下载更新并安装,提升用户体验。
  7. electron-debug:用于在Electron应用程序中添加开发者工具的模块,可以调试应用程序、查看日志信息等,便于开发和调试过程。
  8. Electron IPC模块:用于实现Electron应用程序中不同进程间的通信的模块,包括主进程和渲染进程之间的消息传递功能,实现数据交换和功能调用。

讨论一下Electron安全性方面需要注意的事项,如何防止攻击和提高应用程序的安全性?

  1. 安全沙盒:Electron应用程序将主进程和渲染进程分开,并使用Chrome浏览器作为渲染进程的渲染引擎,从而提供了一定程度的安全隔离。但仍需谨慎处理用户输入数据,避免发生跨站脚本(XSS)攻击等安全漏洞。
  2. 安全通信:确保主进程和渲染进程之间的通信是安全的,可以使用Electron提供的IPC通信或者加密通信来保护数据传输的安全性,避免数据泄露。
  3. 避免远程代码执行:避免在Electron应用程序中执行远程或动态加载的代码,因为这可能会导致安全问题和漏洞。尽量在应用程序中使用本地可信代码。
  4. 安全更新:确保应用程序的自动更新功能是安全的,可以使用签名和校验等手段来验证更新包的完整性和真实性,防止被劫持或篡改。
  5. 文件系统访问:限制应用程序对本地文件系统的访问权限,避免恶意代码对用户数据和系统文件的恶意操作。避免直接操作系统文件,应该尽量使用Electron提供的API进行文件操作。
  6. 安全存储:确保用户敏感数据和证书等信息的安全存储,可以使用Electron提供的secure-electron-store等安全存储模块来保护用户数据。
  7. 内置Web服务安全:如果应用程序内置了Web服务,需要注意对服务器端代码进行安全编码,防止常见的Web安全漏洞,如SQL注入、跨站脚本等。
  8. 合规性和隐私:确保应用程序符合相关法规和隐私政策,避免收集和传输用户隐私数据,保护用户隐私权。

请描述一下你在使用Electron开发过程中遇到的挑战和解决方案,以及你的经验总结和教训。

  1. 跨平台兼容性:Electron应用程序需要在不同操作系统上运行,并保持一致的用户体验。在开发过程中,需要注意处理各种操作系统的差异性,如文件系统路径、系统API支持等,确保应用程序在不同平台上正常运行。

解决方案:尽量使用Electron提供的跨平台API和模块,避免直接调用系统级API;进行针对性的测试,保证应用程序在不同平台上的兼容性;使用打包工具,如electron-builder,生成适配不同平台的安装包。

  1. 性能优化:Electron应用程序基于Chromium引擎,可能存在内存占用过高、启动速度慢等性能问题。特别是对于大型的Electron应用程序,需要更加注意性能优化。

解决方案:优化渲染进程和主进程的代码,避免过度占用内存;使用Electron提供的工具进行性能分析,定位瓶颈并优化;减少无必要的资源加载和渲染操作,提高应用程序的响应速度。

  1. 安全问题:Electron应用程序需要注意安全问题,避免被攻击和数据泄露。需要注意处理用户输入和敏感数据,保护应用程序的安全性。

解决方案:严格验证和过滤用户输入,避免XSS、CSRF等安全漏洞;对IPC通信进行加密保护;注意更新机制的安全性,避免被劫持或篡改;保护用户数据和隐私信息。

如果你需要对一个现有的Web应用程序(Vue, React项目)进行移植到桌面应用程序,你将如何选择和使用Electron来完成这个任务?

  1. 创建 Electron 项目:首先需要在项目目录中初始化 Electron 项目。可以使用 Electron Forge 工具来快速生成 Electron 项目的基本结构。

    1
    2
    $ npx create-electron-app my-electron-app  
    $ cd my-electron-app
  2. 项目整合:将 React 或 Vue 项目的打包后的源代码dist/复制到 Electron 项目的 src 目录下,并在 Electron 主进程中加载 React 或 Vue 应用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // ./src/main.js
    const { app, BrowserWindow } = require('electron');

    function createWindow () {
    // 创建一个浏览器窗口,并加载 React 或 Vue 应用
    const win = new BrowserWindow({
    //
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true
    }
    });

    win.loadURL('http://localhost:3000'); // React 或 Vue 应用的开发服务器地址
    }

    app.on('ready', createWindow);
  3. 配置开发环境:在进行开发阶段,你可以运行 React 或 Vue 项目的开发服务器,并运行 Electron 项目来加载这些项目。以便进行开发调试。

    1
    2
    $ npm start  
    $ npm run start-electron
  4. 打包和发布:当项目开发完毕,你可以对应用进行打包和发布。使用 Electron Forge、Electron Builder 或 Electron Packager 等工具来打包 Electron 应用。

    1
    $ npx electron-forge make

ts

说说对typescript的理解

TypeScript 是一种由微软开发的开源编程语言,它是 JavaScript 的一个超集,提供了静态类型检查和其他特性。通过代码类型推断和编译时类型检查,TypeScript 可以在编写代码的同时帮助开发人员发现潜在错误,并提供代码提示,提高代码的可靠性和可维护性。

说说typescript与javascript区别

  • 静态类型:TypeScript 引入了静态类型检查,提供了更严格的编译时错误检查,帮助开发人员在代码编写阶段捕获潜在的错误。
  • 更好的工具支持:TypeScript 提供了更丰富的开发工具支持,如代码补全、自动重构、静态错误高亮等。
  • 更好的可维护性:通过使用类型注解、接口和类等面向对象的特性,TypeScript 可以让代码更易于理解、重构和维护方便。
  • ECMAScript 的最新特性支持:TypeScript 支持使用最新的 ECMAScript 标准,可以在JavaScript 引擎中运行

typescript 类型注释和类型推断

  • 类型注解是在变量、函数、参数等声明时显式指定类型的方式。
  • 类型推断是 TypeScript 根据变量的赋值语句自动推断出变量类型的过程

说说typescript有哪些类型

  1. 基本类型
    • number:表示数字类型。
    • string:表示字符串类型。
    • boolean:表示布尔类型。
    • null:表示空值。
    • undefined:表示未定义的值。
    • symbol:表示唯一的标识符。
    • void:表示没有返回值的函数类型。
  2. 复合类型
    • Array:表示数组类型。
    • Tuple:表示固定长度和类型的数组。
    • Object:表示对象类型。
  3. 特殊类型
    • any:表示任意类型,可以绕过类型检查。
    • unknown:表示未知类型,比 any 类型更安全。
    • never:表示永远不会有返回值的类型。
  4. 函数类型
    • Function:表示函数类型。
    • (): ReturnType:表示函数返回值的类型。
  5. 高级类型
    • Union:表示多个类型中的其中一个。
    • Intersection:表示多个类型的交集。
    • Type Aliases:表示自定义类型别名。
    • Enum:表示枚举类型。
    • Generics:表示泛型类型。

说说typescript中高级类型的理解

高级类型是指一些复杂、灵活且强大的类型操作和编程模式,可以用来描述和操作各种类型。

  1. 联合类型(Union Types)
    • 联合类型表示一个值可以是多种类型中的一种。使用 | 符号将多个类型联合在一起。
    • 例如,string | number 表示某个值可以是字符串或数字类型。
  2. 交叉类型(Intersection Types)
    • 交叉类型表示将多个类型合并为一个类型。使用 & 符号将多个类型交叉在一起。
    • 例如,A & B 表示将类型 A 和类型 B 的属性和方法合并为一个新的类型。
  3. 类型别名(Type Aliases)
    • 类型别名可以用来为复杂的类型或联合类型定义简洁的别名。使用 type 关键字定义类型别名。
    • 例如,type MyString = string | null | undefined 定义了一个名为 MyString 的联合类型别名。
  4. 条件类型(Conditional Types)
    • 条件类型基于判断条件来选择不同的类型。通过使用条件类型,根据输入的类型参数决定返回的结果类型。
    • 例如,T extends U ? X : Y 表示如果类型 T 可以赋值给类型 U,则返回类型 X,否则返回类型 Y
  5. 映射类型(Mapped Types)
    • 映射类型可以通过映射现有类型的每个属性来创建一个新类型。可以在类型级别上操作对象的属性。
    • 例如,type Readonly<T> = { readonly [P in keyof T]: T[P] } 创建了一个将所有属性变为只读的类型。
  6. Readonly 类型
    • Readonly<T> 类型可以将对象的所有属性变为只读属性,防止对对象的属性进行修改。
    • 例如,type Point = Readonly<{ x: number, y: number }> 定义了一个不可修改的点类型。

什么是类?如何在 TypeScript 中创建类

类是一种面向对象的编程概念,用于描述具有相同属性和方法的对象的模板。在 TypeScript 中使用 class 关键字创建类,并使用 new 关键字创建类的实例

什么是接口?如何在 TypeScript 中使用接口?

接口是用于定义对象的类型的一种方式,它描述了一个对象应该具有的属性和方法。使用 interface 关键字创建接口,并在对象中实现该接口。

什么是泛型(Generics)?如何在 TypeScript 中使用泛型?

泛型允许在定义函数、类或接口时延迟指定具体类型,而在使用时根据需要指定具体类型。在 TypeScript 中使用 <T> 语法定义泛型,并在代码中使用具体类型替代 T

如何声明和使用枚举(Enum)类型?

在 TypeScript 中使用 enum 关键字来声明和定义枚举类型。枚举类型可以作为一组命名的常量值的集合。

什么是 TypeScript 断言?

TypeScript 断言是一种方式,让编译器知道一个值的类型。并且可以强制将该值视为指定的类型

TypeScript 中的两种类型断言的语法和用法。

TypeScript 中有两种类型断言的语法:尖括号语法和 as 语法。

  • 尖括号语法:let someValue: any = "hello"; let strLength: number = (<string>someValue).length;
  • as 语法:let someValue: any = "hello"; let strLength: number = (someValue as string).length;

什么时候应该使用类型断言?

  • 当编译器无法自动推断变量的类型,或者开发人员更了解某个变量的具体类型时,可以使用类型断言来告诉编译器该变量的准确类型。
  • 当需要在某个类型上调用特定方法,但编译器无法正确推断类型的时候也可以使用类型断言。

什么是非空断言运算符(!)?它在 TypeScript 中的作用是什么?

  • 非空断言运算符 ! 可以告诉 TypeScript 编译器一个值不会为 nullundefined
  • 当开发人员明确知道某个值不会是 nullundefined 时,可以使用非空断言运算符来告诉编译器。
1
2
3
4
5
6
7
let element: HTMLElement | null = document.getElementById('app');  
// 这里 TypeScript 会认为 element 可能为 null,会有警告

// 使用非空断言运算符来告诉编译器 element 不会为 null
element!.textContent = 'Hello, TypeScript!';

// 在这里,我们明确知道 element 不会为 null,所以可以安全地访问它的属性或方法

ts类

在 TypeScript 中,可以使用类(class)来创建对象和定义对象的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {  
name: string;
age: number;

constructor(name: string, age: number) {
this.name = name;
this.age = age;
}

greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}

let person = new Person("Alice", 30);
person.greet();

ts类的继承

类之间可以通过继承创建父类和子类的关系。子类可以继承父类的属性和方法,并且可以添加自己的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Student extends Person {  
grade: number;

constructor(name: string, age: number, grade: number) {
super(name, age);
this.grade = grade;
}

study() {
console.log(`${this.name} is studying in grade ${this.grade}.`);
}
}

let student = new Student("Bob", 12, 6);
student.greet();
student.study();

ts修饰符

修饰符可以限制类成员的访问权限,包括 public(默认)、privateprotected

  • public:所有地方都可以访问。
  • private:仅在类内部可以访问。
  • protected:类内部和子类可以访问。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Animal {  
private _name: string;
protected _sound: string;

constructor(name: string, sound: string) {
this._name = name;
this._sound = sound;
}

makeSound() {
console.log(this._sound);
}
}

class Dog extends Animal {
bark() {
console.log(`The dog named ${this._name} is barking.`);
}
}

let dog = new Dog("Buddy", "Woof");
dog.makeSound(); // Accessible
dog.bark(); // Accessible
// dog._name; // Private member '_name' cannot be accessed outside class 'Animal'

ts抽象类&使用技巧

抽象类是不能直接实例化的类,主要用于作为其他类的基类。抽象类可以包含抽象方法和具体方法的实现,子类继承抽象类时必须实现抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
abstract class Shape {  
abstract calculateArea(): number;

printArea() {
console.log(`Area: ${this.calculateArea()}`);
}
}

class Circle extends Shape {
constructor(private radius: number) {
super();
}

calculateArea() {
return Math.PI * this.radius ** 2;
}
}

let circle = new Circle(5);
circle.printArea(); // Output: Area: 78.53981633974483

ts枚举类型

枚举(enum)是一种包含命名常量的数据结构,用于定义一组常量值。

1
2
3
4
5
6
7
8
9
enum Direction {  
Up = "Up",
Down = "Down",
Left = "Left",
Right = "Right",
}

let direction: Direction = Direction.Right;
console.log(direction); // Output: Right

git

常用 Git 命令

  1. 基本操作
    • git init:初始化一个新的 Git 本地仓库。
    • git clone <repo_url>:克隆一个远程仓库。
    • git status:查看当前工作区和暂存区的状态。
    • git add <file>:将文件添加到暂存区。
    • git commit -m "commit message":提交暂存区的更改。
    • git push:将本地更改推送到远程仓库。
    • git pull:从远程仓库拉取最新的更改并合并到本地。
  2. 查看历史
    • git log:查看提交历史。
    • git diff:查看未暂存的更改。
    • git show <commit_hash>:查看某次提交的详细信息。
  3. 分支操作
    • git branch:列出所有分支。
    • git branch <branch_name>:创建新分支。
    • git checkout <branch_name>:切换到指定分支。
    • git merge <branch_name> / git rebase:合并指定分支到当前分支。
  4. 提交撤销操作
    • 未暂存的撤销可以使用checkout
    • 已暂存的撤销可以使用reset
    • 回滚代码,可以使用reset配合参数( 硬重置,软重置)
    • 撤销提交可以使用revert

如何撤销更改?

  • 撤销未暂存的更改:

    1
    git checkout -- <file>  
  • 撤销已暂存的更改:

    1
    git reset HEAD <file>
  • 回滚到某个提交并丢弃后续更改:

    1
    git reset --hard <commit_hash>

如何回滚代码

  1. 使用 git reset 回滚至某个提交

    • 回到某个提交并保留未提交的更改:

      1
      git reset <commit_hash>
    • 回到某个提交并丢弃未提交的更改:

      1
      git reset --hard <commit_hash>  
  2. 使用 git revert 创建一个新提交

    • 如果希望撤销某次提交,并且保持项目历史完整,可以使用:

      1
      git revert <commit_hash>  
    • 这会生成一个新的提交,反转指定的提交。

  3. 软重置(Soft Reset):这会撤销最新的提交,并将更改保留在暂存区中。

    1
    git reset --soft HEAD^
  4. 硬重置(Hard Reset):这会撤销最新的提交,并将更改完全删除。

    1
    git reset --hard HEAD^

如何合并分支

  1. 确保当前在目标分支

    • 切换到想要合并到的分支,例如 main或master

      1
      git checkout main  
  2. 合并指定分支

    • 合并其他分支(例如 feature-branch)到当前分支:

      1
      git merge feature-branch  
    • 如果合并时没有冲突,Git 会自动完成合并。

    • 如果出现冲突,将会提示你手动解决冲突。

什么是“rebase”,它与“merge”有什么不同?

  • **merge**:保留所有分支的历史,创建一个新的合并提交。
  • **rebase**:将一个分支的更改“移动”到另一个分支的基础上,形成一条线性历史,不会留下合并提交。rebase 通常会使历史记录更加简洁。

解决合并冲突

  1. 编译冲突的文件。

  2. 手动编辑冲突的行。

  3. 保存文件后,使用:

    1
    git add <resolved_file>  
  4. 完成合并:

    1
    git commit -m "Merge branch 'feature-branch'"  

将工作区保存临时区域

要回到某个提交并保留未提交的更改,然后再回到原来的工作区,您可以按照以下步骤进行操作:

  1. 使用 git stash 命令将当前未提交的更改保存到一个临时区域(stash)中:

    1
    git stash  
  2. 使用 git log 命令查找您要回到的提交的哈希值(commit hash)。

  3. 使用 git checkout 命令切换到您要回到的提交:

    1
    git checkout <commit-hash>  
  4. 在回到的提交中进行必要的操作。

  5. 如果您完成了在回到的提交中的操作,并且想要回到原来的工作区,可以使用 git stash apply 命令将之前保存的更改应用到当前工作区:

    1
    git stash apply  

    如果您有多个 stash,可以使用 git stash list 命令查看 stash 列表,并使用 git stash apply stash@{<stash-index>} 应用特定的 stash。

网络协议 & 兼容

http 状态码

五个类别:

1xx - 信息性状态码:表示请求已被接收,继续处理。

2xx - 成功状态码:表示请求已成功被服务器接收、理解和处理。

3xx - 重定向状态码:表示需要进一步操作以完成请求。

4xx - 客户端错误状态码:表示服务器无法处理请求,客户端可能需要修改请求。

5xx - 服务器错误状态码:表示服务器在处理请求时发生了错误。

  • 200 OK:请求成功,服务器成功返回请求的数据。
  • 201 Created:请求成功,服务器成功创建了新的资源。
  • 204 No Content:请求成功,服务器成功处理了请求,但没有返回任何内容。
  • 301 Moved Permanently:请求的资源已永久移动到新的 URL。
  • 400 Bad Request:请求无效,服务器无法理解请求的语法。
  • 401 Unauthorized:请求要求身份验证,客户端未提供有效的身份验证凭据。
  • 500 Internal Server Error:服务器在执行请求时发生了未知的内部错误。
  • 501 Not Implemented:服务器不支持实现请求所需要的功能,或者无法完成请求。
  • 502 Bad Gateway:作为网关或代理工作的服务器尝试执行请求时,从上游服务器接收到无效响应。
  • 503 Service Unavailable:服务器暂时无法处理请求(可能是由于过载或维护)。
  • 504 Gateway Timeout:作为网关或代理工作的服务器尝试执行请求时,但并未及时从上游服务器接收到响应。
  • 505 HTTP Version Not Supported:服务端不支持客户端指定的 HTTP 版本。

介绍 HTTP 的基本原理和工作过程。

HTTP TCP下的用于传输超文本的协议,是 Web 数据传输的基础。通过请求-响应的交互方式实现数据传输。

工作过程:

  1. 客户端发起请求:HTTP 是基于客户端-服务器模式工作的,客户端(一般是浏览器)向服务器发起 HTTP 请求。请求由请求行、请求头和请求体组成,请求行包括请求方法(如 GET、POST)、请求的 URL(统一资源定位符)、协议版本等。

  2. 服务器处理请求:服务器接收到客户端的请求后,根据请求的 URL 定位资源,并执行相应的处理逻辑,如读取文件、执行程序等。服务器处理完请求后会生成一个 HTTP 响应发送给客户端。

  3. 服务器返回响应:HTTP 响应由响应行、响应头和响应体组成。响应行包括协议版本、状态码(用于表示请求的处理结果)和状态消息。响应头包括各种元数据,如日期、内容类型等。响应体则包含实际的响应内容。

  4. 客户端接收响应:客户端接收到服务器返回的 HTTP 响应后,根据响应的内容进行处理,展示给用户或者执行相关操作。如果需要加载额外资源(如图片、样式表、脚本等),客户端会再次向服务器发起新的请求。

  5. TCP 连接:HTTP 是基于 TCP/IP 协议的,通信双方需要建立 TCP 连接才能进行数据传输。连接建立后,客户端和服务器之间可以通过该连接进行数据传输,传输完成后可以关闭连接。

  6. 状态保持:HTTP 协议本身是无状态的,即服务器不会保留关于客户端的状态。为了实现状态保持,通常使用一些技术手段,比如 Cookie、Session 或者 Token 来实现用户状态的管理和保持。

http长连接和短连接

HTTP 请求方法有哪些?它们各自的作用是什么?

什么是状态码?举例说明几个常见的状态码及其含义。

介绍HTTP/1 , HTTP/2 和 HTTP/3 的特性和区别。

RESTful 架构风格包括哪些设计原则?

http和https区别

  1. 安全性:
    • HTTP 是超文本传输协议 (Hypertext Transfer Protocol) 的缩写,它使用明文传输数据。这意味着通过 HTTP 传输的数据在网络上可以被轻松地截取和窥视,存在较高的安全风险。
    • HTTPS 是安全超文本传输协议 (Hypertext Transfer Protocol Secure) 的缩写,它使用了 SSL/TLS 协议对数据进行加密。HTTPS 能够确保客户端和服务器之间发送的数据经过加密,并且提供身份验证机制,因此通信更加安全可靠。
  2. 加密:
    • HTTP 不对发送或接收的数据进行加密处理,因此可能会被恶意攻击者截取并窥视其中包含的敏感信息。
    • HTTPS 使用 SSL/TLS 协议对通信过程中所发送、接收或存储到服务器上面所有信息(如个人信息、银行账号等)进行加密。
  3. 默认端口:
    • HTTP 默认端口为 80。
    • HTTPS 默认端口为 443。

HTTPS 相对于 HTTP 的优点和工作原理。

  1. 安全性
  2. 数据完整性
  3. 身份验证
  4. 改善搜索排名

https加密过程(SSL/ TLS)

  1. 客户端请求建立安全连接
  2. 服务器响应并验证身份
  3. 客户端生成密钥
  4. 服务器解密密钥
  5. 双方生成会话密钥
  6. 数据传输加密

https证书如何防止篡改,防调包

SSL/TLS 握手过程包括哪些步骤?

get和post区别

  1. 数据传输方式:
    • GET:通过 URL 参数传递数据,数据附在 URL 后面,以 ? 开始,参数之间以 & 分隔。例如:http://example.com/path?name=value&age=30
    • POST:通过请求体传递数据,在 HTTP 请求的消息体中发送,并不会显示在 URL 中。
  2. 安全性:
    • GET:因为参数附在 URL 上,所以可能会被保存在浏览器历史记录、代理服务器日志等地方,不适合发送敏感信息。
    • POST:由于数据不会暴露在 URL 上,在一定程度上比 GET 更安全,并且可以使用 HTTPS 进行加密传输。
  3. 数据长度限制:
    • GET 请求对URL长度有限制(约 2KB),而 POST 没有明确的长度限制(取决于服务器和客户端设置)。
  4. 幂等性:
    • GET 请求通常是幂等的。即多次相同的请求会产生相同结果并且没有副作用。
    • POST 请求通常是非幂等的。即多次相同的请求可能每次产生不同结果或者有副作用(如创建一个新资源)。
  5. 缓存处理:
    • 对于相同URL和参数GET会被缓存到浏览器缓存或者CDN,POST则将忽略来自服务器指令

浏览器渲染原理

  1. 从网络获取HTML:浏览器通过网络加载HTML文件。这可能包括从服务器请求主HTML文档以及从文档中加载的外部资源(如CSS、JavaScript、图像等)。
  2. 创建DOM树:浏览器将HTML解析为DOM(文档对象模型)树,即一个包含HTML标签的层次结构表示。这是页面的逻辑结构,反映了HTML中元素的父子关系。
  3. 构建CSSOM树:同时,浏览器会加载并解析外部CSS文件,并将其转换为CSSOM(CSS对象模型)树。这是一个表示CSS规则和其应用到文档中元素的树结构。
  4. 合并DOM和CSSOM树:浏览器将DOM树和CSSOM树合并为一个渲染树(render tree)。渲染树只包括需要显示的节点和这些节点的样式信息,不包括不可见的元素(例如<head>中的元素)。
  5. 布局(Layout):浏览器对渲染树进行布局,计算每个节点在屏幕上的位置和大小。这个过程被称为重排(reflow)。
  6. 绘制(Painting):最后,浏览器将布局后的元素绘制在屏幕上,这个过程叫做绘制(painting)。

浏览器缓存 - http缓存

指浏览器在访问网页时将一些静态资源存储在本地的过程。这样做可以减少对服务器的请求次数,加快页面加载速度,并节省带宽消耗。
浏览器缓存通常分为两种类型:强缓存协商缓存

  • 强制缓存:通过设置响应头控制客户端是否直接使用缓存而不发送请求到服务器。常见的控制头有 Cache-ControlExpires
    • Cache-Control:指令包括 max-age(缓存有效时间)、no-cache(需要进行协商缓存验证)等。
    • Expires:设置资源过期时间,是一个绝对时间。
  • 协商缓存:当强制缓存失效时,客户端会发送请求到服务器验证资源是否更新。主要通过 If-Modified-SinceIf-None-Match 这两个请求头来实现。
    • If-Modified-Since:表示资源的最后修改时间,服务器会比较该时间与资源的修改时间来判断资源是否需要更新。
    • If-None-Match:表示资源的唯一标识符(通常是 ETag),服务器会比较该标识符与当前资源的标识符来判断资源是否

浏览器缓存 - 本地缓存。cookie,localStorage,sessionstorage,会把数据存在哪,受不受同源策略制约

  • Cookie:cookie是一种在客户端存储数据的机制。可以通过在HTTP响应头中设置Set-Cookie头部来创建和修改Cookie,下次请求信息的时候会自动在请求头中包含Cookie。Cookie有一些限制,例如每个响应的Cookie数量和总大小都有限制,同时Cookie会在每次请求时都被发送到服务器,会增加网络流量。Cookie 受同源策略的限制,只能被设置和访问与其所属网站相同的域名、协议和端口。
  • localStorage:是HTML5新增的一种在客户端存储数据的机制,数据以键值对的形式存储在客户端的浏览器中。数据会永久保存在浏览器的本地存储空间中,除非被显式清除或网站使用代码进行删除。每个域名有独立的 localStorage 存储空间,受同源策略的限制。
  • sessionStorage:sessionStorage 也是以键值对的形式将数据存储在客户端的浏览器中。与 localStorage 不同的是,sessionStorage 中的数据在数据只在当前会话(当前的浏览器窗口或者选项卡)有效。结束后会被清除,即当用户关闭浏览器标签页或窗口时会话结束。每个域名有独立的 sessionStorage 存储空间,受同源策略的限制。

localStorage存储超过限制怎么处理

  1. 对超过限制的数据进行分割
  2. 压缩数据
  3. 使用服务器端存储
  4. IndexedDB或WebSQL本地存储方案

跨域请求如何产生?如何解决跨域问题?

跨域请求是指浏览器端发起的请求在安全策略下跨越了当前页面所在的域名、端口或协议,去请求其他域名下的资源。跨域请求是指浏览器端发起的请求在安全策略下跨越了当前页面所在的域名、端口或协议,去请求其他域名下的资源。

  1. JSONP(JSON with padding):通过动态插入<script>标签,使用回调函数的方式获取跨域的数据。JSONP只支持GET请求,且需要接口提供支持和配合。
  2. CORS(跨域资源共享):在服务端设置响应头Access-Control-Allow-Origin允许特定的域名访问,或者使用通配符*表示允许所有域名访问。这样浏览器在发起跨域请求时,会在请求头里携带Origin字段,服务端根据这个字段来决定是否允许跨域请求。
  3. 代理服务器:在你的服务器上设置一个代理,用于转发请求。客户端向自身的服务端发出请求,然后服务端将请求发送给目标服务器,并将目标服务器的响应返回给客户端。由于请求是在同一个域名下完成的,跨域问题得到解决。

cookie,session,token是保持用户会话状态的一个认证机制,因为HTTP 协议是一种无状态的协议,每个请求都是独立的。cookie会受同源策略的限制。session,token不受同源策略的限制。

  • Cookie:Cookie 是一种在客户端存储数据的技术,通常是由服务器发送到客户端的小型文本文件,以键值对的形式存在。通过HTTP响应头中的Set-Cookie字段传递给客户端,存储在客户端的浏览器中。Cookie 可以包含各种信息,如用户身份标识、会话状态,当前页面的状态,域名,过期时间等,在后续的请求中,浏览器会自动携带相应的Cookie信息发送到服务器。
  • Session:Session 是一种在服务器端存储用户会话数据的机制。当用户访问网站时,服务器会为用户创建一个唯一的会话标识符(Session ID),并将该标识符存储在服务器上,由服务器进行管理。客户端通常通过 Cookie 中的 Session ID 来与服务器建立关联,以便在后续的请求中识别用户会话。Session 数据不受同源策略的限制。
  • Token:Token 是一种用于验证用户身份和授权访问的令牌。它通常是由服务器生成的一串字符,可以包含用户身份信息和权限等数据。作为客户端和服务器之间进行身份验证和授权的凭据。Token 不依赖于服务器端的存储,而是通过在客户端和服务器之间传递进行验证。可以作为请求头的一部分发送到服务器。Token 可以用于实现无状态的身份验证和授权机制,不受同源策略的限制。
  • Name:Cookie 的名称,用于标识特定的 Cookie。
  • Value:Cookie 的值,存储在客户端的浏览器中,并在每次 HTTP 请求中被发送到服务器。
  • Domain:指定 Cookie 可以发送到哪些域名。默认情况下,Cookie 只能发送到设置它的页面所在的域名。可以通过设置 Domain 属性来扩展 Cookie 的作用域。
  • Path:指定 Cookie 可以发送到哪些路径。默认情况下,Cookie 只能发送到设置它的页面所在的路径。可以通过设置 Path 属性来限制 Cookie 的作用范围。
  • Expires/Max-Age:指定 Cookie 的过期时间。可以通过设置 Expires 属性为一个具体的日期时间或 Max-Age 属性为一个相对时间(秒数)来控制 Cookie 的有效期。

JWT(JSON Web Token)

JWT原理

服务器认证完成以后,会生成一个JSON对象,发挥给用户。之后用户与服务器通信时,都会发回JSON对象。服务器完全只靠这个对象认证用户身份。为了防止数据篡改,服务器会在生成JSON对象时,加上签名。

JWT结构

token是一个很长的字符串,中间用点 (.) 分隔成三部分分别为 ( 头部-Header.负载-Payload.签名-Signature)。内部是没有换行的

认证流程

  1. 浏览器发起请求登录,用户携带用户名和密码等信息
  2. 服务器验证身份,根据算法,将用户标识符打包生成token
  3. 服务器返回JWT信息给浏览器
  4. 浏览器发起请求获取用户资料,会带上刚刚拿到的token
  5. 服务器数据发现有携带token,会进行身份验证
  6. 身份验证通过会返回该用户资料

特点

  • JWT默认是不加密的
  • JWT也可以用于信息交换,降低服务器查询数据库的次数
  • 最大缺点是,服务器不保存session状态,导致使用过程中,JWT一旦签发,在有效期内都能够使用,所以一旦泄露,任何人都可以获得该令牌的所有权限。除非加了其他逻辑判断。或者将有效期设置短一些

token 无感刷新

token无感刷新,是在用户已经登录后,获取token时,在该token快过期时,自动调用后端,后端身份验证成功后,返回新token
通过这种方式,可以在不打扰用户的情况下,自动刷新 Token,并确保用户的登录状态保持有效。

如果是app应用,可以 使用推送通知 或 **使用后台服务(Android 应用)**或 定时任务的方式进行无感刷新

Websocket 相对于传统 HTTP 请求有哪些优势?

  1. 实时性:Websocket 提供了双向通信通道,使服务器可以主动向客户端推送数据,而不需要等待客户端发起请求。这样就可以实现实时数据传输,适用于需要实时更新的应用场景,比如聊天应用、实时数据监控等。
  2. 较低的通信开销:传统的 HTTP 请求每次通信都需要建立连接、传输请求头,而 Websocket 在初始连接建立后,可以保持长连接,减少了每次通信的开销,只需轻量级的数据帧来传输数据。
  3. 更少的网络延迟:由于 Websocket 建立了长连接,避免了重复建立和关闭连接造成的网络延迟,使得通信更加高效。这对于实时交互和快速响应的应用更为重要。
  4. 跨域通信:Websocket 建立在 TCP 协议上,不受同源策略的限制,可以轻松实现跨域通信,而传统的 HTTP 请求就受同源策略的限制。
  5. 更灵活的数据格式:Websocket 可以传输任意格式的数据,包括二进制数据,而传统的 HTTP 请求主要是基于文本的格式。

Websocket 建立连接的握手过程。

  1. 客户端发送一个 HTTP GET 请求给服务器
    包含特殊的 Upgrade 和 Connection 头部,以请求升级到 WebSocket 协议。请求示例:
1
2
3
4
5
6
GET /chat HTTP/1.1  
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

其中:

  • Upgrade 头部指定协议升级到 WebSocket。
  • Connection 头部表示希望使用长连接。
  • Sec-WebSocket-Key 是一个随机生成的密钥,用于安全验证。
  1. 服务器收到请求后进行处理
  • 验证客户端的请求合法性。例如,验证 Upgrade 头部是否为 websocket,Sec-WebSocket-Version 是否支持服务器的版本等。
  • 生成一个握手响应。响应示例:
1
2
3
4
HTTP/1.1 101 Switching Protocols  
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

其中:

  • Status Code 为 101 表示切换协议成功。
  • Sec-WebSocket-Accept 是服务器使用客户端传递的 Sec-WebSocket-Key 计算得到的响应密钥。
  1. 客户端收到服务器的响应后进行处理
  • 验证服务器响应的合法性。比对 Sec-WebSocket-Accept 是否与客户端请求中的 Sec-WebSocket-Key 计算得到的结果匹配等。
  • 若验证通过,握手成功,协议升级为 WebSocket,并开始进行双向数据传输。

DNS 解析流程是怎样的?

  1. 用户在浏览器中输入要访问的域名(例如,www.example.com)。./)
  2. 浏览器首先查询本地 DNS 缓存,如果缓存中存在对应的 IP 地址,则直接返回该地址,结束解析流程。
  3. 如果本地 DNS 缓存中不存在对应的 IP 地址,则浏览器向本地 DNS 服务器发送 DNS 查询请求。
  4. 本地 DNS 服务器(通常由 Internet 服务提供商提供)查询自己的缓存,如果有对应的 IP 地址,则直接返回给浏览器。
  5. 如果本地 DNS 服务器的缓存中没有对应的 IP 地址,则本地 DNS 服务器需要进行迭代查询:
    • 首先,本地 DNS 服务器向根域名服务器发送查询请求,询问顶级域名服务器(例如,.com 域名服务器)的 IP 地址。
    • 根域名服务器返回顶级域名服务器的 IP 地址给本地 DNS 服务器。
    • 本地 DNS 服务器再次向顶级域名服务器发送查询请求,询问次级域名服务器(例如,example.com 域名服务器)的 IP 地址。
    • 次级域名服务器返回具体域名服务器(例如,www.example.com)的-el7j/) IP 地址给本地 DNS 服务器。
  6. 本地 DNS 服务器将获取的 IP 地址返回给浏览器,并将 IP 地址保存在缓存中,以备将来的查询使用。
  7. 浏览器通过获得的 IP 地址与目标服务器建立 TCP 连接,发送 HTTP 请求,并获取相应的数据。

DNS 负载均衡、故障恢复等相关概念。

DNS 负载均衡和故障恢复是通过 DNS 服务器进行管理和调度的一种技术

  1. DNS 负载均衡(DNS Load Balancing):
    DNS 负载均衡是通过域名解析系统将请求分配到多个不同的服务器上,以实现负载均衡和分流流量的目的。当一个域名被解析为多个 IP 地址时,不同的请求可能会被分配到不同的服务器上,从而分散服务器负载,提高系统性能和稳定性。DNS 负载均衡可以基于权重、轮询、IP 地址范围等算法来实现。

  2. DNS 故障恢复(DNS Failover):
    DNS 故障恢复是指当某个服务器或服务发生故障时,DNS 服务器可以迅速将流量重定向到备用服务器或服务上,以实现系统的高可用性和灾难恢复。通常情况下,DNS 服务器会通过监控机制来检测服务器的可用性,一旦发现故障,会自动将解析请求引导到备用服务器上,避免服务中断或延迟。

  3. DNS 热备份和冷备份:

    • DNS 热备份:热备份是备用服务器处于待命状态,可以即时接管流量,实时同步数据并保证服务的运行。在 DNS 故障恢复中,热备份通常用于快速响应和保证系统持续稳定运行。

    • DNS 冷备份:冷备份是备用服务器处于休眠状态,只有在主服务器故障时才会启动并接管流量,通常需要手动介入。在 DNS 故障恢复中,冷备份用于较低需求下的备份方案。

OSI七层模型

  1. 物理层(Physical Layer)
    这是网络通信的最低层,负责定义物理介质以及数据的传输方式。它处理的是比特流的传输,包括电压、电流和物理接口等。
  2. 数据链路层(Data Link Layer)
    数据链路层负责将原始比特流转换为数据帧,并在物理介质上进行传输。它还提供了错误检测和纠正机制,并管理不同设备之间的数据流动。
  3. 网络层(Network Layer)
    网络层处理数据的分组和路由,为数据包选择最佳路径。它负责不同网络之间的连接和通信,并支持 IP 协议。
  4. 传输层(Transport Layer)
    传输层提供可靠的端到端数据传输,处理数据分段和重组,以及错误检测和恢复。它还负责协议选择(如TCP或UDP)和端口管理。
  5. 会话层(Session Layer)
    会话层负责建立、管理和终止应用程序之间的通信会话。它提供了会话控制和同步功能,以确保通信的顺序和可靠性。
  6. 表示层(Presentation Layer)
    表示层负责数据的格式化和转换,以确保不同应用程序之间的数据兼容性。它处理数据的加密、压缩、编码等。
  7. 应用层(Application Layer)
    应用层是最高层,提供用户与网络服务之间的接口。它包括各种应用协议,如HTTP、FTP、SMTP等,并处理特定应用程序的通信需求。

TCP 和 UDP 的区别及应用场景。

  1. 可靠性
    • TCP 是一种可靠的协议,它提供了数据包的确认和重传机制,以确保数据的可靠传输。它使用序列号、确认和超时重传等机制来保证数据的准确性和完整性。
    • UDP 是一种不可靠的协议,它不提供数据包的确认和重传机制。数据的传输没有确认机制,可能会丢失、重复或者无序。
  2. 连接性
    • TCP 是面向连接的协议,通过三次握手建立连接,并保持双方的连接状态。在数据传输完成后,通过四次挥手断开连接。
    • UDP 是无连接的协议,它不需要建立和维护连接。每个数据包都是独立的,可以独立地发送或接收。
  3. 传输效率
    • TCP 在传输可靠性方面提供了很好的保证,但由于引入了确认和重传机制,以及连接的建立和断开过程,会增加一些开销,导致传输效率相对较低。
    • UDP 没有确认和重传机制,以及连接建立过程,传输效率相对更高。数据包的发送和接收速度更快。
  4. 应用场景
    • TCP 适用于对数据传输可靠性要求较高的应用场景,如文件传输、电子邮件、网页浏览等。它可以确保数据的完整性和顺序,但传输效率相对较低。
    • UDP 适用于对实时性要求较高、数据传输可靠性要求相对较低的应用场景,如音视频流媒体、在线游戏、网络电话等。它传输效率高,但对数据的可靠性要求不高。

TCP 三次握手四次挥手

TCP 三次握手(Three-way Handshake):

  1. 第一步:客户端发送 SYN
    • 客户端向服务器发送一个 SYN(同步)报文段,表示希望与服务器建立连接。
  2. 第二步:服务器发送 SYN-ACK
    • 服务器收到 SYN 报文后,会回复一个 SYN-ACK(同步-确认)报文段,表示同意与客户端建立连接,并确认客户端的 SYN。
  3. 第三步:客户端发送 ACK
    • 客户端收到 SYN-ACK 后,会向服务器发送一个 ACK(确认)报文段,表示收到了服务器的确认,连接建立完成。

TCP 四次挥手(Four-way Handshake):

  1. 第一步:客户端发送 FIN
    • 当客户端需要关闭连接时,会向服务器发送一个 FIN(结束)报文段,表示不再发送数据,但仍允许接收数据。
  2. 第二步:服务器发送 ACK
    • 服务器收到客户端的 FIN 后,会向客户端发送一个 ACK,确认收到 FIN。
  3. 第三步:服务器发送 FIN
    • 当服务器也准备关闭连接时,会向客户端发送一个 FIN,表示不再发送数据,但仍允许接收数据。
  4. 第四步:客户端发送 ACK
    • 客户端收到服务器的 FIN 后,会向服务器发送一个 ACK,确认收到 FIN。此时,连接完全关闭。

什么是UTF-8编码

UTF-8编码使用1个字节来表示ASCII字符,使用2到4个字节来表示其他Unicode字符。这种灵活性使得UTF-8成为目前最常用的Unicode编码方式之一。

字符编码的诞生过程可以追溯到计算机科学的早期。下面是字符编码的主要里程碑和发展过程:

  1. ASCII编码(1960年代):ASCII(American Standard Code for Information Interchange)是最早的字符编码标准之一。它使用7位二进制数(后来扩展为8位),编码了一些基本的英文字母、数字和符号,用于在计算机系统中传输和存储英文字符。
  2. 扩展ASCII编码(1970年代):随着计算机的发展和国际化需求的增加,ASCII编码的范围不再能满足其他语言的需求。因此,针对不同语言的扩展ASCII编码出现了,如ISO8859编码系列。
  3. Unicode标准(1990年代):为了解决多语言字符编码的问题,Unicode(统一码)标准被提出。Unicode致力于为全球范围内的所有字符和符号提供唯一的编码值。它采用了16位的编码空间,共能够表示65,536个字符。
  4. UCS-2编码(1990年代):Unicode最早采用的是UCS-2编码,即用两个字节表示一个字符。但由于这个编码方式无法表示超过65,536个字符的Unicode字符,后来被更先进的UTF-16编码取而代之。
  5. UTF编码(1990年代):为了兼容ASCII字符和较低范围的Unicode字符,UTF(Unicode Transformation Format)编码被引入。UTF-8、UTF-16和UTF-32是UTF编码的主要变种,分别使用不同的存储方式来表示Unicode字符,其中UTF-8是最常用的一种。
  6. UTF-8编码的推广(2000年代):随着互联网的发展和全球化的趋势,UTF-8成为了互联网上的主流字符编码方式。它具有兼容ASCII、变长编码和节省存储空间的特点,被广泛应用于文本处理、网页和网络通信等领域。

什么是浏览器兼容性问题?

浏览器兼容性问题指的是页面代码在不同浏览器在解析和渲染网页时存在的差异,导致网页在不同浏览器上显示不一致或出现错误。

如何检测和处理特定浏览器的兼容性问题?

可以使用现有的工具和服务对页面进行检测,BrowserStack和CrossBrowserTesting等工具。来检测不同浏览器的功能支持情况。

处理特定浏览器的兼容性问题通常需要使用特定的解决方案或技术,例如使用CSS前缀以适应某些浏览器的私有属性,或使用相应的JavaScript库和Polyfill来提供缺失的功能支持。

CSS一般会出现什么样的兼容性问题,怎么解决

常见的CSS兼容性问题包括盒模型差异、浮动布局问题、选择器支持不一致等。

  • 使用CSS Reset或Normalize.css来重置或规范化浏览器默认样式。
  • 使用box-sizing属性来统一盒模型的解析方式。
  • 避免过度使用浮动,可以使用Flexbox或Grid布局来代替。
  • 针对选择器支持不一致,可以使用Polyfill或JavaScript库来提供相应的功能支持。

JavaScript一般会出现什么样的兼容性问题,怎么解决

常见的JavaScript兼容性问题主要是一些浏览器对ES语法新特性支持度不同、浏览器API差异和JavaScript引擎的不同表现等。

  • 使用Babel等工具将ES6+的代码转换为ES5语法,以提供更好的兼容性。
  • 检测并处理不同浏览器的API差异,可以使用Modernizr等库或自定义的特性检测方法。
  • 针对不同JavaScript引擎的不同表现,可以使用Polyfill或特定库来提供功能支持,例如使用axios来规避XMLHttpRequest的差异性。

在项目开发中,如何保证跨浏览器的兼容性?

  • 使用项目构建工具进行代码转换达到兼容性处理
  • 在开发过程中进行跨浏览器的测试,确保网页在主流浏览器上均有良好的展示效果。
  • 遵循Web标准,使用符合规范的HTML、CSS和JavaScript代码。或者使用浏览器特定的兼容性代码
  • 使用重置样式表或Normalize.css来规范化不同浏览器的默认样式。
  • 使用兼容性库、Polyfill或特定框架,以提供一致的功能支持。
  • 定期更新项目的依赖项和技术栈,以保持对最新浏览器版本的支持。

MySql

  1. 数据库和表的创建:
  • 创建数据库:CREATE DATABASE database_name;
  • 使用数据库:USE database_name;
  • 创建表:CREATE TABLE table_name (column1 datatype, column2 datatype, ...);
  1. 基本的数据类型:
  • 整数类型:INT, BIGINT, SMALLINT, TINYINT
  • 浮点数类型:FLOAT, DOUBLE, DECIMAL
  • 字符串类型:CHAR, VARCHAR, TEXT
  • 日期和时间类型:DATE, TIME, DATETIME
  1. 数据的插入、查询、更新和删除:
  • 插入数据:INSERT INTO table_name (column1, column2) VALUES (value1, value2);
  • 查询数据:SELECT column1, column2 FROM table_name WHERE condition;
  • 更新数据:UPDATE table_name SET column1 = value1 WHERE condition;
  • 删除数据:DELETE FROM table_name WHERE condition;
  1. 过滤和排序:
  • 过滤查询结果:SELECT * FROM table_name WHERE condition;
  • 排序查询结果:SELECT * FROM table_name ORDER BY column ASC/DESC;
  1. 聚合函数和分组:
  • 聚合函数:SUM(), AVG(), COUNT(), MAX(), MIN()
  • 分组查询:SELECT column1, SUM(column2) FROM table_name GROUP BY column1;
  1. 索引和查询优化:
  • 创建索引:CREATE INDEX index_name ON table_name (column);
  • 查询优化:使用合适的索引、避免使用SELECT *、使用EXPLAIN分析查询等方式。
  1. 外键约束:
  • 创建外键约束:ALTER TABLE table_name ADD CONSTRAINT fk_column FOREIGN KEY (column) REFERENCES other_table(column);