菜鸟前端面试记录U·ェ·U
介绍自己
吃苦耐劳,态度认真,有较强的抗压能力,较强逻辑思维能力,较好分析问题能力、学习能力。
性格开朗,善于沟通,具有团队合作精神,学习能力较强,热爱技术,有钻研精神,具备较强的抗压能力。
实习描述
同程
负责Martix项目管理平台和轻任务协作数据管理平台完成前端业务的开发,Martix的项目进行了第二版本的性能优化,首页性能提高到1170ms以内,对轻任务进行个人待办事项和团队待办事项的开发。
中科迅联
负责pc端业务和微信端H5页面的开发,对不同的移动端进行适配,渲染表单元素进行开发。
Martix项目管理平台
技术栈:qiankun+React+Vue2+Nextjs+Vue-Router+Vuex+Nodejs+Axios+Echarts+Element
描述:Martix项目管理平台
- 使用qiankun微前端架构,使用 React 开发的主应用负责动态加载和管理多个独立开发的微应用;
- 封装富文本编辑器组件,实现图片自动上传、代码块编辑、历史版本管理和恢复以及内容缓存管理等功能。组件支持内容粘贴处理、自动识别和转换图片,确保编辑过程的流畅性和稳定性,优化用户界面的互动体验。
- 开发需求管理组件和实现需求评审流程配置功能,运用Vue.js、Vuex等技术实现复杂表单的动态生成和校验,处理多层嵌套组件的通信和状态管理,优化异步数据的加载和处理逻辑,实现了需求数据的动态加载和实时更新;
- 开发文件批量上传与管理组件,支持文件多种操作(如上传、预览、关联文档等),通过客户端验证减少服务器压力,提高项目文档管理的效率和用户体验;
- 开发甘特图视图组件,采用虚拟滚动和延迟加载技术解决大规模数据渲染带来的性能挑战,开发自定义列配置组件,利用localStorage实现了用户配置的跨会话持久化,实现的视图切换和动态数据过滤功能,包括精确的时间线对齐、动态数据绑定、以及用户交互的拖拽和缩放功能,提高甘特图的查看和操作效率。
微前端
使用registerMicroApps注册微前端,使用nextjs的useRouter路由和useEffect监听路由,
并通过全局状态管理与子应用共享路由状态,使得子应用能够响应路由变化。
qiankun生命周期,在子应用中使用
bootstrap、mount、unmount、update
当使用 Vue 2 构建 Qiankun 的子应用时,你需要在子应用中定义和导出相应的生命周期钩子,这些钩子会与 Qiankun 主应用进行交互以实现模块的正确加载和卸载。这些生命周期钩子包括:bootstrap
、mount
、unmount
,以及可选的 update
(如果你需要处理主应用传递的 props)。下面是如何在 Vue 2 子应用中实现这些生命周期钩子的一个示例:
bootstrap:
- 这个阶段主要用于全局级别的初始化工作,如设置日志、初始化服务等,通常只运行一次。
mount:
- 这个阶段用于将 Vue 实例挂载到 DOM 上,通常在这里初始化和渲染 Vue 组件。
unmount:
- 这个阶段用于清理、卸载 Vue 实例,避免内存泄露,确保所有事件监听器和依赖都被适当移除。
update:
- 这个阶段用于处理新的 props 或状态更新,是可选的,依赖于你的应用是否需要响应外部变化。
示例代码
1 | import Vue from 'vue'; |
实现注意事项
- 确保在
mount
函数中正确地挂载 Vue 实例到由 Qiankun 提供的 DOM 容器中。通常这是通过props.container.querySelector('#app')
实现的,其中#app
是容器内的一个 DOM 元素的 ID。 - 在
unmount
函数中,彻底销毁 Vue 实例和清理 DOM 是至关重要的,以防止内存泄漏或其他副作用。 - 如果实现了
update
函数,确保你的应用可以响应外部状态的变化,例如,接收来自主应用的数据更新。
通过这样的设置,Vue 2 子应用可以被 Qiankun 框架有效地管理,实现真正意义上的微前端架构,其中各个子应用可以独立开发、部署和更新,而不会影响到主应用或其他子应用的运行。
路由鉴权
您提供的 JavaScript 函数 hasPermission
是用于在基于路由的系统中检查用户是否拥有访问某个特定路由的权限。这个函数通过分析路由对象和用户的权限列表来决定是否允许访问某个路由。下面我将详细解释这个函数的逻辑和作用:
函数参数
to
: 这个参数表示即将访问的路由对象。通常,这个对象包含了路由的元数据(如名称、查询参数等)。hasPermissionList
: 这是一个数组,包含了用户拥有的权限名称。
函数逻辑
检查元数据是否存在:
- 函数首先检查
to.meta
是否存在,to.meta
通常包含与路由相关的元数据,比如权限相关信息。
- 函数首先检查
特定路由的权限检查:
- 函数中有特定的逻辑来处理名为
workload
和bugreport
的路由。对于这些路由,它进一步检查查询参数to.query.type
,来决定用户是否有权限访问不同类型的统计信息。 - 对于
workload
路由:- 如果
type
是dept
(部门),则检查用户权限列表中是否包含'部门工作量统计'
。 - 如果
type
是member
(员工),则检查是否包含'员工工作量统计'
。 - 如果
type
是project
(项目集),则检查是否包含'项目集工作量统计'
。
- 如果
- 对于
bugreport
路由:- 类似地,基于
type
(部门、员工或项目集),检查相应的'部门缺陷量统计'
、'员工缺陷量统计'
或'项目集缺陷量统计'
。
- 类似地,基于
- 函数中有特定的逻辑来处理名为
通用权限检查:
- 如果路由名称不是
workload
或bugreport
,则检查hasPermissionList
是否包含to.meta.title
。如果不包含,则表示没有权限。
- 如果路由名称不是
默认行为:
- 如果没有
to.meta
,函数默认允许访问,即返回true
。
- 如果没有
示例使用场景
假设这个函数被用在一个 Web 应用的路由守卫中,每次路由跳转之前都会调用这个函数来确定用户是否有权限访问目标路由。这种权限检查是维护应用安全性的重要方式,确保用户只能访问他们被授权的部分。
代码改进建议
虽然这个函数能够正常工作,但它的逻辑有些重复,可以通过一些重构来优化:
- 提取重复逻辑:对于检查权限的逻辑,可以考虑抽象出一个辅助函数,简化代码。
- 使用映射:将权限名称和路由查询类型映射起来,减少重复的
if-else
语句。 - 错误处理:在实际应用中,可能需要更复杂的错误处理逻辑,比如当路由对象缺失必要信息时提供默认行为或记录错误。
这个函数的存在显示了如何在客户端进行基于路由的访问控制,这是构建安全且可维护的现代 Web 应用的一个关键方面。
轻任务协作数据管理平台
描述:
技术栈:React+Redux+Nextjs+Typescript+Webpack+Antd+Axios+WebSocket+Sentry
描述:实时协作数据平台,允许用户创建、管理和共享数据表、表单和仪表板。项目采用了模块化的设计和Websocket通信,提供强大的实时更新和协作能力,支持多种视图类型,包含个人待办事项、团队待办事项和模板库,通过引入自定义待办事项和丰富的模板库,提升用户在项目管理、数据分析和团队协作方面的效率。
登录系统集成无痕验证码技术,通过服务器端自动执行,减少了传统验证码对用户体验的干扰。该系统全面涵盖了用户数据处理、前端校验、无痕验证,以及服务器端的认证与状态管理,确保用户操作的便捷性和系统的安全性;
实现动态的页面导航,通过postMessage实现了iframe中的应用与主页面的安全通信,根据不同的应用场景,如分享视图、嵌入视图、个人路径等,实现了条件性导航逻辑,在不同上下文中都能够正确地引导用户到期望的视图;
通过Sentry用于错误监控和性能优化,提高错误追踪的准确性和应用性能监控的效率,设计自定义hooks用于封装复杂的业务逻辑和API请求,包括节点的增删改查、个人待办事项的管理、共享和收藏节点操作等;
基于Websocket实现实时双向通信,在不刷新页面情况下同步和更新用户数据,增强用户交互体验;性能优化与后台处理:引入 Web Workers 处理复杂的后台数据计算任务,解放了主 UI 线程,避免了复杂运算对用户界面响应的影响。通过有效管理线程,提高了页面渲染效率和应用响应速度。
负责甘特图视图内任务内容的动态渲染组件开发,使用延迟加载和按需渲染的策略,减少了页面的初始加载时间和运行时的性能开销,采用 React 和 Konva 库实现客户端绘制,通过 Next.js 动态导入技术优化加载速度;
负责轻任务应用的主导航栏组件,支持在不同设备和屏幕尺寸上的操作体验,处理了用户登录、空间选择、通知计数等多个状态的同步和更新,根据用户权限动态调整导航栏展示的功能模块,对于非管理员用户隐藏空间管理入口,对于删除空间的用户限制访问等,实现了细粒度的功能控制。
开发个人待办事项管理组件,通过Hooks和高阶组件等技术实现功能模块的解耦和复用,利用自定义hooks处理数据请求和副作用,在处理共享目录的逻辑时,通过Redux和封装自定义hooks,实现了状态的更新和数据的同步;
04-09北京海致科技集团
遍历数据方法?
for循环
1 | for(let i = 0; i < 5; i++) { |
for of:ES6中的方法,不会遍历数组的私有属性,不能遍历对象,可以遍历字符串,数组,有返回值
1 | let arr=[1,2,3,4,5] |
for in:数组的私有属性也会遍历,所以一般用来遍历对象
1 | let arr=[1,2,3,4,5]; |
map:数据唯一
foreach:forEach() 方法对数组的每个元素执行一次给定的函数。
1 | let arr=[1,2,3,4,5]; |
遍历对象的方法和遍历数组的方法分别说五个
在 JavaScript 中遍历对象并访问其属性的方法有多种,每种方法都适用于不同的情况和需求。以下是一些常见的方法:
1. for...in
循环
这是遍历对象属性最传统的方式。for...in
循环会遍历对象自身的所有可枚举属性以及它继承的可枚举属性。
1 | const obj = { a: 1, b: 2, c: 3 }; |
注意:使用 for...in
时,通常需要使用 hasOwnProperty
方法来过滤掉从原型链继承的属性:
1 | for (let key in obj) { |
2. Object.keys()
方法
Object.keys()
返回一个数组,包含对象自身的所有可枚举属性名称。配合 forEach
循环使用可以遍历对象属性:
1 | Object.keys(obj).forEach(function(key) { |
3. Object.values()
方法
这个方法返回一个数组,包含对象自身的所有可枚举属性值:
1 | Object.values(obj).forEach(function(value) { |
4. Object.entries()
方法
Object.entries()
返回一个数组,其元素是与对象自身可枚举属性键值对对应的数组:
1 | Object.entries(obj).forEach(([key, value]) => { |
5. Object.getOwnPropertyNames()
方法
这个方法返回一个数组,包含对象自身的所有属性(无论是否可枚举)的名称:
1 | Object.getOwnPropertyNames(obj).forEach(function(key) { |
6. 使用 for...of
循环(配合 Object.entries()
)
如果你想使用 for...of
循环遍历对象属性,可以结合使用 Object.entries()
:
1 | for (let [key, value] of Object.entries(obj)) { |
7. Reflect.ownKeys()
方法
Reflect.ownKeys(obj)
返回一个数组,包含对象自身的所有键(包括不可枚举属性及 Symbol 类型的键):
1 | Reflect.ownKeys(obj).forEach(function(key) { |
使用场景选择
- **
for...in
**:适用于需要遍历继承的属性的情况,但通常需要配合hasOwnProperty
。 - **
Object.keys()
、Object.values()
、Object.entries()
**:适用于现代 JavaScript 应用,可以更方便地遍历对象的可枚举属性。 - **
Object.getOwnPropertyNames()
和Reflect.ownKeys()
**:适用于需要访问对象所有键(包括不可枚举的)的高级用途。
这些方法可以灵活选择和使用,以满足不同的遍历需求和特定的编程环境。
遍历数组的方法有:
- for 循环
- forEach() 方法
- map() 方法
- filter() 方法
- reduce() 方法
- some() 方法
- every() 方法
- for…of 循环
04-11-滴滴日常实习
事件循环
qiankun机制是怎样的
qiankun
是一个实现了微前端架构的JavaScript库,它允许开发者将一个大型的前端应用分解为多个可以独立开发、部署、运行的小型应用(微应用)。qiankun
基于single-spa
,提供了一套比较完善的微前端解决方案,其工作机制主要可以从以下几个方面理解:
初始化和注册
主应用启动:在主应用中,
qiankun
提供registerMicroApps
方法用于注册微应用。在注册时,你需要为每个微应用指定一个唯一的名称、入口(可以是URL或者一段HTML标记)、容器(用于挂载微应用的DOM元素)以及激活条件(当URL符合某个规则时激活对应的微应用)。微应用加载:当激活条件满足时,
qiankun
会动态加载微应用的静态资源(JavaScript、CSS等)。这一过程通常是通过fetch
API完成的。
沙箱机制
JavaScript沙箱:为了防止微应用间的全局变量污染,
qiankun
为每个微应用创建了一个JavaScript运行时沙箱。这个沙箱通过代理全局对象(如window
)来隔离全局变量,确保微应用间的隔离性。样式隔离:
qiankun
使用Shadow DOM或者其他策略来实现CSS隔离,防止微应用间的样式冲突。
生命周期管理
qiankun
为微应用定义了一系列生命周期钩子,例如bootstrap
、mount
、unmount
等。主应用通过这些钩子可以管理微应用的加载、挂载、更新和卸载。微应用需要暴露这些生命周期方法,以便
qiankun
在适当的时刻调用它们。
通信
qiankun
提供了主应用和微应用之间的通信机制。通常,这是通过在注册微应用时传递props
来实现的。props
可以包含需要共享的状态、函数等。
性能优化
预加载:
qiankun
支持配置预加载微应用的静态资源,以缩短微应用首次启动的时间。资源复用:
qiankun
支持主应用和微应用之间的依赖共享,减少了重复加载相同依赖库的需要。
总结
qiankun
的机制提供了一种有效的方式来构建大型的、由多个团队独立开发和部署的前端应用。它通过动态加载、沙箱隔离、生命周期管理和通信机制,实现了技术栈无关、运行时独立、应用间隔离的微前端架构。这使得大型项目可以更加模块化、易于管理,同时还能保证不同团队的开发效率和应用性能。
nextjs做服务端渲染
Next.js 是一个基于 React 的框架,专门用于构建服务端渲染(Server-Side Rendering, SSR)和静态网站生成(Static Site Generation, SSG)的应用。Next.js 通过预渲染页面来提高性能和搜索引擎优化(SEO),预渲染可以在服务端生成HTML,也可以在构建时生成,然后发送到客户端。
服务端渲染(SSR)的工作原理
在传统的React应用中,大部分渲染工作都在客户端进行。而在Next.js中,服务端渲染意味着首次页面加载时的HTML是在服务器上生成的,这个过程包括:
- 请求处理:用户请求一个页面时,该请求被发送到服务器。
- 页面渲染:服务器运行React代码来生成页面的HTML内容,同时也可以在这个阶段获取和包含必要的数据(例如,通过API调用获取)。
- 发送响应:服务器将生成的HTML作为响应发送给客户端。
- 客户端处理:客户端浏览器接收到HTML并呈现给用户。然后,React代码在浏览器端“接管”页面,使其成为一个完全交互式的单页应用。
如何在Next.js中使用SSR
Next.js 通过文件系统基路由和数据获取方法如getServerSideProps
自动处理SSR。
页面组件
每一个位于pages
目录下的.js
、.jsx
、.ts
或.tsx
文件都会自动成为路由,并且默认是通过服务端渲染的。
数据获取
对于需要服务端获取数据的页面,你可以使用getServerSideProps
函数来获取数据:
1 | // pages/posts.js |
getServerSideProps
在请求时运行,允许你在渲染页面前从服务器获取数据。这个函数只在服务端运行,绝不会在客户端运行,因此可以直接写服务器代码,如直接访问数据库等。
优势
- SEO友好:由于页面的HTML是预先在服务器上生成的,搜索引擎可以更容易地抓取和索引内容。
- 性能提升:首次加载时直接提供包含所有必要数据的HTML,可以减少首屏渲染时间。
- 灵活的数据获取策略:Next.js不仅支持SSR,还支持SSG(静态网站生成),以及客户端数据获取,开发者可以根据页面的需求选择最适合的策略。
Next.js通过这些机制,极大地简化了使用React进行服务端渲染应用的开发流程,同时也提供了灵活、高效的数据处理和页面渲染方案。
看题输出–this
1 | let obj = { |
在JavaScript中,this
关键字的值取决于函数是如何被调用的。在对象obj
中定义的方法表现出不同的this
行为:
- a - 是一个正常的函数,当被调用时,
this
指向obj
对象。 - b - 是ES6的简写方法,当被调用时,
this
同样指向obj
对象。 - c - 是一个箭头函数,它没有自己的
this
,this
值是从创建它的上下文(即定义它的上下文)继承的,在全局代码中,this
指向全局对象。 - d - 是一个正常的函数,它返回一个箭头函数。箭头函数内部的
this
由外围最近一层非箭头函数决定,在这里是d
函数,因此箭头函数中的this
指向obj
对象。 - e - 是一个正常的函数,它调用
obj
对象的b
方法,this
指向obj
对象。 - f - 是一个正常的函数,它返回
obj
对象的b
方法,但不执行它。当f
被调用,它返回对b
的引用,此时this
尚未被绑定。
现在,根据以上规则,可以确定console.log
语句的输出:
1 | console.log(obj.a()) // '222222' - 'this'指向obj |
对于obj.c()
,由于箭头函数不绑定this
,它将会捕获定义时的this
值。如果obj
是在全局上下文中定义的,this.dev
可能会返回undefined
(严格模式下)或者全局对象的dev
属性(非严格模式下)。
对于obj.f()
,它返回b
函数的引用,但在执行时它已经失去了原始的obj
上下文,因此在严格模式下this
将会是undefined
,在非严格模式下通常是全局对象。然而,如果obj.f()
在obj
的上下文中直接被调用,this
会正确地指向obj
,因为它继承了f()
调用时的上下文。
请注意,如果obj
对象是在某个函数作用域或模块作用域中定义的,而且该作用域是严格模式(例如ES6模块默认是严格模式),那么this
的行为可能会与非严格模式不同。在Node.js或模块化JavaScript环境中运行时,全局上下文中的this
是undefined
。
宏任务和微任务
在JavaScript中,执行顺序涉及到宏任务和微任务的概念。setTimeout
被放入宏任务队列,而Promise的.then
方法产生的回调被放入微任务队列。微任务队列在每次宏任务执行完毕后执行,且在下一个宏任务开始前,微任务队列会被完全清空。这里是这段代码执行的顺序解释:
console.log('1')
直接执行,打印1
。setTimeout(..., 0)
排入宏任务队列,等待执行。Promise.resolve().then(...)
的回调函数排入微任务队列。- 新的
Promise
立即执行console.log('4')
,打印4
。 - 然后立即执行
console.log('5')
,打印5
。 - 该Promise的
.then(...)
回调函数被排入微任务队列。 - 第二个带
setTimeout
的Promise构造函数执行,但其内部的setTimeout
是一个宏任务,排入宏任务队列,等待执行。 - 当前宏任务(脚本的主体)执行完毕,开始执行微任务队列:首先执行
Promise.resolve().then(...)
的回调,打印3
,然后执行第一个Promise的.then(...)
回调,打印6
。 - 微任务队列清空,开始下一个宏任务,首先是第一个
setTimeout
的回调,打印2
。 - 然后执行第二个
Promise
中的setTimeout
,等待100ms后,它的.then(...)
回调被排入微任务队列。 - 等到该宏任务完成后,执行微任务队列中的回调,打印
7
。
综上,打印顺序是:1
, 4
, 5
, 3
, 6
, 2
, 7
。
1 | console.log('1') |
Promise的常用方法有哪些?
Promise.all(iterable)
- 接受一个 Promise 对象的集合(数组、迭代器等)作为输入。
- 当所有的 Promise 都成功解决(resolved)时,返回一个新的 Promise,该 Promise 成功解决,并且其结果是所有输入 Promise 的结果数组。
- 如果任何一个 Promise 被拒绝(rejected),
Promise.all
返回的 Promise 会立即被拒绝,其拒绝原因是第一个拒绝的 Promise 的原因。
Promise.allSettled(iterable)
- 接受一个 Promise 对象的集合作为输入。
- 无论输入的 Promise 对象是成功还是失败,都会等待它们全部完成。
- 返回一个新的 Promise,该 Promise 解决后的结果是一个数组,数组中每个元素表示对应的 Promise 的结果,包括状态(
fulfilled
或rejected
)和值(或拒绝原因)。
Promise.race(iterable)
- 接受一个 Promise 对象的集合作为输入。
- 返回一个新的 Promise,它将与输入的 Promise 中第一个解决或拒绝的 Promise 具有相同的解决值或拒绝原因。
Promise.resolve(valve)
- 返回一个以给定值解决的 Promise。
- 如果输入是一个 Promise,那么
Promise.resolve
将返回这个 Promise。 - 如果输入带有一个
then
方法(即“thenable”对象),返回的 Promise 会“跟随”这个 thenable 的行为,即采用其最终状态。
Promise.reject(reason)
返回一个以给定原因拒绝的 Promise。
Promise.prototype.then(onFulfilled,onRejected)
- 在 Promise 上添加解决(fulfilled)和拒绝(rejected)的回调函数。
- 返回一个新的 Promise,以链式处理异步操作。
Promise.prototype.catch(onRejected)
- 添加一个拒绝(rejected)回调函数,是
.then(null, onRejected)
的语法糖。 - 返回一个新的 Promise。
Promise.prototype.finally(onFinally)
- 添加一个回调函数,无论 Promise 是解决还是拒绝,该回调函数都会被执行。
- 返回一个新的 Promise,在原始 Promise 解决或拒绝之后解决。
这些方法提供了丰富的接口来处理异步操作,使得异步代码更加可读和易于管理。
Commonjs和ES Module
Commonjs 和 Es Module 有什么区别 ?
CJS:
- 环境:最初被设计用于 Node.js。
- 导入模块:使用
require()
函数导入模块。 - 导出模块:使用
module.exports
和exports
对象导出模块。 - 加载方式:同步加载模块,即在代码执行到
require()
语句时立即同步地加载和执行模块。 - 用例:主要用于服务器端和桌面应用程序。
ESM:
- 环境:作为 ECMAScript(JavaScript的规范)的一部分,被设计用于浏览器和现代 JavaScript 环境。
- 导入模块:使用
import
语句导入模块。 - 导出模块:使用
export
关键字导出模块。 - 加载方式:支持异步加载模块,允许进行代码分割和懒加载。
- 用例:主要用于浏览器应用程序,但现在在 Node.js 和其他 JavaScript 环境中也得到了支持。
区别:
- 语法:CommonJS 使用
require
和module.exports
,而 ES Modules 使用import
和export
。 - 加载机制:CommonJS 模块是同步加载的,主要适用于服务器端,而 ES Modules 可以异步加载,更适合浏览器环境。
- 模块解析:在 CommonJS 中,
require
的路径可以是动态的,支持表达式,而 ES Modules 中的import
路径必须是静态的,不能使用表达式。 - 模块值的处理:CommonJS 导入的模块值是一个模块对象的拷贝,而 ES Modules 导入的是一个模块接口的“活”绑定。
联系:
- 模块化目标:两者都旨在提供一种封装代码的方式,将代码分割成可复用的单元,并控制它们的作用域和公开接口。
- 兼容性考虑:在现代 JavaScript 环境中(特别是在 Node.js 中),有努力使 CommonJS 和 ES Modules 能够相互操作。例如,Node.js 通过特定的导入语法支持在使用 CommonJS 规范的代码中导入 ES Module。
随着 JavaScript 生态的发展,ES Modules 逐渐成为主流的模块化标准,但在很多现有项目和库中,CommonJS 仍然广泛使用。在新项目中选择哪种标准,通常取决于项目的运行环境、目标平台和开发者的偏好。
Commonjs 如何解决的循环引用问题
既然有了 exports,为何又出了 module.exports ? 既生瑜,何生亮 ?
require 模块查找机制 ?
Es Module 如何解决循环引用问题 ?
exports = {} 这种写法为何无效 ?
关于 import() 的动态引入 ?
Es Module 如何改变模块下的私有变量 ?
04-11趣链科技
介绍实习项目
介绍微前端
qiankun
是一个基于 single-spa
的微前端实现库,它通过一系列 API 简化了微前端的实现,允许开发者将不同的前端应用组合成一个完整的应用。以下是 qiankun
的一些底层实现细节以及主应用和子应用通信的方式。
qiankun
底层实现
JavaScript 沙箱:
qiankun
通过 JavaScript 沙箱技术隔离主应用和子应用的运行环境。沙箱主要通过代理全局变量来实现,每个微应用都运行在自己的全局上下文中。
样式隔离:
- 为了防止 CSS 污染,
qiankun
对子应用的样式进行隔离。它可以使用 Shadow DOM 或者为子应用的样式增加特定的前缀来实现。
- 为了防止 CSS 污染,
生命周期钩子:
qiankun
实现了一套生命周期钩子,允许在加载、挂载、更新和卸载微应用时进行相应的处理。
资源加载:
qiankun
使用fetch
API 动态加载微应用的资源文件,包括 HTML、JavaScript 和 CSS。
路由代理:
qiankun
对浏览器历史 API 进行了劫持,以支持微应用的路由系统,确保路由变化能够被正确处理。
主应用和子应用通信方式
Props 传递:
- 类似于 React 或 Vue 组件的 props,
qiankun
允许主应用在加载微应用时传递 props。这些 props 可以是简单的数据,也可以是可以调用的函数,用于主子应用间的直接通信。
- 类似于 React 或 Vue 组件的 props,
全局变量:
- 可以通过全局变量在主应用和子应用之间通信。例如,主应用可以将一个全局变量或者函数挂载到
window
对象上,子应用可以访问和使用这些变量或函数。
- 可以通过全局变量在主应用和子应用之间通信。例如,主应用可以将一个全局变量或者函数挂载到
自定义事件(Custom Events):
- 主应用和子应用可以通过触发和监听自定义事件来实现通信。这种方式不直接依赖于
qiankun
的 API。
- 主应用和子应用可以通过触发和监听自定义事件来实现通信。这种方式不直接依赖于
状态管理库:
- 如果主应用和子应用都使用相同的状态管理库(如 Redux、Vuex),可以通过共享一个状态存储来进行通信。
消息通道(Message Channel):
- 可以使用
window.postMessage
和监听window
上的message
事件来在主应用和子应用之间进行跨域通信。
- 可以使用
Observable 对象:
- 使用诸如 RxJS 这类响应式编程库中的 Observable 对象,可以在应用之间创建一个可观察的数据流。
这些通信方式可以根据应用的实际需要和开发习惯进行选择和使用。在微前端架构中,选择合适的通信机制对于确保应用的独立性和协调一致性非常重要。
Qiankun 是一个基于 single-spa 的微前端框架,提供了更简单的 API 和配置方式,让开发者能够构建大型的、由多个前端应用组合而成的应用系统。在 Qiankun 中,每个子应用都有自己的生命周期,这些生命周期钩子允许主应用在特定时刻介入子应用的状态,实现控制和资源管理。
Qiankun 子应用的生命周期
以下是 Qiankun 子应用的主要生命周期钩子及其用途:
bootstrap:
- 时机:子应用初次加载时调用。
- 用途:用于启动子应用,通常包括初始化资源、依赖项或其它只需执行一次的设置。
mount:
- 时机:每次子应用需要被激活(即展示到视图中)时调用。
- 用途:用于挂载子应用的实例到 DOM,处理状态恢复和事件监听器的添加等。
unmount:
- 时机:每次子应用需要被卸载(从视图中移除)时调用。
- 用途:用于卸载子应用,清除相关资源和事件监听器,保证内存释放,避免内存泄漏。
update(可选):
- 时机:主应用或自身传入新的 props 时调用。
- 用途:响应 props 变化,执行相关更新逻辑。
生命周期的实现
在子应用中,你通常需要导出这些生命周期钩子的实现。这可以通过单独的函数或对象方法实现。例如,如果你的子应用是用 React 构建的,你的生命周期实现可能如下:
1 | export async function bootstrap() { |
使用场景
这些生命周期方法对于管理应用状态、资源分配和优化性能至关重要。例如,通过 unmount
方法清理事件监听器和外部插件,可以避免内存泄漏。通过 mount
和 unmount
方法,可以确保子应用只在必要时占用浏览器资源,从而优化整体的应用性能。
了解和合理利用这些生命周期钩子,是使用 Qiankun 微前端框架有效管理多个前端应用的关键。这有助于确保应用的稳定性、响应性和高效性。
CSS的动画了解吗:比如transition
事件循环机制:宏任务和微任务
浏览器的存储,cookie,localstorage,sessionstorage
说一下Websocket
WebSocket 是一种在单个TCP连接上进行全双工通信的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。他不是HTTP协议,
节流和防抖的使用场景,闭包
强缓存和协商缓存:cache-control+Expires ,协商缓存:E-Tag,Last-Modified
Promise和一些静态方法(promise.all,promise,race等)
Promise
是JavaScript中用于异步编程的一个重要概念。它代表了一个最终可能完成或失败的操作及其结果值。一个Promise
对象有三种状态:
pending
(等待状态):初始状态,既不是成功,也不是失败状态。fulfilled
(成功状态):操作完成,且成功返回值。rejected
(失败状态):操作完成,但失败,返回拒绝的原因。
Promise
提供了一种更加优雅的处理异步操作的方法,相比于传统的回调函数,它可以减少代码的嵌套,并且更易于理解和维护。
Promise
的静态方法
- **Promise.all(iterable)**:当你需要并行执行多个异步操作,并且只有当所有异步操作成功时才继续执行,可以使用
Promise.all
。 - **Promise.allSettled(iterable)**:如果你需要知道一系列异步操作的每一个的结果,不管是成功还是失败,可以使用
Promise.allSettled
。 - **Promise.race(iterable)**:当你有多个异步操作,并且你只关心哪个操作最先完成(或失败),可以使用
Promise.race
。 - **Promise.resolve(value)和Promise.reject(reason)**:这两个方法可以用来创建已经处于完成或拒绝状态的
Promise
,用于测试或者与其他Promise
操作结合使用。
Vue2的生命周期
创建前后,挂载前后,更新前后,销毁前后
异步请求的阶段:created,mounted
组件通信的方式
- props,$emit;
- provide,inject;
- Event Bus
- vuex
- Vue3 Composition API(Vue3):setup
- Refs 和 $parent / $children:
路由鉴权
路由守卫routerConfig.beforeEach的meta.access的值决定是否允许访问某个路由
微前端生命周期钩子
- bootstrap:子应用的启动
- mount:挂载
- onmount:卸载
在微前端架构下,子应用的静态资源路径可能需要动态设置以确保资源加载正确。代码中通过修改__webpack_public_path__
来实现这一点。
04-12网易云
vue2和vue3区别
url分割
dom节点事件
DOM 事件流描述了从页面中接收事件的顺序,它是一个将事件从窗口发送到具体节点,然后再回传回去的过程。DOM 事件流主要分为三个阶段:捕获、目标、冒泡
1. 捕获阶段(Capturing Phase)
- 事件从
window
对象传导到事件目标的父节点的过程。 - 在捕获阶段,事件会从文档的根节点开始,经过祖先节点,向下传递到目标节点(触发事件的那个最深层的节点)。
- 目的是更早地在文档层次中捕捉事件,但是默认情况下,大多数事件处理程序在此阶段不会处理事件。
2. 目标阶段(Target Phase)
- 当事件到达目标元素时。在这个阶段,事件不会进一步传播,而是在目标上触发指定的监听器。
- 这是事件被实际处理的阶段,可以在这个阶段对事件进行操作或响应。
3. 冒泡阶段(Bubbling Phase)
- 事件从目标元素向上冒泡到文档的根节点。
- 在冒泡阶段,事件会从目标元素开始,逐级向上传递给祖先节点,一直传到文档的根。
- 大部分事件处理发生在此阶段,因为它允许一个单一的父级处理器来监听一个元素和它的所有子元素上发生的事件。
使用事件流的优点
利用 DOM 事件流的特性,可以实现更加灵活的事件处理机制,例如事件委托。事件委托是一种利用事件冒泡原理来减少事件处理器数量的技巧。通过在父节点上监听事件,然后根据事件的目标(event.target
)来判断如何响应,可以有效地处理动态元素或大量相似元素的事件,这样做既可以减少内存占用,也可以提高程序性能。
设置事件监听的阶段
在使用 addEventListener
方法为元素添加事件监听器时,可以指定是在捕获阶段还是冒泡阶段触发处理器。这是通过该方法的第三个参数实现的,如果是 true
,则在捕获阶段触发;如果是 false
或不设置,则在冒泡阶段触发。
理解 DOM 事件流的工作机制,对于开发复杂的交互式网页应用是非常重要的,它有助于开发者编写出更高效、更可控的事件处理代码。
Vue 2 和 Vue 3 在设计和功能上有许多重要的区别,这些区别旨在提高性能、增加灵活性,以及改进开发体验。下面是 Vue 2 和 Vue 3 之间的一些主要区别:
1. 性能提升
Vue 3 引入了许多性能优化,包括更快的虚拟 DOM 算法、优化的组件初始化过程、以及基于代理的观察者机制。这些改进使得 Vue 3 在渲染速度、内存占用以及启动时间上相较于 Vue 2 有显著提升。
2. Composition API
Vue 3 引入了一个全新的 Composition API,它提供了一种更灵活的方式来组织和重用逻辑。与 Vue 2 的 Options API 相比,Composition API 使得在相同组件中处理多个功能点变得更加清晰,并且更容易将逻辑提取到可复用的函数中。
3. 更好的 TypeScript 支持
Vue 3 从一开始就考虑了对 TypeScript 的支持,使得在使用 TypeScript 开发 Vue 应用时的体验更加流畅。相比之下,Vue 2 的 TypeScript 支持是后来通过社区努力逐步改进的。
4. Fragment、Teleport 和 Suspense
- Fragment: Vue 3 允许组件有多个根节点,解决了 Vue 2 中组件必须有单一根节点的限制。
- Teleport: Vue 3 引入的 Teleport 特性允许将组件的子元素渲染到 DOM 树的其他位置,这对于如模态框或通知之类的 UI 元素非常有用。
- Suspense: Vue 3 新增了 Suspense 组件,为异步组件的加载提供了内置的等待机制,使得处理异步组件变得更加方便。
5. 响应式系统的重写
Vue 3 的响应式系统从使用 Object.defineProperty 重写为基于 ES6 的 Proxy,这提供了更好的性能和更灵活的响应式能力,同时解决了 Vue 2 中的一些响应式系统限制,如检测属性的添加和删除、数组索引和长度的变化等。
6. 更轻量和模块化
Vue 3 的设计更加模块化,允许在打包过程中自动摇树优化(Tree-shaking),这意味着最终的应用只包含真正使用到的功能代码,从而减小了应用大小。
7. 新的组件挂载方式和全局 API 更改
Vue 3 更改了组件挂载到 DOM 的方式,以及全局 API(如 Vue.use
、Vue.mixin
等)的使用方式,使得它们更适合模块化的项目结构。
尽管 Vue 3 带来了很多新特性和改进,Vue 2 仍然是一个非常稳定和功能丰富的框架,被广泛用于生产环境中。迁移到 Vue 3 是一个值得考虑的步骤,尤其是对于新项目或正在考虑重构的项目。
代码输出:
1 | for(let i=1;i<=3;i++){ |
这段JavaScript代码定义了一个名为Fn
的构造函数,它没有参数和执行体。接着,在Fn
的原型(Fn.prototype
)上定义了一个名为add
的方法和一个名为count
的属性。
1 | function Fn() {}//`function Fn() {}`:这行代码定义了一个空的构造函数`Fn`。 |
Fn.prototype.count = 0;
:这行代码在Fn
的原型对象上设置了一个属性count
,初始值为0。所有通过Fn
构造函数创建的实例都会共享这个原型上的count
属性。但是,当使用add
方法时,由于属性提升(即实例上没有count
属性,所以会在实例上创建一个新的count
属性,并且将其值设置为原型上count
的当前值加1),每个实例都会有自己独立的count
属性。
然后代码创建了两个Fn
的实例,并且分别调用了它们的add
方法:
let fn1 = new Fn();
:通过new
关键字和Fn
构造函数创建了一个新的实例fn1
。fn1.add();
:调用fn1
实例的add
方法,这会使fn1
实例上的count
属性值从0变为1,并打印出this.count: 1
。let fn2 = new Fn();
:同样地,创建了另一个名为fn2
的Fn
实例。fn2.add();
:调用fn2
实例的add
方法,和fn1
一样,这也会使fn2
实例上的count
属性值从0变为1,并打印出this.count: 1
。
值得注意的是,尽管count
属性最初是定义在Fn
的原型上的,当调用add
方法时,由于this.count++
这行代码的作用,实际上是在各自的实例上创建了一个新的count
属性,并且这个属性不再和原型上的count
属性共享。所以,即使是Fn.prototype.count
的值没有变化,每个实例上的count
属性都是独立的,它们的值互不影响。
Vite 和 webpack 区别
Vite 和 Webpack 是前端开发中常用的两个现代 JavaScript 构建工具,它们在模块打包和开发服务器方面有着根本的不同。了解这两个工具的区别对于选择适合项目的构建工具非常重要。
Vite
- 开发模式: Vite 在开发模式下使用原生 ES 模块导入(ESM),这允许浏览器直接加载模块,而无需打包。这种方法可以显著提高冷启动时间和模块热更新(HMR)的速度。
- 生产模式: 在构建生产版本时,Vite 使用 Rollup 进行打包。Rollup 通常被认为在打包库和轻量级应用时比 Webpack 更高效。
- 优点: Vite 提供了极快的启动时间和即时模块热替换(HMR),因为它不需要预打包。它还利用现代浏览器支持的 ES 模块导入,减少了不必要的代码转换。
- 使用场景: Vite 特别适合 Vue 和 React 项目,但也可以配置用于其他框架。它的设计理念使其特别适合需要快速开发和迭代的现代网页应用(SPA)。
Webpack
- 开发模式: Webpack 在开发和生产模式下都依赖于将项目的所有依赖打包成一个(或多个)bundle的概念。在开发模式下,它提供了模块热替换(HMR)但通常启动和编译速度比 Vite 慢。
- 生产模式: Webpack 使用复杂的算法来优化输出的大小和加载时间,包括代码拆分、树摇(Tree-shaking)、懒加载等。
- 优点: Webpack 提供了高度灵活和可配置的打包策略,支持广泛的插件生态系统,适合于需要复杂打包逻辑的大型应用。
- 使用场景: Webpack 适用于各种项目,从简单的静态网站到复杂的单页应用(SPA)。它的灵活性和插件生态使其成为许多企业级应用的首选。
主要区别
- 启动速度: Vite 在开发模式下提供了更快的启动和热更新速度,因为它避免了预打包的步骤。
- 打包策略: Vite 利用现代浏览器的 ES 模块特性,而 Webpack 使用传统的打包方法,将所有资源和代码编译成一个或多个文件。
- 构建工具链: Vite 默认使用 Rollup 作为生产环境的打包工具,而 Webpack 自身就是一个打包工具。
- 插件和生态系统: Webpack 拥有一个成熟的插件生态系统,几乎可以定制任何构建流程。Vite 也支持插件,但相对较新,生态系统仍在发展中。
- 配置复杂度: Vite 旨在提供开箱即用的体验,其默认配置已经足够多数应用,而 Webpack 通常需要更多的配置和优化。
选择 Vite 还是 Webpack 取决于项目需求、团队熟悉度和特定场景。对于新项目,特别是当开发速度和现代浏览器特性是优先考虑时,Vite 可能是一个更好的选择。对于需要复杂构建流程和已经深度依赖于 Webpack
生态系统的项目,Webpack 仍然是一个强大的选择。
Vue2 升级 Vue3 需要注意什么
数组去重有哪些方法
数组去重是编程中常见的一个需求,尤其是在处理大量数据时。JavaScript 提供了多种方法来实现数组的去重。以下是一些常用的方法:
1. 使用 Set 和展开运算符
这是最简单和最现代的方法之一,利用了 Set
对象可以存储任何类型的唯一值的特性。
1 | const array = [1, 2, 2, 3, 4, 4, 5]; |
2. 使用 Set 和 Array.from()
与使用展开运算符类似,但是通过 Array.from()
方法将 Set
转换成数组。
1 | const array = [1, 2, 2, 3, 4, 4, 5]; |
3. 使用 filter 方法
通过数组的 filter()
方法结合 indexOf()
方法来过滤重复元素。这种方法不需要额外的数据结构。
1 | const array = [1, 2, 2, 3, 4, 4, 5]; |
4. 使用 reduce 方法
使用 reduce()
方法累加数组中的元素,结合条件判断实现去重。
1 | const array = [1, 2, 2, 3, 4, 4, 5]; |
5. 使用 Map 数据结构
利用 Map
的键唯一的特性来去重,适合于数组元素是对象这类比较复杂的情况。
1 | const array = [1, 2, 2, 3, 4, 4, 5]; |
6. 循环比较(传统方法)
传统的双重循环检查方法,性能较低,但在一些特定情况下仍然有其用途。
1 | const array = [1, 2, 2, 3, 4, 4, 5]; |
总结
根据你的具体需求(比如是否考虑到数组中元素的类型、执行效率等),可以选择最适合的方法进行数组去重。对于大多数情况,使用 Set
和展开运算符或 Array.from()
是最简洁和现代的解决方案。
CSS怎么解析选择器
2、V8解析执行JavaScript代码过程
3、script标签
4、子元素里面绝对定位,设置宽高,能否撑起父元素
5、怎么形成BFC
6、ES6新特性
7、ES6新增什么作用域,之前有什么作用域
8、类型转换
9、代码题——立即执行函数能否访问到全局变量
10、React——setState同步异步
11、React父子组件通信
0412网易
介绍项目,项目的适配不是基于移动端的适配,针对不同的电脑
盒模型以及组成
事件流:捕获、目标、冒泡,如果有1000个事件需要触发,如何解决?–事件代理,原生事件问得多
css如何布局?flex:1
–flex-grow,flex-basis,flex-shrink
![img](https://lofter.lf127.net/1698569107364/SCR-20231029-nrdl.png)
左右浮动,上面设置右边的flex:1,右边的文字设置超过两行隐藏
css的动画,transition
和animation
transition:只能在初始状态和结束状态之间提供动画效果,不支持中间状态或复杂的动画序列。
1 | div { |
**animation:**用于更复杂的动画效果,它允许你通过关键帧(@keyframes 规则)定义动画的中间步骤。你可以控制动画的中间状态、循环次数、方向、延迟等。
1 | @keyframes example { |
webpack、vite,代码中如何配置,举例说明
分成不同的环境:生产、灰度、线上,
ts的泛型、接口、枚举
- 定义泛型函数
1
2
3
4
5
6function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString"); // 类型为 string
let output2 = identity<number>(100); // 类型为 number - ts的泛型有属性的时候可以使用extends关键字进行约束,在这个例子中,泛型 T 被约束了,它必须符合 Lengthwise 接口,这意味着 T 必须有一个 length 属性。
1
2
3
4
5
6
7
8interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 它有一个 .length 属性
return arg;
} - 枚举遍历,for … in
1
2
3
4
5
6
7
8
9enum Color {
Red,
Green,
Blue
}
for (let color in Color) {
console.log(color); // 打印出 "0", "1", "2", "Red", "Green", "Blue"
}
美团
cookie如何设置跨域
Cookie跨域实现主要依赖于CORS(Cross-Origin Resource Sharing)策略。在服务器端设置Access-Control-Allow-Credentials和Access-Control-Allow-Origin响应头,并在AJAX请求中设置withCredentials为true。
服务器端设置(以Node.js的Express为例):
1 | app.use(function(req, res, next) { |
客户端设置(以JavaScript的XMLHttpRequest为例):
1 | var xhr = new XMLHttpRequest(); |
确保服务器响应头中的Access-Control-Allow-Origin与实际请求的域相匹配,并且如果withCredentials为true,则Access-Control-Allow-Credentials也必须设置为true。
0415DeepLang
postmessage和http通信
浏览器原理,事件循环和url,
ES6创建的函数我不想他被new创造新的实例
使用new target:
1 | function myFunction() { |
对es6的理解
强缓存和协商缓存
用户1修改了数据,用户2及时更新查看,你们是用的是什么方式?请求的数据如何不请求http的方式进行更新
在多用户环境中,确保所有用户看到的数据都是最新的,是一个常见的挑战。特别是在你提到的场景中,用户1修改了数据后,用户2需要无需发送传统HTTP请求就能看到更新的数据。为此,可以使用以下技术和策略:
1. WebSocket
WebSocket 提供了一种在单个连接上进行全双工、双向交互的方式。服务器可以通过WebSocket连接直接向客户端推送数据,这对于实时应用来说非常有用。当用户1更新数据时,服务器可以通过所有打开的WebSocket连接向其他用户推送这些更改。
实施步骤:
- 建立WebSocket连接。
- 当用户1提交更新时,服务器处理这些更改并通过WebSocket向所有其他在线用户广播更新。
- 客户端收到推送的数据后,更新UI以反映最新信息。
2. Server-Sent Events (SSE)
Server-Sent Events 允许服务器向浏览器推送事件。与WebSocket相比,SSE是单向的,从服务器到客户端,这在只需要服务器推送而不需要从客户端到服务器的消息传递的应用中非常有效。
实施步骤:
- 客户端设置监听特定URI的SSE。
- 当用户1更新数据时,服务器将这些更新作为事件发送给所有监听该事件的客户端。
- 客户端接收到这些事件后,更新页面数据。
3. 使用轮询或长轮询
如果WebSocket或SSE不适用于您的情况,轮询是另一种确保用户看到最新数据的方法,尽管它不如前两种方法实时。轮询涉及定期发送HTTP请求以检查更新,而长轮询则保持连接打开直到有更新可发送。
实施步骤:
- 客户端每隔几秒发送一次HTTP请求,询问服务器是否有数据更新。
- 服务器响应请求,如果有更新则发送更新数据。
4. 使用Pub/Sub模型的服务
使用像Google Firebase Realtime Database这样的Pub/Sub模型服务,可以允许客户端订阅数据更新,并在数据发生变化时接收实时更新。
实施步骤:
- 客户端在应用中订阅特定数据。
- 任何数据更改都会触发通知,所有订阅了这些数据的客户端将自动接收更新。
结论
根据你的应用需求(如用户基数、预期负载、实时性需求等)和技术堆栈,你可以选择最适合的方法。WebSocket和SSE提供了更加实时的数据同步方案,而轮询则是一种更简单但请求量较大的解决方案。使用如Firebase这样的实时数据库可以简化开发工作,但可能会引入外部依赖和额外成本。
封装一个hooks
1 | import React, { useState } from 'react'; |
现在我有一个文件,下载需要8s,如何使用协商缓存和强缓存进行优化?
对于文件下载缓慢的问题,正确地使用HTTP缓存策略可以显著提高性能。在HTTP缓存中,主要分为强缓存和协商缓存。这两种缓存策略可以有效减少不必要的网络请求,加快文件访问速度。以下是如何选择和使用这两种缓存策略来优化你的文件下载问题:
强缓存 (Strong Caching)
强缓存直接从浏览器缓存中获取资源,不与服务器进行交互,除非缓存过期。设置强缓存的方法主要是通过Cache-Control
和 Expires
HTTP响应头实现。
Cache-Control: 最常用的缓存控制指令包括
max-age
(资源可以缓存的最大时间,单位是秒)和public
(指示响应可被任何缓存区缓存)。1
Cache-Control: public, max-age=31536000
这表示资源将被缓存并在一年后过期,非常适用于不经常变动的文件。
Expires: 指定资源的过期日期和时间。在
Cache-Control
出现之前就已存在,通常与Cache-Control
一起使用以保证兼容性。1
Expires: Thu, 01 Dec 2022 16:00:00 GMT
如果设置了
Cache-Control
的max-age
,通常不需要Expires
。
协商缓存 (Negotiation Caching)
当强缓存过期后,协商缓存允许浏览器向服务器发出请求,询问资源是否有更新。如果服务器确认资源未更新(通过发送HTTP状态码304),浏览器将从本地缓存加载资源。
Last-Modified / If-Modified-Since: 服务器在响应中发送
Last-Modified
日期,浏览器下次请求时发送If-Modified-Since
,询问服务器在该日期后资源是否被修改。1
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
ETag / If-None-Match: 服务器发送资源的ETag(一种标识符),浏览器使用
If-None-Match
来发送ETag,检查资源是否有变动。1
ETag: "686897696a7c876b7e"
选择适合的缓存策略
对于你的文件下载问题,考虑以下因素:
- 文件变动频率:如果文件不经常更新,使用强缓存是一个好选择。可以通过设置
Cache-Control
的max-age
为一个相对较长的值来实现。 - 实时性要求:如果文件更新不频繁但需要保证用户能快速获取最新版本的文件,可以结合使用强缓存和协商缓存。强缓存确保在文件不变时快速加载,协商缓存确保文件一旦更新,用户能立即获取最新版本。
实施示例
1 | Cache-Control: public, max-age=86400 |
这种设置意味着文件将被缓存一天,每天浏览器会检查一次文件是否更新。这样可以显著减少不必要的下载时间,同时保持文件的相对新鲜度。如果你的文件是用户每次都需要最新版的,可以适当减少max-age
值,或者仅依赖ETag进行更频繁的检查。
通过这种方式,你可以有效利用HTTP缓存提高文件的访问速度和用户体验。
二面
http的head有哪些
http协议
不使用递归实现中序遍历:栈
mysql的事务有哪些
杭州某外包
react的hooks
useMemo和useEffect的区别
在React中,
useMemo
和useEffect
都是钩子(Hooks),它们用于增强函数组件的功能。这两个钩子虽然都与组件的渲染周期密切相关,但它们的用途、功能和执行时机有明显的不同。理解这些差异对于编写高效和可预测的React应用至关重要。
useMemo
useMemo
是用于优化性能的钩子,通过记忆复杂函数的计算结果来避免在每次渲染时都执行这些计算。useMemo
接收一个“创建”函数和一个依赖数组,仅当依赖项改变时,才会重新计算记忆值。
主要用途:
- 性能优化:避免在每次渲染时进行高开销的计算。
- 记忆化复杂派生数据:基于依赖计算得到的数据,只有当依赖改变时,才重新计算。
示例:
1 | const expensiveValue = useMemo(() => { |
在这个例子中,只有当a
或b
变化时,函数computeExpensiveValue
才会被调用。
useEffect
useEffect
是用于在函数组件中执行副作用的钩子。副作用指的是那些对外部世界产生影响的操作,比如数据获取、手动修改DOM和订阅事件。useEffect
同样接收一个函数和一个依赖数组,但它的工作方式与useMemo
大不相同。
主要用途:
- 与外部世界的交互:执行与渲染无关的操作,如API调用、订阅或定时器。
- 资源清理:在组件卸载时进行清理,如取消订阅或清除定时器。
- DOM更新后的操作:在组件和DOM更新后执行操作。
示例:
1 | useEffect(() => { |
这个例子中,useEffect
用于订阅一个数据源,依赖数组确保只在dataSource
变化时重新订阅。
执行时机的区别
useMemo:
- 在组件渲染过程中执行,即同步执行。因为它可能会影响渲染输出的内容,必须在渲染流程中完成。
useEffect:
- 在组件渲染到屏幕之后执行,即异步执行。这意味着它不会阻塞浏览器的绘制过程,适用于那些对渲染结果无直接影响的操作。
总的来说,useMemo
主要用于计算优化,以减少组件的重渲染成本;而useEffect
主要用于处理副作用,实现与应用的其他部分的交互。正确的使用这两个钩子,可以提高应用的性能和响应速度。
父子传值:ref
子组件
1 | import React from 'react'; |
父组件:
1 | import React, { useRef } from 'react'; |
vue不仅仅是使用路由进行鉴权
promise.all如何在接受到rejected也成功返回?promise.race
答:通过catch
箭头函数
let const
vuex全局状态管理
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式和库。它以集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 非常适用于大型单页应用,它可以帮助你更好地在组件外部管理状态,让状态的变化更加可追踪和易于调试。
Vuex 的核心概念
State
- Vuex 使用单一状态树 —— 用一个对象就包含了全部的应用层级状态。每个应用将仅仅包含一个 store 实例。
- 状态存储是响应式的,Vue组件从 store 中读取状态,并且当状态发生变化时,相应的组件也会相应地得到高效更新。
Getters
- 类似于 Vue 的计算属性,getters 可以用于从 store 的 state 中派生出一些状态,例如过滤列表、计数等。
- Getters 会接受 state 作为其第一个参数,也可以接受其他 getters 作为第二个参数。
Mutations
- 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutations 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和一个 处理函数 (handler)。
- Mutation 必须是同步函数。
Actions
- Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
- Action 函数接收一个与 store 实例具有相同方法和属性的 context 对象,因此可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
- Action 类似于 mutation,不同在于:
Modules
- Vuex 允许我们将 store 分割成模块(module),每个模块拥有自己的 state、mutation、action、getter,甚至是嵌套子模块 —— 从而允许我们对复杂的应用进行状态管理。
- 模块内部的 mutation 和 getter 接收的第一个参数是模块的局部状态对象。
Vuex 的使用场景
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
Vuex 的使用示例
以下是一个简单的 Vuex store 的定义示例:
1 | import Vue from 'vue'; |
在组件中使用:
1 | <template> |
结论
Vuex 提供了一个集中存储所有组件状态的方式,并以规范的方式更新状态,易于维护和监控状态变化,特别适合处理大型、复杂的应用中多个组件共享状态的情形。
完美世界
介绍一下fiber
箭头函数和普通函数的区别
箭头函数是在ES6中引入的,提供了一种更简洁的方式来写函数表达式。箭头函数与普通函数(如函数声明或函数表达式)相比,有几个主要区别:
语法更简洁:
- 箭头函数提供了更短的函数语法。例如,一个普通的函数表达式
function(x, y) { return x + y; }
可以用箭头函数写为(x, y) => x + y
。
- 箭头函数提供了更短的函数语法。例如,一个普通的函数表达式
**没有自己的
this
**:- 在箭头函数中,
this
关键字具有其封闭执行环境的上下文,这与普通函数不同,普通函数的this
指向在运行时被调用时的上下文(如由一个对象调用)。 - 这使得箭头函数特别适合用在需要维持上下文的情况,如在回调函数和方法链中。
- 在箭头函数中,
不绑定
arguments
对象:- 箭头函数不提供
arguments
对象。如果你需要访问传给函数的参数列表,应使用剩余参数语法(...args
)代替。
- 箭头函数不提供
不可以使用
new
操作符:- 箭头函数不能用作构造函数,不可以使用
new
操作符,因为它们没有[[Construct]]
方法。尝试对箭头函数使用new
会抛出一个错误。
- 箭头函数不能用作构造函数,不可以使用
**不绑定
super
**:- 箭头函数不绑定
super
关键字。在使用类的继承时,这一点需要注意。
- 箭头函数不绑定
不能用作生成器函数:
- 箭头函数不能使用
yield
关键字,在其内部使用会导致语法错误。
- 箭头函数不能使用
总结来说,箭头函数主要用于那些不需要绑定自身 this
、不用作构造函数、并且更注重简洁性的场合。由于它们在功能上的这些限制,它们并不总是可以替代普通函数。
hooks可以写在if判断语句里吗
在React中,Hooks不能写在条件语句(如if
)、循环、嵌套函数或任何不是React函数组件顶层的地方。这是因为Hooks必须按照相同的顺序在每次组件渲染时被调用,以保证Hooks状态的正确性。将Hooks放在条件语句中使用可能会破坏这种顺序,导致不可预测的结果。
为什么Hooks不能放在条件语句中
React的Hooks功能依赖于调用顺序。每次组件渲染时,React按照Hooks被调用的顺序来维护和更新状态。如果你将Hook放入一个条件语句中,那么在某些渲染时Hook可能被调用,而在另一些渲染时可能不被调用,这将破坏React对Hooks调用顺序的跟踪。
错误示例
以下是一个错误的使用示例,展示了将useState
放在一个if
语句中的情况:
1 | import React, { useState, useEffect } from 'react'; |
这种用法会导致React无法正确追踪count
状态的变化,可能会引发运行时错误。
正确用法
如果你需要根据条件初始化状态或进行其他操作,你应该将条件逻辑放在Hook内部,而不是将Hook放在条件语句中。以下是一个改进的示例:
1 | import React, { useState, useEffect } from 'react'; |
在这个改进的例子中,useState
和useEffect
都被放在组件的顶层,并根据条件以适当的方式使用。这保证了Hooks的调用顺序是一致的,同时也能根据不同的条件执行不同的逻辑。
结论
总的来说,React Hooks应始终在函数组件的最顶层使用,无论是条件、循环还是嵌套函数都不应包含Hooks。遵守这一规则将帮助你避免在组件状态管理中遇到复杂和难以调试的问题。
computed与wacth有什么区别
ES6
原型和原型链
原型:prototype,在 JavaScript 中,几乎所有的对象都有一个“原型”。原型本身也是一个对象,被作为一个模板对象,它存储了可以被其他对象继承的属性和方法。当你创建一个对象时,这个对象自动引用一个原型,从中可以继承方法和属性。
例如,所有的 JavaScript 函数都有一个 prototype
属性,这个属性指向一个对象。当你使用构造函数创建一个新对象时(使用 new
关键字),新对象的内部 [[Prototype]]
属性(在大多数浏览器中可以通过 __proto__
访问)将被设置为构造函数的 prototype
对象。
例如:
1 | function Person(name) { |
原型链:prototype chain,原型链是一个对象通过其内部 [[Prototype]]
属性链接到其原型,而这个原型本身也可能有自己的原型,并从中继承方法和属性,这样的链条一直向上直到一个对象的原型为 null
。通常,原型链的终点是 Object.prototype
,这是所有纯粹对象默认的原型。
当你尝试访问一个对象的属性时(包括方法),JavaScript 首先查找对象本身是否有这个属性。如果没有找到,JavaScript 将沿着原型链向上查找,直到找到该属性或到达原型链的末端。
1 | function Person(name) { |
JavaScript 中的继承是通过原型和原型链实现的。这种机制允许对象共享方法和属性,而无需在每个对象上重新定义它们,这提高了效率并节省了内存。理解原型和原型链是深入理解 JavaScript 对象、继承和类行为的关键。
new的过程都干了些什么
在 JavaScript 中,new
操作符用于创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型的实例。当你使用 new
操作符时,实际上会执行以下几个步骤:
创建一个新对象:
new
操作符首先会创建一个空的简单 JavaScript 对象;即一个新的空对象。
设置原型:
- 接着,这个新对象的内部
[[Prototype]]
(也就是__proto__
)属性会被赋值为构造函数的prototype
属性。这意味着新对象将继承构造函数原型上的方法和属性。
- 接着,这个新对象的内部
执行构造函数:
- 构造函数被调用,并将
this
绑定到新创建的对象上。这意味着在构造函数中你可以使用this
关键字来引用新对象,并给这个新对象添加属性和方法。
- 构造函数被调用,并将
返回新对象:
- 如果构造函数返回一个对象,则
new
表达式结果为这个对象;如果构造函数返回的不是对象(包括undefined
或任何原始类型的值),则返回新创建的对象。
- 如果构造函数返回一个对象,则
通过这些步骤,new
操作符不仅仅创建了一个对象实例,还正确设置了对象的原型链,这对于实现基于原型的继承非常关键。这使得实例可以访问构造函数原型上定义的方法和属性。例如:
1 | function Person(name) { |
在这个例子中,new Person("Alice")
创建了一个新的 Person
实例,并设置了 name
属性和 greet
方法。每次使用 new Person
时,都会按照这个模式创建一个新的对象。
事件循环
箭头函数和普通函数有什么区别
假设vue组件里一个ref的变量 然后再里渲染了它 然后去修改这个变量 那页面上为什么会修改这个变量呢? 介绍一下这个具体的过程
在 Vue.js 框架中,ref
是一个用于创建响应式引用的函数,这是 Vue 3 中 Composition API 的一部分。当你在一个 Vue 组件中使用 ref
声明一个变量,并在 <template>
中使用这个变量时,Vue 设置了一个响应式系统,来确保当这个变量的值改变时,视图也会自动更新。下面是这个过程的具体说明:
创建响应式引用:
使用ref
函数创建一个响应式变量时,Vue 会将这个变量包裹在一个响应式对象中。例如,const count = ref(0);
这里的count
实际上是一个带有value
属性的对象,count.value
初始化为 0。在模板中使用:
在 Vue 的<template>
中使用这个响应式变量时,可以直接引用它,如<div>{{ count }}</div>
。Vue 的渲染系统会自动处理ref
并访问其value
属性,因此你无需在模板中写count.value
。依赖跟踪:
当模板被渲染时,Vue 会自动跟踪所有被访问的响应式引用。这意味着 Vue 知道模板中的某部分依赖于count
变量。这是通过 Vue 的响应式系统内部的依赖跟踪和收集机制完成的,通常涉及一些内部的发布者-订阅者模式。响应式更新:
当你更新一个响应式引用的值时,如count.value = 1
,Vue 的响应式系统会察觉到这个变化。因为count
是响应式的,所以任何依赖于count
的视图或计算值都会被 Vue 的调度系统安排重新计算和更新。视图更新:
改变count.value
后,因为视图部分依赖于count
的值,Vue 将重新渲染与之相关的 DOM 元素。Vue 的虚拟 DOM 系统会计算之前和当前虚拟 DOM 树的差异,并高效地更新真实的 DOM 以反映新的状态。
通过这个过程,Vue 确保了界面的数据和视图之间的同步。这就是为什么在 Vue 组件中改变一个通过 ref
创建的变量的值会自动更新页面上相应部分的原因。这种模型极大地简化了动态界面的开发,使得开发者可以专注于数据逻辑而非如何操作 DOM。
垃圾回收(面试官反问了解v8吗,新生代分为哪几块区域,这几块儿区域是用来干什么的?这两块儿区域什么时候互换呢?互换了有什么好处吗?)
JavaScript 中的垃圾回收(Garbage Collection, GC)是一个自动的内存管理机制,它帮助开发者管理内存,避免内存泄露。垃圾回收的主要任务是识别并回收不再使用的内存,以便这部分内存可以被重新利用。
垃圾回收的基本概念
JavaScript 的垃圾回收主要是基于“可达性”(reachability)的概念:
- 可达值:从根(root)集合(通常是在代码中直接引用的变量)出发,可以访问到的值被认为是“可达的”。这些值不会被回收,因为它们可能在未来的执行过程中还需要被使用。
- 不可达值:如果一个值不可通过任何方式被根集合访问,那么这个值就被认为是“不可达的”,并可以被垃圾回收器回收。
V8 引擎的垃圾回收机制
V8 引擎是 Google 开发的 JavaScript 引擎,广泛应用于 Chrome 浏览器和 Node.js 环境。V8 的垃圾回收机制较为复杂,主要包括以下几个方面:
分代回收:
- 新生代(Young Generation):V8 将内存分为新生代和老生代两部分。新生代存放生命周期短的小对象。这部分内存的回收频率较高,使用 Scavenge 算法进行高效的垃圾回收。
- 老生代(Old Generation):存放生命周期长或从新生代晋升过来的对象。老生代的垃圾回收使用 Mark-Sweep(标记-清除)和 Mark-Compact(标记-整理)算法,以减少内存碎片。
停顿时间优化(Stop-The-World):
- 垃圾回收过程中,为了安全地清除不再需要的对象,JavaScript 的执行通常会暂停,这被称为“全停顿”(Stop-The-World)。V8 努力减少垃圾回收带来的停顿时间,通过增量标记(Incremental Marking)和延迟清扫(Lazy Sweeping)等技术,将垃圾回收工作分散到多个小周期中。
并行处理和并发处理:
- 在进行垃圾回收时,V8 尝试使用并行处理,以多线程的方式同时进行垃圾回收,以减少每次停顿时间。
- V8 还引入了并发回收(Concurrent GC),其中某些垃圾回收任务可以在 JavaScript 运行时并行进行,进一步减少对主线程的影响。
总的来说,V8 引擎的垃圾回收策略旨在平衡内存使用和回收效率,同时最大限度地减少对程序执行的干扰。这些技术使得在 V8 上运行的应用能够更加高效地管理内存,提供更好的用户体验。
闭包
ts:interface、type分别是什么时候使用
都是用来定义类型的,接口和类型别名
在 TypeScript 中,interface
和 type
都是用来定义类型的,但它们在某些方面有所不同,并且各自适用于不同的场景。下面是它们的主要区别以及使用场景的指导。
1. 基本区别
Interface(接口):
- 主要用于定义对象的形状,支持继承和实现(implements)。
- 接口是一种更强大的方式来定义包括方法和属性的契约。
- 支持声明合并(Declaration Merging),即允许同名的接口自动合并其成员。
Type(类型别名):
- 类型别名可以用来定义对象类型,也可以用来定义其他类型,如基本类型、联合类型、交集类型等。
- 类型别名不支持声明合并,即同名的类型别名不能重复声明,但可以通过交集和联合类型进行扩展。
- 类型别名更适合用于复杂的类型组合。
2. 使用场景
何时使用 Interface:
- 当你需要定义一个类或对象字面量的结构时,接口是首选。它们提供了一个清晰的方式来描述一个对象应有的形状。
- 当你在构建大型代码库或第三方类型定义(如 DefinitelyTyped)时,接口提供了一种易于扩展和重用的方式。
- 如果需要其他类或接口继承自该类型时,使用接口。接口支持多重继承。
何时使用 Type:
- 当你需要使用联合或交集类型来组合现有的类型时,类型别名是更好的选择。
- 当你需要为基本类型(如字符串或数字)定义一个别名时。
- 当你需要在一个表达式中使用泛型或其他复杂操作时,类型别名提供了更大的灵活性。
3. 实例比较
1 | // 使用 Interface |
4. 总结
虽然 interface
和 type
都可以在许多场景下互换使用,但它们各自的特性使得它们在特定的使用场景下更为合适。接口更适合于定义公共的API的对象结构,而类型别名在处理复杂的类型组合时则更加灵活。选择哪一个,取决于具体的应用场景和个人或团队的偏好。
枚举类型?
构建工具只用过webpack吗?有用过其他的吗?Vite的优势在哪?有没有改过它们的一些配置
webpack里的Loader与Plugin有什么区别
在 Webpack 中,Loader 和 Plugin 是两种扩展 Webpack 功能的方法,它们在使用和功能上有显著的区别。理解它们的不同之处对于有效地使用 Webpack 构建项目至关重要。下面是 Loader 和 Plugin 的主要区别及其各自的作用:
Loader
定义和作用:
- Loader 在 Webpack 中主要用于转换某些类型的模块。它们允许你在
import
或“加载”模块时预处理文件。因此,Loader 类似于一个“转换器”,用于处理非 JavaScript 文件(如 TypeScript, SCSS, JSX 等)。
工作方式:
- Loader 可以链式传递,每个 Loader 单独完成一项转换工作。
- 它们处理输入的源文件并产出新的源文件,这些文件可以传递给链中的下一个 Loader,或者输出到最终的 bundles 中。
配置方式:
- 在 Webpack 配置中,Loader 是通过
module.rules
数组进行配置的,可以指定多个 Loader,并且有顺序之分。
示例:
1 | module: { |
Plugin
定义和作用:
- Plugin 用于扩展 Webpack 的功能。它们直接访问 Webpack 的整个编译生命周期,并且可以执行更广泛的任务,如打包优化、资源管理和环境变量注入等。
工作方式:
- Plugin 通过钩子机制工作,可以监听 Webpack 构建过程中的各种事件,然后在这些事件发生时执行特定的函数来实现功能扩展。
配置方式:
- Plugin 在 Webpack 配置中通过
plugins
数组配置,通常是通过实例化 Plugin 类(使用new
关键字)来配置的。
示例:
1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); |
总结
Loader 和 Plugin 虽然都是 Webpack 的功能扩展方式,但它们的应用场景和实现方式大不相同:
- Loader 更多地关注于资源模块的加载和处理,它们对文件内容进行转换。
- Plugin 提供了一种工作在构建过程更深层次的方法,能够处理更多构建流程中的广泛任务。
在实际使用 Webpack 进行项目构建时,合理利用 Loader 和 Plugin 可以极大地提高构建的灵活性和功能性。
TCP连接三次握手流程
项目里的图片压缩是怎么做的?
删除一个单向链表的倒数第n个节点,讲一下思路
有a和b两个变量,想把a和b交换,不能用中间变量和es6的解构赋值,请你讲一下思路
一般通过什么方式学前端? 如果可以的话 最快什么时间到岗 能实习到什么时候
使用websocket
在简历中描述实时通信功能的实现,尤其是使用 WebSocket 技术,是一个很好的机会来展示你的技术能力和对现代Web技术的掌握。WebSocket 是一种在单个TCP连接上进行全双工通信的协议,允许服务器主动发送信息给客户端,非常适合需要实时数据更新的应用,如在线聊天、游戏、实时通知系统等。确实,WebSocket 能够在不刷新页面的情况下实现实时通信。
实现原理: 实现了客户端与服务端的双向通信,只需要连接一次,就可以相互传输数据,很适合实时通讯、数据实时更新等场景。
Websocket 协议与 HTTP 协议没有关系,它是一个建立在 TCP 协议上的全新协议,为了兼容 HTTP 握手规范,在握手阶段依然使用 HTTP 协议,握手完成之后,数据通过 TCP 通道进行传输。
Websoket 数据传输是通过 frame 形式,一个消息可以分成几个片段传输。这样大数据可以分成一些小片段进行传输,不用考虑由于数据量大导致标志位不够的情况。也可以边生成数据边传递消息,提高传输效率。
优点
: 双向通信。客户端和服务端双方 都可以主动发起通讯。 没有同源限制。客户端可以与任意服务端通信,不存在跨域问题。 数据量轻。第一次连接时需要携带请求头,后面数据通信都不需要带请求头,减少了请求头的负荷。 传输效率高。因为只需要一次连接,所以数据传输效率高。
缺点
: 长连接需要后端处理业务的代码更稳定,推送消息相对复杂; 兼容性,WebSocket 只支持 IE10 及其以上版本。 服务器长期维护长连接需要一定的成本,各个浏览器支持程度不一; 【需要后端代码稳定,受网络限制大,兼容性差,维护成本高,生态圈小】
BFC
技术描述
前端开发工程师
[公司名称] | [日期]
- 实时通信实现:设计并实现了基于 WebSocket 的实时通信系统,支持无需页面刷新即时更新数据,增强用户交互体验。利用
Socket.IO
在多用户环境下同步状态和通知,确保信息的即时传递和显示。 - 动态用户界面响应:开发了响应式通知和消息系统,通过 WebSocket 接收后端推送的实时更新,动态调整前端显示,包括警告提示、状态更新等,无需用户手动刷新页面。
- 高效数据处理与优化:优化 WebSocket 数据传输和事件处理逻辑,降低了网络延迟和服务器负载,提升了系统的响应速度和可靠性。
- 跨浏览器兼容性和错误处理:确保 WebSocket 实现在所有主流浏览器上稳定运行,通过优雅的错误处理和重连策略增强了应用的健壮性。
技术细节
你可以进一步细化描述,如果面试官对这部分感兴趣,以下是你可以讨论的一些技术细节:
- WebSocket vs. 传统轮询:解释为什么选择 WebSocket 而非 HTTP 长轮询或短轮询,强调 WebSocket 的低延迟和减少服务器负载的优势。
- 实现细节:讨论如何使用
Socket.IO
(如果你使用了这个库)处理连接的建立、数据的接收和发送、断线重连等。 - 安全措施:描述如何保障 WebSocket 通信的安全性,例如使用 WSS(WebSocket Secure),以及如何管理和验证客户端。
这样的描述不仅展示了你在实现关键功能方面的技术能力,还突出了你对提升用户体验和应用性能的关注,是求职简历中的亮点之一。
postmessage
postMessage
是一个允许不同窗口之间或一个窗口与其嵌入的 <iframe>
或其他浏览器上下文之间安全地进行脚本通信的方法。这个功能是 Web HTML5 API 的一部分,特别重要在于它提供了一种方式来绕过同源策略的限制,即通常情况下,不同源的窗口不能彼此通信。
功能和用途
跨源通信:
postMessage
允许不同源之间的窗口(页面间或页面与嵌入的<iframe>
、<object>
、<embed>
或在不同标签页中的页面)进行数据交换。这对于加载了第三方内容的网页尤为重要。数据传输:
通过postMessage
,你可以发送多种类型的数据,包括字符串、对象等。发送的数据被结构化克隆,这意味着复杂的对象结构可以在不同的窗口或框架间完整传递。安全性:
postMessage
方法包括一个targetOrigin
参数,指定了哪些域名可以接收到消息,这是一个重要的安全特性。如果设为"*"
,则表示接受所有域的消息,但这通常不推荐,因为这可能带来安全风险。
使用示例
在一个简单的应用中,假设有一个父窗口和一个嵌入的 <iframe>
:
父窗口代码:
1 | // HTML |
在 <iframe>
页面中的代码:
1 | // JavaScript |
总结
postMessage
是一个强大的 API,用于在浏览器上下文之间进行安全、有效的数据通信。正确使用时,它既能保证数据交换的灵活性,也能确保通信的安全。因此,在开发涉及多个源或多窗口交互的现代 Web 应用时,postMessage
是不可或缺的工具。
Web Workers 和 WebSocket 的主要区别
Web Workers
Web Workers 提供一种将计算密集或阻塞性任务从主线程(通常是 UI 线程)中移出的方法,以避免阻碍 UI 的响应性。Web Workers 运行在后台线程中,不会影响页面的性能,特别适用于执行需要大量计算的任务。
特点和用途:
- 并行处理:Web Workers 允许在背景并行线程中运行 JavaScript,不干扰主 UI 线程。
- 性能优化:适用于执行复杂计算,如图像处理、大数据分析等,从而提升应用的响应速度和用户体验。
- 无 DOM 访问:Workers 运行在一个与主页面隔离的全局上下文中,不能直接操作 DOM。
WebSocket
WebSocket 提供了全双工通信机制,允许客户端和服务器之间建立一个持久的连接,并通过这个连接实时地双向传输数据。这对于需要实时数据更新的应用非常重要,如在线游戏、聊天应用或实时交易平台。
特点和用途:
- 实时通信:WebSocket 使得客户端和服务器可以在任何时候开始发送数据,支持更高效的实时数据交换。
- 减少开销:与传统的 HTTP 连接相比,WebSocket 在建立后可以保持连接开放,减少了需要建立连接的频繁开销。
- 双向通信:客户端和服务器都可以主动发送数据给对方,而不是仅由客户端发起请求。
相关性与区别
- 处理数据的方式不同:Web Workers 用于执行在客户端进行的计算密集型或阻塞性任务,而 WebSocket 主要用于在客户端和服务器之间实时交换数据。
- 用途和目的:Web Workers 是为了改善用户界面的性能和响应性,而 WebSocket 是为了实现网络应用中的实时数据传输。
- 交互性:WebSocket 通过网络连接实现实时、双向的数据交流,适用于任何需要与服务器实时通信的场景;Web Workers 主要是单向的从主线程分配任务,并在任务完成后可能返回结果,主要用于处理本地的数据和任务。
总的来说,虽然 Web Workers 和 WebSocket 都与数据处理有关,但它们服务的上下文和目的有很大的不同。Web Workers 解决的是计算性能问题,而 WebSocket 解决的是实时网络通信问题。
跨域的解决办法
1. CORS(Cross-Origin Resource Sharing): CORS是一种跨域资源共享的标准,通过服务端设置响应头中的Access-Control-Allow-Origin字段来允许跨域请求。服务端在收到跨域请求时,检查Origin字段,如果请求的源在白名单内,则在响应头中添加Access-Control-Allow-Origin字段,并设置为允许的源,从而允许跨域请求。
2. JSONP(JSON with Padding): JSONP是一种利用script标签的src属性没有跨域限制的特性来实现跨域请求的技术。通过动态创建script标签,将跨域请求的数据封装在一个回调函数中,然后由服务端返回,并执行回调函数,从而实现跨域数据的获取。
3. 代理服务器: 在开发环境中,可以通过配置代理服务器来实现跨域请求。代理服务器接收前端请求,然后在后端发起真正的请求,获取数据后再返回给前端,由于请求是由后端发起的,所以不存在跨域问题。
4. iframe跨域通信: 使用iframe标签可以实现跨域通信。父页面和iframe页面属于不同的源,但是它们之间可以通过postMessage方法来进行跨域通信,实现数据的传递和交互。
前端缓存可以分为两种类型:浏览器缓存和 Web Storage。
- 浏览器缓存:浏览器缓存是指浏览器在本地保存一些静态资源的副本,以便在下次访问相同资源时可以直接从本地获取,而不需要再次从服务器下载。浏览器缓存主要包括以下几种类型:
- 强缓存:浏览器在请求资源时,会先检查该资源的缓存标识(如 Cache-Control 和 Expires),如果命中强缓存,则直接从缓存中获取资源,不会发送请求到服务器。
- 协商缓存:如果资源的缓存标识表示资源已经过期(如 Cache-Control 中的 max-age 或者 Last-Modified 和 ETag 等),浏览器会发送请求到服务器,服务器会根据请求头中的条件判断来决定是否返回资源内容,如果返回 304 状态码表示资源未发生改变,浏览器可以使用缓存中的资源。
- Web Storage:Web Storage 是 HTML5 提供的一种浏览器本地存储数据的方式,主要包括 localStorage 和 sessionStorage 两种。它们可以存储在浏览器中供网站使用,可以在页面会话结束后依然保留(localStorage),或者在页面会话结束后被清除(sessionStorage)。
- localStorage:localStorage 存储的数据没有过期时间,除非手动清除,否则会一直保存在浏览器中。
- sessionStorage:sessionStorage 存储的数据在页面会话结束时被清除,即当页面被关闭时数据也会被清除。
WebSocket 是一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket 通信协议于2011年被IETF定为标准RFC 6455,并由RFC7230补充规范。WebSocket 是 HTML5 标准的一部分,可实现客户端和服务器之间的持久连接,使服务器可以实时推送数据给客户端。
以下是一个使用Python语言结合Flask框架创建WebSocket服务器的简单示例:
from flask import Flask, render_template
from flask_socketio import SocketIO
app = Flask(name)
app.config[‘SECRET_KEY’] = ‘secret!’
socketio = SocketIO(app)
@app.route(‘/‘)
def index():
return render_template(‘index.html’)
@socketio.on(‘message’, namespace=’/chat’)
def handle_message(message):
print(‘Received message: ‘ + message)
socketio.emit(‘message’, message, namespace=’/chat’)
if name == ‘main‘:
socketio.run(app, debug=True)
在这个例子中,我们使用了Flask和Flask-SocketIO库来创建一个简单的WebSocket服务器。我们定义了一个路由/,当用户访问这个路由时,他们会看到一个index.html页面。我们还定义了一个事件处理程序handle_message,它会在收到客户端发送的消息时触发。
在客户端,你可以使用JavaScript的WebSocket API或者利用类似于jQuery的JavaScript库来轻松地建立和管理WebSocket连接。
以下是一个使用JavaScript和jQuery创建WebSocket客户端的简单示例:
在这个HTML页面中,我们使用jQuery库来处理DOM元素,并通过WebSocket与服务器进行通信。当用户在文本输入框中输入消息并点击发送按钮时,我们通过WebSocket发送消息到服务器。同时,我们也监听来自服务器的消息,并在控制台打印出来。
以上就是一个简单的WebSocket服务器和客户端的实现。在实际应用中,你可能需要处理更多的逻辑,例如用户认证、错误处理、消息的可靠传递等。
JWT
JWT(JSON Web Tokens)是一种开放标准(RFC 7519),用于在网络应用环境间安全地传递信息。它主要以一种紧凑且自包含的方式,在各方之间作为 JSON 对象安全地传输信息。这些信息可以被验证和信任,因为它是数字签名的。JWT 常用于身份验证和信息交换,特别适用于分布式站点的单点登录(SSO)场景。
JWT 的结构
JWT 通常包含三部分:头部(Header)、载荷(Payload)和签名(Signature)。
头部(Header):
- 头部通常由两部分组成:token 的类型(即 JWT)和所使用的签名算法(如 HMAC SHA256 或 RSA)。
- 示例:
1
2
3
4{
"alg": "HS256",
"typ": "JWT"
}
载荷(Payload):
- 载荷包含声明(Claims)。声明是关于实体(通常是用户)和其他数据的语句。
- 有三种类型的声明:注册的声明、公共的声明和私有的声明。
- 示例:
1
2
3
4
5{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
签名(Signature):
- 为了创建签名部分,你必须取头部的编码、载荷的编码,然后用头部中指定的算法进行签名。
- 示例:
1
2
3HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret)
JWT 的使用方式
在身份验证的过程中,JWT 通常如下使用:
用户认证:
- 用户通过登录凭证(用户名和密码)登录系统。
- 系统验证凭证的正确性,并创建一个 JWT,其中包含用户的一些信息,并对其进行签名,以证明该 token 在传送过程中未被篡改。
发送 JWT:
- Token 通常通过 Bearer schema 通过 HTTP 授权头部发送给用户,格式如下:
Authorization: Bearer <token>
。
- Token 通常通过 Bearer schema 通过 HTTP 授权头部发送给用户,格式如下:
服务端验证:
- 服务端或者需要验证用户身份的应用将接收到的 token 进行解码,验证签名是否有效。
- 验证 token 是否过期或者符合其他的业务规则。
JWT 的优点
- 紧凑:JWT 可以通过 URL、POST 参数或在 HTTP 头中发送,因其小巧,网络传输效率高。
- 自包含:JWT 包含了所有用户需要的信息,避免了多次查询数据库。
JWT 的缺点
- 存储问题:JWT 一旦签发在有效期内就一直有效,这可能导致一旦 JWT 被盗用,直到过期都无法废止(除非使用黑名单机制)。
- 体积较大:相比于传统的 session ID,JWT 因包含较多信息,体积较大,如果包含过多信息则会影响传输效率。
- 安全性问题:如果不使用 HTTPS,JWT 由于容易被截取,因此安全性会降低。此外,如果使用不当(如签名密钥泄露),也可能导致安全风险。
JWT 是一种非常流行的在服务之间安全地传递信息的方式,但需要注意安全实践,如使用 HTTPS 和定期更新签名密钥,以保证信息安全。
小米
文件上传,localstorage保存本地
盒模型
1 | /* 标准盒模型 */ |
css左右宽度固定,中间自适应,flex:1不同的属性是什么意思
var函数作用域
事件循环,看代码输出
树的转化,
useEffect函数配合return 输出顺序
虚拟列表的渲染?实现,视窗
文件上传localstorage,sessionstorage,cookie区别
微前端,iframe和qiankun的区别,样式隔离如何做的?别的微前端实现方式?
伪类和伪元素
string和new 创建的string一样吗?
apply bind call 修改this的指向如何实现?实现代码
HTML5+CSS
是的,Web Workers 和 WebSocket 都是 HTML5 提出的重要特性。它们各自解决了 web 应用开发中的不同问题,使得现代 web 应用能够更加高效和动态。
Web Workers
Web Workers 提供了一种在浏览器后台执行脚本的方法,而不会干扰主线程的用户界面。它允许开发者创建多线程环境,用于处理计算密集或耗时的任务,从而提升应用性能和响应速度。
特点:
- 并行处理:Web Workers 允许 JavaScript 运行在与主线程分离的后台线程中,这意味着可以并行处理复杂任务,不会造成界面卡顿。
- 非阻塞:由于运行在独立线程,即使执行复杂的计算,也不会阻塞 UI 更新或用户交互。
- 通信:主线程和 Workers 之间通过传递消息的方式进行数据交互,它们不共享同一内存空间,因此数据在互传过程中是复制的。
WebSocket
WebSocket 提供了一种在客户端和服务器之间建立持久连接的方法,通过这个连接,可以实现双向实时通信。与传统的 HTTP 连接不同,一旦 WebSocket 连接建立,服务器和客户端之间可以随时互发消息,无需重新建立连接。
特点:
- 实时通信:WebSocket 允许服务器主动向客户端发送消息,非常适合需要实时数据更新的应用,如在线游戏、实时交易平台、协作应用等。
- 减少开销:WebSocket 连接建立后,数据帧的传输开销比 HTTP 小得多,因为避免了频繁的握手和头部信息的重复传输。
- 全双工通信:WebSocket 支持全双工通信,客户端和服务器可以同时发送和接收信息。
HTML5 和现代应用
Web Workers 和 WebSocket 都是 HTML5 标准的一部分,是现代 web 应用常用的技术。它们的引入标志着 web 应用的一个重要进步,让 web 应用在性能和用户体验上更接近传统的桌面应用。
- Web Workers 使得开发者可以创建更加平滑和响应快速的用户界面,即使在执行重任务时也不会影响到主 UI 线程。
- WebSocket 则改变了客户端与服务器交互的方式,为实时多用户功能提供了基础,大大增强了应用的互动性。
这两种技术各有用武之地,选择使用哪一种,取决于应用的具体需求:是否需要后台处理能力,或是需要实时的网络通信。
盒模型
标准盒模型:content-box
怪异盒模型:border-box
左右布局
水平垂直居中布局
使用Flex布局
实现方式: 使用CSS的Flex布局可以轻松实现水平垂直居中。通过设置容器的display: flex; justify-content: center; align-items: center;
,即可实现容器内元素的水平垂直居中。
使用绝对定位和transform属性
实现方式: 使用CSS的绝对定位和transform属性也可以实现水平垂直居中。通过设置元素的position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
,即可实现元素的水平垂直居中。
使用表格布局
实现方式: 使用CSS的表格布局也可以实现水平垂直居中。通过设置容器和元素的display: table; display: table-cell; vertical-align: middle; text-align: center;
,即可实现元素的水平垂直居中。
实现方式: 使用Flex布局的margin:auto方法也可以实现水平垂直居中。通过设置容器的display: flex;
,然后在需要居中的元素上添加margin: auto;
,即可实现元素的水平垂直居中。
使用Flex布局的margin:auto方法
flex:1是什么属性的缩写?
移动端适配
viewport
rem
动画:transition、animation
在 CSS 中,动画可以通过两种主要方式实现:transition
和 animation
。这两种方法各有特点,适用于不同的场景。
CSS Transitions
CSS transition
属性用于在 CSS 属性值之间创建平滑的过渡效果。当一个 CSS 属性改变时,transition 会使这个属性值的变化更加平滑和渐进。
使用场景
- 适用于简单的从一种样式过渡到另一种样式的情况。
- 常用于响应用户的交互,如悬停、点击或聚焦。
基本语法
1 | .element { |
- property:指定要过渡的 CSS 属性(如
all
表示所有可过渡的属性)。 - duration:过渡效果花费的时间,通常以秒或毫秒表示。
- timing-function:定义速度曲线,常用的有
linear
、ease
、ease-in
、ease-out
、ease-in-out
。 - delay:延迟时间,即在过渡开始之前等待的时间。
示例
1 | div { |
在此示例中,div
的背景色会在鼠标悬停时在 2 秒内平滑地从蓝色过渡到红色。
2. CSS Animations
CSS animation
属性允许动画从一个样式配置过渡到另一个。与 transition
相比,animation
提供了对动画过程更细致的控制,包括多个关键帧和复杂的动画序列。
使用场景
- 适用于复杂的动画效果,需要在多个阶段或多个样式值之间进行过渡。
- 可以在无需触发事件的情况下自动开始。
基本语法
首先,你需要定义关键帧(@keyframes
):
1 | @keyframes example { |
然后,将动画应用于元素:
1 | div { |
- animation-name:引用
@keyframes
名称。 - animation-duration:动画持续时间。
- animation-timing-function:速度曲线。
- animation-delay:延迟开始的时间。
- animation-iteration-count:动画重复次数。
- animation-direction:动画是否应该反向播放。
- animation-fill-mode:在动画前后如何应用样式。
示例
1 | @keyframes changeSize { |
在这个示例中,div
将不断地在原始大小和放大 50% 之间变化。
总结
- CSS Transitions 最适合用于单一状态变化的简单动画效果。
- CSS Animations 更适合于需要多个状态或更复杂动画效果的场景。
使用这些工具,你可以为网页元素添加视觉上吸引人的动效,提高用户体验。
Javascript
事件代理
冒泡、目标、捕获
对很多个列表事件进行处理然后才需要最后的结果的时候,可以使用事件代理
事件循环
同步任务、宏任务、微任务
浏览器内存
Cookies、LocalStorage 、SessionStorage
这些技术各自有不同的用途和限制:
1. Cookies
Cookies 最初被设计用来存储少量的用户和服务器间的会话数据。它们由服务器发送到用户的浏览器,并且可以设置为在浏览器再次发起请求时被送回服务器。
特点:
- 大小限制:每个 Cookie 的大小限制约为 4KB。
- 数量限制:每个域的 Cookie 数量也有限制,通常为 20-50 个不等,具体取决于浏览器。
- 过期时间:可以设置失效日期,过期后浏览器不再存储。
- 安全性:支持设置 HttpOnly 属性,阻止 JavaScript 通过
document.cookie
访问,增强安全性。还可以设置 Secure 属性,使 Cookie 仅在 HTTPS 连接中被发送。
2. LocalStorage
LocalStorage 是 HTML5 引入的一种存储方式,用于长期存储数据,即使浏览器关闭后数据仍然保存。
特点:
- 存储容量:通常提供至少 5MB 的存储空间。
- 生命周期:数据存储在本地,没有过期时间,除非被用户或应用程序清除。
- 作用域:数据保存在浏览器中,且仅限于同源的页面(即相同协议、域名、端口)访问。
3. SessionStorage
SessionStorage 与 LocalStorage 类似,也是 HTML5 提供的一种机制,但它是为了存储仅在浏览器会话期间需要保持的数据。
特点:
- 存储容量:通常提供至少 5MB 的存储空间。
- 生命周期:数据仅在浏览器会话期间可用(浏览器窗口打开期间)。当用户关闭浏览器窗口或标签页时,数据被清除。
- 作用域:与 LocalStorage 类似,数据的访问也仅限于同源的页面。
比较与选择
- 用途:Cookies 更适合执行需要与服务器交互的身份验证任务;LocalStorage 适合长期存储大量不需要经常更改的数据,如用户偏好设置;SessionStorage 适合在单个会话中暂存数据,如表单填写状态。
- 性能:与 Cookies 相比,LocalStorage 和 SessionStorage 提供了更快的数据访问速度,因为它们的数据不需要每次与服务器通信。
- 安全性:考虑到安全性,应该避免在客户端存储敏感数据,尤其是未加密的情况下。如果必须使用,应该采取措施如加密数据以增强安全性。
根据应用的需要,开发者可以选择最合适的浏览器存储技术来优化用户体验和应用性能。
ES6的理解
数据类型和判断方式
箭头函数,map、set,块级作用域,
数据遍历的方式
基本类型的遍历
基本数据类型(如数字、字符串、布尔值等)通常不需要遍历,因为它们是单个值。不过,字符串类型可以类似于数组进行索引访问和遍历。
字符串遍历
使用传统的
for
循环:1
2
3
4let str = "hello";
for (let i = 0; i < str.length; i++) {
console.log(str[i]);
}使用
for...of
循环:1
2
3
4let str = "hello";
for (let char of str) {
console.log(char);
}
对象遍历
对于 JavaScript 对象(包括数组和更复杂的对象类型),有多种遍历方法:
1. for...in
循环
用于遍历对象的所有可枚举属性(包括继承的属性)。
1
2
3
4const obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
console.log(key, obj[key]);
}注意:使用
for...in
遍历对象时,最好检查属性是否是对象本身的属性(非继承属性):1
2
3
4
5for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key]);
}
}
2. Object.keys()
, Object.values()
, Object.entries()
这些 ES6 方法提供了返回对象属性的数组,可以与 forEach()
或其他迭代方法配合使用:
Object.keys()
返回一个包含对象自身所有可枚举属性键的数组。1
2
3Object.keys(obj).forEach(key => {
console.log(key, obj[key]);
});Object.values()
返回一个包含对象自身所有可枚举属性值的数组。1
2
3Object.values(obj).forEach(value => {
console.log(value);
});Object.entries()
返回一个给定对象自身可枚举属性的键值对数组。1
2
3Object.entries(obj).forEach(([key, value]) => {
console.log(key, value);
});
3. for...of
循环与 Object.entries()
使用
for...of
遍历Object.entries()
返回的键值对数组是遍历对象属性的另一种实用方式:1
2
3for (let [key, value] of Object.entries(obj)) {
console.log(key, value);
}
总结
遍历数据的方法应根据数据的类型和需要操作的数据结构来选择。字符串虽然是基本类型,但可以像数组一样遍历。对象的遍历则可以使用 for...in
循环,但要注意只遍历对象自身的属性。ES6 的 Object.keys()
, Object.values()
, Object.entries()
提供了更现代的遍历对象的方法。选择合适的遍历方式可以使代码更简洁、更有效率。
new的过程
在 JavaScript 中,new
操作符是用来创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型的实例。new
做的事情可以分解为以下几个步骤:
1. 创建一个新对象
当你使用 new
操作符时,JavaScript 首先会创建一个空的对象。
2. 设置原型链
接着,这个新对象的内部 [[Prototype]]
(也就是 __proto__
)会被赋值为构造函数的 prototype
属性。这意味着新对象可以访问构造函数原型上的属性和方法。
3. 绑定 this
关键字
在构造函数内部,this
关键字引用的是刚刚创建的新对象。如果构造函数中有代码修改了 this
,它会修改新对象的属性。
4. 执行构造函数的代码
构造函数内部的代码将会执行。通常情况下,构造函数会初始化对象的属性,也可能会调用其它方法。
5. 返回新对象
在构造函数执行完毕后,如果构造函数返回一个对象,那么这个对象会成为 new
表达式的结果,如果构造函数没有显式返回一个对象,则默认返回 this
,即新创建的对象。
示例:
1 | function Person(name, age) { |
在这个例子中,new Person('John', 30)
做了以下事情:
- 创建了一个新对象。
- 将新对象的
__proto__
指向了Person.prototype
。 - 将
this
绑定到新对象上。 - 执行
Person
函数体内的代码,将 ‘John’ 赋值给this.name
,将 30 赋值给this.age
并定义了this.greet
方法。 - 返回了这个新对象,赋值给了
john
变量。
结论
new
操作符在 JavaScript 中是一个强大的特性,它允许开发者基于构造函数创建复杂的对象。了解 new
的内部工作原理是理解 JavaScript 面向对象编程的一个重要部分。
promise的理解和静态方法
在 JavaScript 中,Promise
是一种用于异步编程的重要构造函数。它允许你组织和管理异步操作更加简洁和易于理解。Promise 代表了一个最终可能完成或失败的操作和其结果值。
一个 Promise
对象代表了一个异步操作的最终完成(或失败)及其结果值。一个 Promise 有以下三种状态:
- Pending(等待态):初始状态,既不是成功,也不是失败状态。
- Fulfilled(已成功):意味着操作成功完成。
- Rejected(已失败):意味着操作失败。
Promise 提供了一种逃避地狱回调(Callback Hell,即多层嵌套的回调函数)的方式,使异步代码更容易写和理解。
使用 Promise
创建一个 Promise并模拟异步操作:
1 | let promise = new Promise(function(resolve, reject) { |
使用 Promise:
1 | promise.then( |
Promise 的静态方法
Promise 提供了几个静态方法,这些方法有助于控制多个异步操作。
Promise.resolve(value)
- 返回一个状态由给定值解析后的 Promise 对象。如果该值是 thenable(即有
then
方法),返回的 Promise 将“跟随”这个 thenable 的状态;否则返回的 Promise 将以此值完成。
- 返回一个状态由给定值解析后的 Promise 对象。如果该值是 thenable(即有
Promise.reject(reason)
- 返回一个状态为拒绝的 Promise 对象。
Promise.all(iterable)
- 此方法通常用于处理多个 Promise 对象的情况。它返回一个新的 Promise 实例,该实例在 iterable 参数内的所有 Promise 都成功完成时完成。如果任意一个 Promise 失败,返回的 Promise 便立即失败。
1
2
3
4
5Promise.all([Promise1, Promise2]).then(function(results) {
// 所有 Promise 都成功
}, function(reason) {
// 有一个 Promise 失败
});Promise.race(iterable)
- 当 iterable 参数里的任意一个子 Promise 被成功或失败后,父 Promise 立即也会用子 Promise 的成功返回值或失败详情作为参数调用父 Promise 绑定的相应句柄,并返回该 Promise 对象。
Promise.allSettled(iterable)
- 返回一个在所有给定的 Promise 已被决议或拒绝后的 Promise,并带有一个对象数组,每个对象表示对应的 Promise 结果。
使用这些方法可以有效地组合和管理多个异步操作,提高代码的可读性和易维护性。
结论
Promise 是现代 JavaScript 中处理异步操作的核心组件之一。通过使用 Promise 和其静态方法,你可以写出更清晰、更可维护的异步代码。理解和掌握 Promise 是成为一名效率高、代码质量好的 JavaScript 开发者的关键步骤。
跨域
CORS(Cross-Origin Resource Sharing): CORS是一种跨域资源共享的标准,通过服务端设置响应头中的Access-Control-Allow-Origin字段来允许跨域请求。服务端在收到跨域请求时,检查Origin字段,如果请求的源在白名单内,则在响应头中添加Access-Control-Allow-Origin字段,并设置为允许的源,从而允许跨域请求。
JSONP(JSON with Padding): JSONP是一种利用script标签的src属性没有跨域限制的特性来实现跨域请求的技术。通过动态创建script标签,将跨域请求的数据封装在一个回调函数中,然后由服务端返回,并执行回调函数,从而实现跨域数据的获取。
代理服务器: 在开发环境中,可以通过配置代理服务器来实现跨域请求。代理服务器接收前端请求,然后在后端发起真正的请求,获取数据后再返回给前端,由于请求是由后端发起的,所以不存在跨域问题。
iframe跨域通信: 使用iframe标签可以实现跨域通信。父页面和iframe页面属于不同的源,但是它们之间可以通过postMessage方法来进行跨域通信,实现数据的传递和交互。
WebSocket协议: WebSocket是一种全双工通信协议,能够在不受同源策略限制的情况下实现跨域通信。通过WebSocket协议,客户端和服务端可以建立持久的连接,实现实时数据的传输和交互。
nginx
postmessage
垃圾回收
标记清除、引用计数
Typescript
interface、type和泛型
在 TypeScript 中,interface
和 type
都是定义类型的重要工具,它们在很多情况下可以互换使用,但也有一些关键的区别。了解这些区别有助于你在实际开发中更好地选择适合的语法。
相似点
- 定义形状:
interface
和type
都可以用来定义对象的形状,即指定对象应该有哪些属性以及属性的类型。 - 扩展其他类型:两者都可以扩展其他类型,
interface
使用extends
关键字,而type
使用交叉类型(&
)。
不同点
扩展机制:
- interface:可以声明多次,并且会被自动合并。这允许你在不同的地方扩展同一个接口。
- type:不能声明多次(即一旦被声明,不能再被重新声明来添加新的属性),但它可以通过交叉类型来扩展其他类型或别名。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// Interface 自动合并
interface User {
name: string;
}
interface User {
age: number;
}
// User 现在有 name 和 age 属性
// Type 不可以重复声明,但可以通过 & 扩展
type Animal = {
species: string;
}
type Bear = Animal & {
honey: boolean;
}使用
type
可以声明基本类型别名,联合类型,元组等:type
不仅仅限于对象类型。它可以用来声明几乎所有类型,包括基本类型别名、联合类型、元组等。interface
主要用于声明对象类型或函数类型。
1
2type StringOrNumber = string | number;
type TupleType = [string, number];实现细节:
- interface:更加面向对象的编程风格,支持实现(implements)和扩展(extends)。
- type:更灵活,支持使用联合、元组等更复杂的类型结构。
1
2
3
4
5
6
7
8
9
10
11
12interface IAnimal {
species: string;
}
class Dog implements IAnimal {
species = "Canis";
}
type Point = {
x: number;
y: number;
};
let drawPoint = (point: Point) => { /*...*/ };扩展/合并限制:
- interface:允许开放结束的扩展,因为你可以在程序的任何地方添加到现有接口。
- type:定义是封闭的,一次声明定义全部完成。
选择使用哪一个?
- 使用
interface
当你需要一个对象类型或者在面向对象风格的代码库中(如当与类一起使用时)。 - 使用
type
当你需要利用联合类型或元组类型等复杂类型,或者需要对类型进行条件选择。
在实践中,如果两者都可以满足需求,推荐优先选择 interface
,因为它的扩展性更好,更适合在大型项目中使用,能够提供更好的代码组织和更清晰的错误信息。然而,type
的灵活性在处理复杂的类型组合时是无可替代的。
对泛型属性约束:
1 | interface Lengthwise { |
Vue
2和3双向数据绑定的区别理解
object.propert
生命周期
watch和computed
watch:不缓存,实时更新
computed:缓存
v-if、v-show
if是条件渲染
show是通过css的display
组件通信
props和$emit,provide和inject,Event Bus,vuex,
vuex
state、getters、mutations、actions、modules
1. State
State 是 Vuex 存储的基础,是存储在 Vuex 中的数据。它是单一状态树 —— 所有组件的状态都被集中到一个共享的对象中。
1 | const store = new Vuex.Store({ |
在组件内部,你可以通过 this.$store.state
访问 Vuex state。
2. Getters
Getters 类似于 Vue 的计算属性,主要用于基于 Vuex store 的 state 计算出新的状态。Getters 可以被多个组件复用,且当它依赖的 state 变化时,会自动重新计算。
1 | const store = new Vuex.Store({ |
在组件内部,可以通过 this.$store.getters.doneTodos
访问到这个 getter。
3. Mutations
Mutations 是改变 Vuex store 中 state 的唯一方法。Mutation 必须是同步函数,这样能够确保每次状态变化都能被调试工具追踪到。
1 | const store = new Vuex.Store({ |
在组件中使用 this.$store.commit('increment')
来触发 mutation。
4. Actions
Actions 类似于 mutations,但是不同于 mutations 的是,actions 可以包含任意异步操作。Actions 提交的是 mutation,而不是直接变更状态。Actions 可以包含任意异步操作。
1 | const store = new Vuex.Store({ |
在组件中使用 this.$store.dispatch('incrementAsync')
来触发 action。
5. Modules
当 Vuex 应用变得非常复杂时,store 对象可能变得非常臃肿。Modules 可以将 store 分割成模块,每个模块拥有自己的 state、mutations、actions、getters,甚至是嵌套子模块。
1 | const moduleA = { |
每个 Vuex 模块的 state 会被合并到 Vuex 的全局 state 中,并根据模块注册的路径分割命名空间。
这些核心概念共同定义了 Vuex 的结构和机制,使得状态管理更加清晰、高效,并且更易于维护和扩展。
路由
history和哈希模式
React
Webpack
loader和plugin的区别
Loader
Loader 用于转换特定类型的模块或文件。Webpack 本身只理解 JavaScript,Loader 可以让 Webpack 有能力去处理其他类型的文件,并将它们转换为有效的模块,以供应用程序使用以及被添加到依赖图中。
主要特点:
- 文件转换:Loader 可以把文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。Loader 甚至允许你直接在 JavaScript 模块中 import CSS文件。
- 链式调用:Loader 可以链式调用。意味着可以应用多个 loader,它们会按顺序从右到左(或从下到上)应用。
- 每个文件单独处理:Webpack 为每个文件根据配置的 loader 规则进行处理。
示例:使用 css-loader
和 style-loader
处理 CSS 文件。
1 | module: { |
Plugin
Plugin 用于扩展 Webpack 的功能。它们直接作用于整个构建过程,可以执行范围更广的任务,如打包优化、资源管理和环境变量注入等。
主要特点:
- 扩展性强:插件可以用于执行几乎任何任务,包括打包优化、定义环境变量、压缩、等等。
- 直接作用于整个构建流程:Plugin 可以在构建的任何阶段执行操作,提供了完整的构建流程控制。
- 自定义功能:开发者可以编写自己的 Webpack 插件,以实现更具体的需求。
示例:使用 HtmlWebpackPlugin
,它可以生成一个 HTML 文件,或者利用模板生成 HTML 文件,并自动注入生成的资源。
1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); |
区别总结
- Loader 主要用于对模块的源代码进行转换。Loader 可以使你在 import 或”加载”模块时预处理文件。因此,loader 类似于“任务”是文件级别的处理工具。
- Plugin 影响的是整个构建流程。Plugin 可以用于执行广泛的任务,比如打包优化、资源管理和环境变量注入等,影响构建结果。
在实际应用中,理解 Loader 和 Plugin 的工作方式及其区别,可以帮助你更好地控制 Webpack 的行为,优化构建过程,提高应用性能。
Vite和webpack的区别
在现代前端开发中,构建工具扮演着至关重要的角色,它们帮助开发者处理模块化、编译、打包、优化等任务。Webpack
是最流行的构建工具之一,但除此之外还有许多其他的工具,如 Vite
、Parcel
、Rollup
等。每种工具都有其特定的用途和优势。下面我将简要介绍这些工具,并突出 Vite
的优势。
Webpack
Webpack 是一个模块打包器(bundler)。它适用于大型复杂的网页应用,通过 loader 和 plugins 提供了强大的整合能力。
特点:
- 处理依赖:可以将几乎任何类型的文件作为模块处理。
- 丰富的插件系统:通过插件,可以扩展 webpack 的功能。
- DevServer:提供一个简单的 web 服务器和实现热重载(HMR)。
- Splitting:代码分割支持,有助于优化加载时间。
Vite
Vite 是一种新兴的前端构建工具,由 Vue.js 的创作者开发。它利用现代浏览器的原生 ES 模块导入(ESM)功能来提供快速的开发环境启动。
优势:
- 快速的冷启动:不需要打包操作,可以快速启动开发环境。
- 即时的模块热更新(HMR):相比传统的打包系统,HMR 更快,因为没有打包过程。
- 按需编译:只有当请求时才编译模块,没有额外的开销。
- 内置的优化:构建生产环境时,Vite 使用 Rollup 打包,有效利用了 Rollup 的高效代码分割和动态导入功能。
Parcel
Parcel 是另一种构建工具,以其零配置为特色,非常适合简单的项目和快速原型开发。
特点:
- 零配置:默认配置处理广泛的开发场景。
- 自动转换:支持许多文件类型,自动安装需要的转换器。
Rollup
Rollup 是专注于 JavaScript 库的打包,强调的是输出更少的代码和更有效的摇树优化(Tree-shaking)。
特点:
- 高效的 Tree-shaking:减少冗余代码,输出更小的文件。
- 适合库的打包:输出的文件更适合作为库分发。
改动配置
对于这些构建工具,实际开发中通常需要修改其默认配置以适应特定项目的需求。例如:
- Webpack:可能需要添加 loader 处理新的文件类型,或修改 plugins 来优化打包结果。
- Vite:可能需要配置基础公共路径(base),或者插件支持更多框架和文件处理。
- Parcel & Rollup:虽然 Parcel 通常不需要配置,但有时可能需要指定打包方式或代理配置;Rollup 则可能需要配置插件来支持 CommonJS 模块转换或其他语言特性。
结论
选择哪个构建工具取决于项目的需求、团队的熟悉度以及期望从工具中获得的支持。Vite 的优势在于其极速的开发启动时间和按需编译,特别适合用在现代 web 开发环境中。而传统的工具如 Webpack 仍然在功能丰富性和适用于大型复杂项目方面占有一席之地。
vue的template如何进行转化成dom节点的?
给定许多个button如何设置只选择一个节点,当我点击一个按钮的时候,上一个按钮的事件消失,这个按钮触发点击事件
如何虚拟优化
如何查看窗口的大小
如何获取div窗口的大小
ts:interface、type,enum的转化是什么?
ES6知道什么
new
箭头函数
原型和原型链
promise的状态
事件循环的理解
$nexttick
diff算法
watch和computed
数据的双向绑定理解跨域
template渲染的过程?
vue3的setup,ref和reactive
js的垃圾回收,v8的垃圾回收
JavaScript 的 V8 引擎是谷歌开发的开源 JavaScript 引擎,主要用于 Chrome 浏览器和 Node.js 环境中。V8 引擎的内存回收机制是理解其性能管理的关键部分。这种机制帮助确保非活动的或不再需要的对象能够被有效地清除,从而释放内存资源。以下是 V8 内存回收的几个关键概念:
垃圾收集器的工作原理:
- V8 使用了一种名为“分代式垃圾回收”(Generational Garbage Collection)的方法。这种方法基于一个观察:大多数对象在内存中存活的时间很短。
- V8 将内存分为两个主要区域:新生代(New Space)和老生代(Old Space)。新生代存放生命周期短的对象,而老生代则存放生命周期长或从新生代中晋升的对象。
新生代的垃圾回收:
- 新生代区域相对较小,通常只占总内存的一小部分。V8 使用 Scavenge 算法来进行新生代的垃圾回收,这是一种简单高效的复制算法。
- 当进行新生代垃圾回收时,V8 把新生代内存分为两半:活动半区和闲置半区。活动的对象被复制到闲置半区,而非活动的对象则被释放。
老生代的垃圾回收:
- 老生代的垃圾回收更复杂,因为这些对象通常更大,且数量也更多。
- V8 主要使用 Mark-Sweep(标记-清除)和 Mark-Compact(标记-整理)算法来进行老生代的垃圾回收。标记-清除算法用于标记活动的对象并清除未标记的对象,而标记-整理算法在此基础上还会移动对象,以减少内存碎片。
增量标记:
- 为了避免长时间的垃圾回收导致的应用停顿,V8 实现了增量标记(Incremental Marking)。在这种模式下,垃圾回收的标记过程会被分割成多个小部分,穿插在 JavaScript 应用逻辑的执行中。
延迟清理和并行回收:
- V8 也引入了延迟清理(Lazy Sweeping)和并行回收,这样可以在多个后台线程中进行垃圾回收,进一步减少对主线程的影响。
了解这些内存回收的机制可以帮助开发者优化 JavaScript 应用的内存使用,避免内存泄漏,并提高应用的性能和响应速度。在编写高性能的应用时,理解和适应 V8 的内存回收策略是非常重要的。
webpack和vite
快排
链表
如何不使用递归实现斐波那契数:迭代
内存–分配、使用、回收-引用计数、标记清除–标记清楚、标记整理、增量标记
理解计算机内存涉及多个层面,包括硬件级别的物理内存和操作系统以及应用程序(如浏览器)在软件级别对内存的管理。下面详细解释这些层面。
物理内存(硬件)
物理内存,通常称为随机存取存储器(RAM),是计算机的主要存储媒介之一。它用于临时存储正在运行的程序和当前使用的数据。内存是易失性的,意味着当计算机关闭电源时,存储在内存中的数据会丢失。
操作系统的内存管理
操作系统的内存管理是确保有效、高效利用物理内存的关键机制。它包括以下几个方面:
内存分配:
操作系统负责为每个运行的程序分配内存空间,操作系统需要在多个程序之间分配有限的物理内存。
静态分配:在程序运行前就分配固定的内存空间,这种方式简单,但不够灵活。
动态分配:允许程序在运行时根据需要申请和释放内存。动态分配方法包括:
- 堆分配:由操作系统维护一块内存区域(堆),程序可以动态地从中申请和释放内存。
- 栈分配:操作系统为每个线程维护一个栈,用于存储局部变量和函数调用信息。栈的大小可能是固定的,或者有限度地动态增长。
静态分配和动态分配:这包括为程序代码、数据和堆栈分配内存。
虚拟内存:
虚拟内存是一种内存管理技术,它通过使用硬盘空间来扩展物理内存。操作系统创建一个文件(通常称为交换文件或分页文件),用于存储临时从内存中移出的数据。
虚拟内存是现代操作系统用来扩展可用物理内存的技术。它允许程序使用比实际物理内存更多的地址空间,通过以下机制实现:
- 分页:将虚拟内存分成固定大小的页,独立于物理内存的大小。操作系统维护一个页表,将虚拟页映射到物理页。
- 分段:将内存分为逻辑上的段,如代码段、数据段等。分段可以基于程序的逻辑结构优化内存使用。
操作系统通常通过分页机制管理内存。系统内存被分割成固定大小的页。分页允许操作系统只加载程序需要的内存页,减少内存消耗。分页是虚拟内存实现的核心,具体包括:
页表:维护虚拟地址到物理地址的映射。每个进程都有自己的页表。
TLB(Translation Lookaside Buffer):是一种缓存,用于加速虚拟地址到物理地址的转换过程。
页替换算法:当内存满时,如何选择一个页来替换,常见算法有LRU(最近最少使用)、FIFO(先进先出)和OPT(最优替换)。
分段是另一种内存管理技术,它允许程序和数据被分割成逻辑上的段。
内存保护:
作系统通过内存保护确保一个程序不会干扰其他程序的内存空间。这通过以下方式实现:
- 访问控制:页表中的每一项可以包含访问权限标记,如可读、可写和可执行。
- 基址和界限寄存器:用于检查每个内存访问是否在允许的地址范围内。
内存碎片
内存碎片分为两类:
内部碎片:分配给程序的内存块比需要的大,剩下的部分未被使用。
外部碎片:随着时间推移,内存的释放和分配导致空闲内存块之间散布着未使用的小块内存。
- 内存压缩
内存压缩是解决内存碎片问题的一种方法,通过移动已分配的内存块,来整合连续的空闲内存块,从而优化内存的使用。
- 大页支持
一些操作系统提供大页支持(如HugePages in Linux),允许应用程序使用比标准更大的页尺寸。这减少了页表项的数量,降低了TLB miss的概率,提高了内存访问的效率。
浏览器的内存管理
浏览器作为应用程序,也需要进行内存管理,以确保快速响应和有效利用系统资源。浏览器的内存管理包括:
JavaScript 内存管理:
- JavaScript 在浏览器中通过自动垃圾回收机制来管理内存。垃圾回收器定期检查那些不再被应用程序代码引用的对象,并释放它们的内存。
内存隔离:
- 现代浏览器采用进程和线程的隔离策略,确保一个标签页或插件崩溃不会影响到其他标签页。这意味着每个标签页都可以有自己独立的内存空间。
内存限制:
- 浏览器对每个标签页或插件的内存使用进行限制,以防单个网页消耗过多内存影响整个系统的性能。
性能监控和优化:
- 开发者可以使用浏览器提供的开发者工具(如 Chrome 的 DevTools)来监控网页的内存使用情况,识别内存泄漏等问题,并进行优化。
总结
计算机内存的管理是一个复杂的过程,涉及硬件资源的直接管理和通过操作系统及应用程序(如浏览器)的高级管理。有效的内存管理策略可以提高计算机的性能和响应速度,确保系统的稳定运行。对于开发者来说,理解和优化内存的使用是提高应用性能的关键。
虚拟内存是一种计算机系统内存管理的技术,它使得应用程序认为它拥有连续的可用内存,而实际上这些内存可能会被分散存储在物理内存和磁盘存储(通常是硬盘上的交换空间或分页文件)中。虚拟内存的引入主要是为了解决物理内存容量有限的问题,它通过有效地利用磁盘空间来扩展可用内存,从而提供更大的内存空间给程序使用。
虚拟内存的工作原理
内存分页:虚拟内存将内存分成称为“页”的小块数据。操作系统维护一个页表来记录虚拟页和物理页之间的映射关系。
页替换算法:当程序访问的数据不在物理内存中时,会发生“缺页中断”(Page Fault)。操作系统选择一个物理内存中的页来替换,将需要的数据从磁盘读入物理内存。选择哪个页来替换,是由页替换算法决定的,如最近最少使用(LRU)算法、先进先出(FIFO)算法等。
交换空间:交换空间是硬盘上预留的一部分,用于存放被换出的页。这个空间可以是一个专门的交换分区或一个文件。
地址转换:每当应用程序访问内存时,它使用的是虚拟地址。CPU中的内存管理单元(MMU)将这些虚拟地址转换为物理地址。如果所需的虚拟页在物理内存中,转换就很快完成;如果不在,操作系统将从磁盘中调入数据,更新页表,然后继续地址转换。
虚拟内存的优点
- 扩展内存:允许程序使用比实际物理内存更多的内存。
- 安全和隔离:每个程序都有自己的虚拟地址空间,这意味着一个程序无法访问或干扰另一个程序的数据。
- 内存管理简化:程序员可以认为每个程序都有一大块连续的内存,而无需担心物理内存的分配问题。
虚拟内存的缺点
- 性能开销:虚拟内存的使用会引入额外的性能开销,因为涉及到磁盘I/O操作和地址转换。特别是当系统频繁进行页交换时,性能会显著下降,这种现象称为“抖动”(Thrashing)。
- 管理复杂性:虽然对程序员来说内存管理简化了,但操作系统需要处理更复杂的内存管理任务,如页替换、缺页中断处理等。
虚拟内存是现代计算机系统中不可或缺的一部分,尤其在多任务操作系统和需要运行大型应用程序的环境中尤为重要。
在现代浏览器中,是否将每个标签页作为一个独立进程处理取决于浏览器的架构。Google Chrome 是最早采用每个标签页一个进程策略的主流浏览器之一,这种设计被称为“多进程架构”。
多进程架构的优点
- 隔离性:每个标签页在各自的进程中运行,这意味着一个标签页的崩溃不会影响到其他标签页。这提高了浏览器的稳定性和安全性。
- 安全性:通过进程隔离,可以更有效地实施安全策略,防止恶意网站或脚本影响其他标签页或整个系统。
- 响应性:多进程模型可以利用现代多核处理器的能力,通过并行处理提高响应速度。
多进程架构的缺点
- 内存使用:每个进程都会有一定的内存开销,因此多进程架构的浏览器通常会消耗更多的内存。
- 资源管理:需要更复杂的进程管理和通信机制,特别是在进程之间共享和同步数据时。
浏览器的实际实现
- Google Chrome:采用了一种名为“每个站点一个进程”的策略,即通常情况下每个标签页运行在单独的进程中,但来自同一站点的标签页可能共享一个进程。
- Mozilla Firefox:Firefox 采用了“Electrolysis”(简称e10s)架构,它同样支持多进程,但其默认策略并非为每个标签页分配独立进程。Firefox可以配置为运行多个内容进程,这些内容进程中可以承载一个或多个标签页。
- Microsoft Edge:最新版的Edge基于Chromium,与Chrome类似,采用多进程架构。
- Safari:也支持多进程架构,但其进程管理的具体策略可能与Chrome和Firefox有所不同。
总的来说,现代浏览器趋向于使用多进程架构以提高安全性和稳定性,尽管这可能导致更高的内存消耗。每个浏览器的具体实现可能会根据性能、内存使用和安全性之间的权衡做出不同的设计选择。
对于JavaScript及其在浏览器中的内存管理和存储机制的理解,可以从几个方面详细进行分析和总结:
1. 浏览器缓存
强缓存
强缓存不会向服务器发送请求,直接从缓存中读取资源。可以通过设置HTTP响应头Cache-Control
和Expires
来控制。Cache-Control: max-age=3600
表示资源可以被缓存,并且是有效的,直到3600秒后。Expires
则提供一个具体的时间点,告诉浏览器资源有效期。
协商缓存
当强缓存失效后,浏览器会向服务器发送请求,通过Last-Modified
/If-Modified-Since
或ETag
/If-None-Match
来检查资源是否更新。如果资源未更新,服务器返回304状态码,告诉浏览器继续使用缓存。
2. 浏览器本地存储
Cookie
Cookie通常用于存储用户身份信息和跟踪会话。大小限制为每个域约4KB,浏览器每次向同一域发送请求时都会携带Cookie,这可能影响发送请求的性能。
localStorage
提供更大的存储空间(最多5MB),数据在浏览器关闭后依然存在,适合存储大量不经常变动的数据。不参与与服务器的通信。
sessionStorage
与localStorage类似,但其存储的数据仅在浏览器窗口或标签页打开期间存在,关闭窗口或标签页后数据会被清除。同样最大存储量为5MB。
3. JavaScript内存管理
JavaScript的内存管理是自动的,主要通过垃圾回收机制来实现。
垃圾回收
- 标记清除:这是最常用的垃圾回收算法。当变量进入环境时标记为“进入环境”,当变量离开环境时标记为“离开环境”。标记为“离开环境”的变量将被视为垃圾,并被收集和回收。
- 引用计数:这种方法跟踪每个值被引用的次数。当引用次数变为0时,值会被标记为可回收。然而,引用计数方法不能解决循环引用的问题。
4. V8引擎的垃圾回收
V8引擎用于Google Chrome浏览器,使用了更复杂的垃圾回收策略,包括新生代和老生代的分代回收:
- 新生代:from、to存放生命周期短的小对象,使用Scavenge算法,其中主要用到复制方式,将活动对象从一个区域复制到另一个区域。
- 老生代:存放生命周期长或常驻内存的对象,使用标记-清除和标记-整理算法来减少碎片。
结论
JavaScript的内存管理和浏览器存储是现代web开发中不可或缺的一部分,了解这些机制可以帮助开发者更好地优化其应用性能和用户体验。通过合理使用各种存储方式,并理解垃圾回收的工作原理,可以有效地管理应用的内存使用,避免内存泄漏和过度占用资源的问题。
关于JavaScript和浏览器存储及内存管理,还有一些其他重要的方面可以补充,这些包括内存泄漏的识别与预防,Web Workers的使用,以及内存性能监控工具的应用。
5. 内存泄漏的识别与预防
内存泄漏在JavaScript中是一种常见的性能问题,通常由未被垃圾回收机制清除的持续占用内存的对象引起。一些常见的内存泄漏原因包括:
- 全局变量:无意中创建的全局变量可能不会被回收。
- 定时器和回调函数:未被清除的定时器或回调函数可以持续占用内存。
- DOM 引用:被删除的DOM元素如果仍被JavaScript引用,也会导致内存泄漏。
- 闭包:不恰当的闭包使用可能导致父函数作用域内的变量无法被释放。
预防内存泄漏的策略包括:
- 限制全局变量的使用:使用严格模式(
use strict
)来避免意外创建全局变量。 - 及时清理:定时器应当在不需要时清除,事件监听器在元素被移除前应当被移除。
- 使用弱引用:如
WeakMap
和WeakSet
允许其内容在没有其他引用时被垃圾回收。
6. Web Workers的使用
Web Workers提供了一种将执行过程从主线程中分离出来的方法,使得可以在后台线程中执行复杂计算,避免阻塞UI。这对于提升大数据处理或复杂计算的应用的响应性非常有帮助。使用Web Workers时,需要注意数据是通过复制而非共享方式传递,这意味着原始数据和Worker之间不会相互影响。
7. 内存性能监控工具
为了有效地监控和分析JavaScript的内存使用情况,可以使用以下工具:
- Chrome DevTools:提供了强大的内存分析工具,包括堆快照(Heap Snapshot)、记录堆分配(Record Heap Allocations)和时间线(Timeline)视图等,可以帮助开发者识别内存泄漏和优化性能。
- Performance API:这是一个浏览器API,允许收集和分析在应用运行时的性能数据。
结论
通过以上的详细介绍,我们可以更全面地理解JavaScript的内存管理、浏览器缓存和本地存储机制。适当地应用这些知识和工具,可以显著提高Web应用的性能和用户体验。同时,防止内存泄漏和合理利用多线程计算资源也是提升现代Web应用质量的重要策略。
当谈到在Web开发中监控JavaScript性能的实例数据,我们通常关注以下几个关键指标:
1. 时间线数据 (Timeline Data)
在Chrome DevTools中,时间线工具可以记录和展示一个页面在加载和运行过程中的详细活动。这包括了JavaScript执行时间、样式计算、布局、绘制等。一个示例性的时间线数据可能如下:
- 加载事件:页面开始加载到onLoad事件触发的时间。
- DOMContentLoaded:DOM解析完成的时间。
- 脚本执行时间:单个脚本或脚本总和的执行时间。
- 渲染时间:页面进行渲染的时间点和持续时间。
- 帧速率:动画或页面滚动的帧速率。
2. 堆快照 (Heap Snapshot)–控制台的memory
heap snapshot
堆快照显示了在某一时刻JavaScript的所有对象及其关系的内存分布情况。这可以帮助开发者发现内存泄漏。示例数据可能包括:
- 对象数:当前内存中存在的对象总数。
- 分配大小:每种对象类型占用的内存大小。
- 保留大小:对象及其依赖对象总共占用的内存大小。
- 垃圾可回收:已被标记为可回收但尚未清理的内存。
3. 性能记录 (Performance Record)
使用Chrome DevTools的Performance panel可以记录一段时间内页面的运行情况,包括JS执行、样式计算、布局、绘制等。性能记录的数据可能包括:
- 函数调用:函数调用的次数和耗时。
- **FPS (帧率)**:在记录期间的帧率,理想状态下接近60fps。
- CPU利用率:JavaScript执行对CPU的占用情况。
4. 网络请求 (Network Requests)
监控网络请求可以帮助识别加载资源的瓶颈。性能监控数据可能包括:
- 请求时间:每个资源从请求到完成的时间。
- 资源大小:下载的每个文件的大小。
- 缓存状态:资源是否被缓存。
示例:性能监控报告
假设有一个页面加载分析报告,内容可能如下:
- 总加载时间:5秒
- 脚本执行总时间:1.2秒
- 最大DOM深度:22层
- 重绘次数:15次
- 内存使用峰值:150MB
- 网络请求:50个请求,总共传输数据1MB
结论
这些性能监控数据帮助开发者深入理解Web应用的行为和性能瓶颈。通过定期检查这些指标,可以持续优化应用,改善用户体验。在现代Web开发中,有效地利用这些工具和数据是至关重要的。
进程和线程的区别
kmp算法
微前端路由拦截
拦截的浏览器路由API
History API:
history.pushState()
history.replaceState()
history.go()
history.back()
history.forward()
这些方法是HTML5 History API的一部分,用于在不重新加载页面的情况下操作浏览器的历史记录和路由状态。
Hash Changes:
- 监听
hashchange
事件
对于基于哈希的路由(通常在旧的单页应用中使用),监听
hashchange
事件是必要的,以便在URL的哈希部分变化时触发路由逻辑。- 监听
如何进行路由拦截
在微前端架构中,通常会有一个中心化的路由管理器或框架负责处理这些拦截。以下是一些常见的实现方法和技术:
使用 single-spa:
- Single-spa 提供了一个路由前缀的概念,通过配置哪些前缀或路径激活特定的子应用。它通过监听浏览器的
popstate
事件和拦截pushState
和replaceState
方法来实现。
示例代码如下:
1
2
3
4
5
6window.addEventListener('popstate', handlePopState);
const originalPushState = history.pushState;
history.pushState = function() {
originalPushState.apply(this, arguments);
handleRouteChange();
};- Single-spa 提供了一个路由前缀的概念,通过配置哪些前缀或路径激活特定的子应用。它通过监听浏览器的
手动拦截 History API:
- 在主应用的入口文件中,可以手动覆盖
history.pushState
和history.replaceState
方法,以便在路由改变时执行额外的逻辑,比如加载或卸载子应用。
1
2
3
4
5
6
7
8
9
10
11
12
13const originalPushState = history.pushState;
history.pushState = function(state, title, url) {
originalPushState.call(history, state, title, url);
dispatchRouteChange();
};
window.addEventListener('popstate', function(event) {
dispatchRouteChange();
});
function dispatchRouteChange() {
// 在这里处理子应用的加载和卸载
}- 在主应用的入口文件中,可以手动覆盖
框架特定的路由守卫:
- 对于使用 Vue 或 React 等框架的应用,可以利用框架提供的路由守卫功能(如 Vue 的
beforeEach
路由守卫或 React Router 的history.listen
)。
在 Vue 中使用
vue-router
的示例:1
2
3
4router.beforeEach((to, from, next) => {
// 在这里根据路由切换逻辑加载或卸载子应用
next();
});- 对于使用 Vue 或 React 等框架的应用,可以利用框架提供的路由守卫功能(如 Vue 的
在 React 中使用 React Router 的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13import { useHistory } from 'react-router-dom';
function useRouteChange() {
const history = useHistory();
useEffect(() => {
const unlisten = history.listen((location, action) => {
// 在这里处理路由变化
});
return () => {
unlisten();
};
}, []);
}
结论
微前端的路由拦截主要是通过拦截和管理浏览器的 History API 实现的,确保在主应用和子应用之间正确地管理和协调路由状态。这要求在设计微前端架构时,要有一个清晰的路由管理策略,以保证应用的整体性和用户体验的流
畅性。