介绍自己

吃苦耐劳,态度认真,有较强的抗压能力,较强逻辑思维能力,较好分析问题能力、学习能力。

性格开朗,善于沟通,具有团队合作精神,学习能力较强,热爱技术,有钻研精神,具备较强的抗压能力。

实习描述

同程

负责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 主应用进行交互以实现模块的正确加载和卸载。这些生命周期钩子包括:bootstrapmountunmount,以及可选的 update(如果你需要处理主应用传递的 props)。下面是如何在 Vue 2 子应用中实现这些生命周期钩子的一个示例:

  1. bootstrap

    • 这个阶段主要用于全局级别的初始化工作,如设置日志、初始化服务等,通常只运行一次。
  2. mount

    • 这个阶段用于将 Vue 实例挂载到 DOM 上,通常在这里初始化和渲染 Vue 组件。
  3. unmount

    • 这个阶段用于清理、卸载 Vue 实例,避免内存泄露,确保所有事件监听器和依赖都被适当移除。
  4. update

    • 这个阶段用于处理新的 props 或状态更新,是可选的,依赖于你的应用是否需要响应外部变化。

示例代码

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
import Vue from 'vue';
import App from './App.vue';

// Vue 实例
let app = null;

// `bootstrap` 只会在初始化时调用一次
export async function bootstrap() {
console.log('Vue app bootstraped');
}

// 应用每次进入都调用
export async function mount(props) {
app = new Vue({
render: h => h(App),
// 传递 props,事件,等到 Vue 实例
...props
}).$mount(props.container ? props.container.querySelector('#app') : '#app');
}

// 应用每次卸载都调用
export async function unmount() {
app.$destroy();
app.$el.innerHTML = '';
app = null;
}

// 可选:更新应用数据
export async function update(props) {
// 在这里进行更新操作,例如重渲染、更新 props
console.log('update props', props);
}

实现注意事项

  • 确保在 mount 函数中正确地挂载 Vue 实例到由 Qiankun 提供的 DOM 容器中。通常这是通过 props.container.querySelector('#app') 实现的,其中 #app 是容器内的一个 DOM 元素的 ID。
  • unmount 函数中,彻底销毁 Vue 实例和清理 DOM 是至关重要的,以防止内存泄漏或其他副作用。
  • 如果实现了 update 函数,确保你的应用可以响应外部状态的变化,例如,接收来自主应用的数据更新。

通过这样的设置,Vue 2 子应用可以被 Qiankun 框架有效地管理,实现真正意义上的微前端架构,其中各个子应用可以独立开发、部署和更新,而不会影响到主应用或其他子应用的运行。

路由鉴权

您提供的 JavaScript 函数 hasPermission 是用于在基于路由的系统中检查用户是否拥有访问某个特定路由的权限。这个函数通过分析路由对象和用户的权限列表来决定是否允许访问某个路由。下面我将详细解释这个函数的逻辑和作用:

函数参数

  • to: 这个参数表示即将访问的路由对象。通常,这个对象包含了路由的元数据(如名称、查询参数等)。
  • hasPermissionList: 这是一个数组,包含了用户拥有的权限名称。

函数逻辑

  1. 检查元数据是否存在

    • 函数首先检查 to.meta 是否存在,to.meta 通常包含与路由相关的元数据,比如权限相关信息。
  2. 特定路由的权限检查

    • 函数中有特定的逻辑来处理名为 workloadbugreport 的路由。对于这些路由,它进一步检查查询参数 to.query.type,来决定用户是否有权限访问不同类型的统计信息。
    • 对于 workload 路由:
      • 如果 typedept(部门),则检查用户权限列表中是否包含 '部门工作量统计'
      • 如果 typemember(员工),则检查是否包含 '员工工作量统计'
      • 如果 typeproject(项目集),则检查是否包含 '项目集工作量统计'
    • 对于 bugreport 路由:
      • 类似地,基于 type(部门、员工或项目集),检查相应的 '部门缺陷量统计''员工缺陷量统计''项目集缺陷量统计'
  3. 通用权限检查

    • 如果路由名称不是 workloadbugreport,则检查 hasPermissionList 是否包含 to.meta.title。如果不包含,则表示没有权限。
  4. 默认行为

    • 如果没有 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
2
3
for(let i = 0; i < 5; i++) {
// ......
}

for of:ES6中的方法,不会遍历数组的私有属性,不能遍历对象,可以遍历字符串,数组,有返回值

1
2
3
4
let arr=[1,2,3,4,5]
for(let i of arr){
console.log(i);//输出 1 2 3 4 5
}

for in:数组的私有属性也会遍历,所以一般用来遍历对象

1
2
3
4
let arr=[1,2,3,4,5];
for(let item in arr){
console.log(item);//item是数组索引
}

map:数据唯一

foreach:forEach() 方法对数组的每个元素执行一次给定的函数。

1
2
3
4
let arr=[1,2,3,4,5];
arr.forEach((item,index)=>{//item为值,index为数组索引值
console.log(item);
});

遍历对象的方法和遍历数组的方法分别说五个

在 JavaScript 中遍历对象并访问其属性的方法有多种,每种方法都适用于不同的情况和需求。以下是一些常见的方法:

1. for...in 循环

这是遍历对象属性最传统的方式。for...in 循环会遍历对象自身的所有可枚举属性以及它继承的可枚举属性。

1
2
3
4
const obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
console.log(key, obj[key]);
}

注意:使用 for...in 时,通常需要使用 hasOwnProperty 方法来过滤掉从原型链继承的属性:

1
2
3
4
5
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key]);
}
}

2. Object.keys() 方法

Object.keys() 返回一个数组,包含对象自身的所有可枚举属性名称。配合 forEach 循环使用可以遍历对象属性:

1
2
3
Object.keys(obj).forEach(function(key) {
console.log(key, obj[key]);
});

3. Object.values() 方法

这个方法返回一个数组,包含对象自身的所有可枚举属性值:

1
2
3
Object.values(obj).forEach(function(value) {
console.log(value);
});

4. Object.entries() 方法

Object.entries() 返回一个数组,其元素是与对象自身可枚举属性键值对对应的数组:

1
2
3
Object.entries(obj).forEach(([key, value]) => {
console.log(key, value);
});

5. Object.getOwnPropertyNames() 方法

这个方法返回一个数组,包含对象自身的所有属性(无论是否可枚举)的名称:

1
2
3
Object.getOwnPropertyNames(obj).forEach(function(key) {
console.log(key, obj[key]);
});

6. 使用 for...of 循环(配合 Object.entries()

如果你想使用 for...of 循环遍历对象属性,可以结合使用 Object.entries()

1
2
3
for (let [key, value] of Object.entries(obj)) {
console.log(key, value);
}

7. Reflect.ownKeys() 方法

Reflect.ownKeys(obj) 返回一个数组,包含对象自身的所有键(包括不可枚举属性及 Symbol 类型的键):

1
2
3
Reflect.ownKeys(obj).forEach(function(key) {
console.log(key, obj[key]);
});

使用场景选择

  • **for...in**:适用于需要遍历继承的属性的情况,但通常需要配合 hasOwnProperty
  • **Object.keys()Object.values()Object.entries()**:适用于现代 JavaScript 应用,可以更方便地遍历对象的可枚举属性。
  • **Object.getOwnPropertyNames()Reflect.ownKeys()**:适用于需要访问对象所有键(包括不可枚举的)的高级用途。

这些方法可以灵活选择和使用,以满足不同的遍历需求和特定的编程环境。

遍历数组的方法有:

  1. for 循环
  2. forEach() 方法
  3. map() 方法
  4. filter() 方法
  5. reduce() 方法
  6. some() 方法
  7. every() 方法
  8. for…of 循环

04-11-滴滴日常实习

事件循环

qiankun机制是怎样的

qiankun是一个实现了微前端架构的JavaScript库,它允许开发者将一个大型的前端应用分解为多个可以独立开发、部署、运行的小型应用(微应用)。qiankun基于single-spa,提供了一套比较完善的微前端解决方案,其工作机制主要可以从以下几个方面理解:

初始化和注册

  • 主应用启动:在主应用中,qiankun提供registerMicroApps方法用于注册微应用。在注册时,你需要为每个微应用指定一个唯一的名称、入口(可以是URL或者一段HTML标记)、容器(用于挂载微应用的DOM元素)以及激活条件(当URL符合某个规则时激活对应的微应用)。

  • 微应用加载:当激活条件满足时,qiankun会动态加载微应用的静态资源(JavaScript、CSS等)。这一过程通常是通过fetchAPI完成的。

沙箱机制

  • JavaScript沙箱:为了防止微应用间的全局变量污染,qiankun为每个微应用创建了一个JavaScript运行时沙箱。这个沙箱通过代理全局对象(如window)来隔离全局变量,确保微应用间的隔离性。

  • 样式隔离qiankun使用Shadow DOM或者其他策略来实现CSS隔离,防止微应用间的样式冲突。

生命周期管理

  • qiankun为微应用定义了一系列生命周期钩子,例如bootstrapmountunmount等。主应用通过这些钩子可以管理微应用的加载、挂载、更新和卸载。

  • 微应用需要暴露这些生命周期方法,以便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是在服务器上生成的,这个过程包括:

  1. 请求处理:用户请求一个页面时,该请求被发送到服务器。
  2. 页面渲染:服务器运行React代码来生成页面的HTML内容,同时也可以在这个阶段获取和包含必要的数据(例如,通过API调用获取)。
  3. 发送响应:服务器将生成的HTML作为响应发送给客户端。
  4. 客户端处理:客户端浏览器接收到HTML并呈现给用户。然后,React代码在浏览器端“接管”页面,使其成为一个完全交互式的单页应用。

如何在Next.js中使用SSR

Next.js 通过文件系统基路由和数据获取方法如getServerSideProps自动处理SSR。

页面组件

每一个位于pages目录下的.js.jsx.ts.tsx文件都会自动成为路由,并且默认是通过服务端渲染的。

数据获取

对于需要服务端获取数据的页面,你可以使用getServerSideProps函数来获取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// pages/posts.js
export async function getServerSideProps(context) {
const res = await fetch(`https://api.example.com/data`);
const data = await res.json();

return {
props: { data }, // 将作为props传递给页面组件
};
}

function Posts({ data }) {
return (
<div>
{/* 渲染数据 */}
</div>
);
}

export default Posts;

getServerSideProps在请求时运行,允许你在渲染页面前从服务器获取数据。这个函数只在服务端运行,绝不会在客户端运行,因此可以直接写服务器代码,如直接访问数据库等。

优势

  1. SEO友好:由于页面的HTML是预先在服务器上生成的,搜索引擎可以更容易地抓取和索引内容。
  2. 性能提升:首次加载时直接提供包含所有必要数据的HTML,可以减少首屏渲染时间。
  3. 灵活的数据获取策略:Next.js不仅支持SSR,还支持SSG(静态网站生成),以及客户端数据获取,开发者可以根据页面的需求选择最适合的策略。

Next.js通过这些机制,极大地简化了使用React进行服务端渲染应用的开发流程,同时也提供了灵活、高效的数据处理和页面渲染方案。

看题输出–this

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
let obj = {
dev: '22222',
a: function() {
return this.dev
},
b() {
return this.dev
},
c: () => {
return this.dev
},
d: function() {
return (() => {
return this.dev
})()
},
e: function() {
return this.b()
},
f: function() {
return this.b
},
}
console.log(obj.a())//a
console.log(obj.b())//a
console.log(obj.c())//undefined
console.log(obj.d())//a
console.log(obj.e())//a
console.log(obj.f()())//undefined

在JavaScript中,this关键字的值取决于函数是如何被调用的。在对象obj中定义的方法表现出不同的this行为:

  1. a - 是一个正常的函数,当被调用时,this指向obj对象。
  2. b - 是ES6的简写方法,当被调用时,this同样指向obj对象。
  3. c - 是一个箭头函数,它没有自己的thisthis值是从创建它的上下文(即定义它的上下文)继承的,在全局代码中,this指向全局对象。
  4. d - 是一个正常的函数,它返回一个箭头函数。箭头函数内部的this由外围最近一层非箭头函数决定,在这里是d函数,因此箭头函数中的this指向obj对象。
  5. e - 是一个正常的函数,它调用obj对象的b方法,this指向obj对象。
  6. f - 是一个正常的函数,它返回obj对象的b方法,但不执行它。当f被调用,它返回对b的引用,此时this尚未被绑定。

现在,根据以上规则,可以确定console.log语句的输出:

1
2
3
4
5
6
console.log(obj.a())  // '222222' - 'this'指向obj
console.log(obj.b()) // '222222' - 'this'指向obj
console.log(obj.c()) // undefined 或报错,取决于运行环境(在严格模式下,全局的this是undefined;非严格模式下,浏览器中this指向window,Node.js中this指向global对象)
console.log(obj.d()) // '222222' - 外层函数的'this'指向obj
console.log(obj.e()) // '222222' - 'this'指向obj,调用b方法
console.log(obj.f()()) // 'undefined',取决于返回的函数b如何被调用。在这种情况下,它是作为一个独立的函数被调用,所以通常情况下'this'不会指向obj,但由于在obj的上下文中直接调用,'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环境中运行时,全局上下文中的thisundefined

宏任务和微任务

在JavaScript中,执行顺序涉及到宏任务和微任务的概念。setTimeout被放入宏任务队列,而Promise的.then方法产生的回调被放入微任务队列。微任务队列在每次宏任务执行完毕后执行,且在下一个宏任务开始前,微任务队列会被完全清空。这里是这段代码执行的顺序解释:

  1. console.log('1') 直接执行,打印 1
  2. setTimeout(..., 0) 排入宏任务队列,等待执行。
  3. Promise.resolve().then(...) 的回调函数排入微任务队列。
  4. 新的 Promise 立即执行 console.log('4'),打印 4
  5. 然后立即执行 console.log('5'),打印 5
  6. 该Promise的.then(...)回调函数被排入微任务队列。
  7. 第二个带setTimeout的Promise构造函数执行,但其内部的setTimeout是一个宏任务,排入宏任务队列,等待执行。
  8. 当前宏任务(脚本的主体)执行完毕,开始执行微任务队列:首先执行 Promise.resolve().then(...) 的回调,打印 3,然后执行第一个Promise的.then(...)回调,打印 6
  9. 微任务队列清空,开始下一个宏任务,首先是第一个setTimeout的回调,打印 2
  10. 然后执行第二个Promise中的setTimeout,等待100ms后,它的.then(...)回调被排入微任务队列。
  11. 等到该宏任务完成后,执行微任务队列中的回调,打印 7

综上,打印顺序是:1, 4, 5, 3, 6, 2, 7

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
console.log('1')

setTimeout(()=>{
console.log('2')
},0)

Promise.resolve().then((a)=>{
console.log('3')
})

new Promise((resolve)=>{
console.log('4')
resolve()
console.log('5')
}).then(()=>{
console.log('6')
})

new Promise((resolve)=>{
setTimeout(() => {
resolve();
}, 100);
}).then(() => {
console.log('7');
});

//输出:1 4 5 3 6 2 7

Promise的常用方法有哪些?

Promise.all(iterable)

  • 接受一个 Promise 对象的集合(数组、迭代器等)作为输入。
  • 当所有的 Promise 都成功解决(resolved)时,返回一个新的 Promise,该 Promise 成功解决,并且其结果是所有输入 Promise 的结果数组。
  • 如果任何一个 Promise 被拒绝(rejected),Promise.all 返回的 Promise 会立即被拒绝,其拒绝原因是第一个拒绝的 Promise 的原因。

Promise.allSettled(iterable)

  • 接受一个 Promise 对象的集合作为输入。
  • 无论输入的 Promise 对象是成功还是失败,都会等待它们全部完成。
  • 返回一个新的 Promise,该 Promise 解决后的结果是一个数组,数组中每个元素表示对应的 Promise 的结果,包括状态(fulfilledrejected)和值(或拒绝原因)。

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

  1. Commonjs 和 Es Module 有什么区别 ?

    CJS:

    • 环境:最初被设计用于 Node.js。
    • 导入模块:使用 require() 函数导入模块。
    • 导出模块:使用 module.exportsexports 对象导出模块。
    • 加载方式:同步加载模块,即在代码执行到 require() 语句时立即同步地加载和执行模块。
    • 用例:主要用于服务器端和桌面应用程序。

    ESM:

    • 环境:作为 ECMAScript(JavaScript的规范)的一部分,被设计用于浏览器和现代 JavaScript 环境。
    • 导入模块:使用 import 语句导入模块。
    • 导出模块:使用 export 关键字导出模块。
    • 加载方式:支持异步加载模块,允许进行代码分割和懒加载。
    • 用例:主要用于浏览器应用程序,但现在在 Node.js 和其他 JavaScript 环境中也得到了支持。

    区别:

    • 语法:CommonJS 使用 requiremodule.exports,而 ES Modules 使用 importexport
    • 加载机制: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 仍然广泛使用。在新项目中选择哪种标准,通常取决于项目的运行环境、目标平台和开发者的偏好。

  2. Commonjs 如何解决的循环引用问题

  3. 既然有了 exports,为何又出了 module.exports ? 既生瑜,何生亮 ?

  4. require 模块查找机制 ?

  5. Es Module 如何解决循环引用问题 ?

  6. exports = {} 这种写法为何无效 ?

  7. 关于 import() 的动态引入 ?

  8. Es Module 如何改变模块下的私有变量 ?

04-11趣链科技

介绍实习项目

介绍微前端

qiankun 是一个基于 single-spa 的微前端实现库,它通过一系列 API 简化了微前端的实现,允许开发者将不同的前端应用组合成一个完整的应用。以下是 qiankun 的一些底层实现细节以及主应用和子应用通信的方式。

qiankun 底层实现

  1. JavaScript 沙箱

    • qiankun 通过 JavaScript 沙箱技术隔离主应用和子应用的运行环境。沙箱主要通过代理全局变量来实现,每个微应用都运行在自己的全局上下文中。
  2. 样式隔离

    • 为了防止 CSS 污染,qiankun 对子应用的样式进行隔离。它可以使用 Shadow DOM 或者为子应用的样式增加特定的前缀来实现。
  3. 生命周期钩子

    • qiankun 实现了一套生命周期钩子,允许在加载、挂载、更新和卸载微应用时进行相应的处理。
  4. 资源加载

    • qiankun 使用 fetch API 动态加载微应用的资源文件,包括 HTML、JavaScript 和 CSS。
  5. 路由代理

    • qiankun 对浏览器历史 API 进行了劫持,以支持微应用的路由系统,确保路由变化能够被正确处理。

主应用和子应用通信方式

  1. Props 传递

    • 类似于 React 或 Vue 组件的 props,qiankun 允许主应用在加载微应用时传递 props。这些 props 可以是简单的数据,也可以是可以调用的函数,用于主子应用间的直接通信。
  2. 全局变量

    • 可以通过全局变量在主应用和子应用之间通信。例如,主应用可以将一个全局变量或者函数挂载到 window 对象上,子应用可以访问和使用这些变量或函数。
  3. 自定义事件(Custom Events)

    • 主应用和子应用可以通过触发和监听自定义事件来实现通信。这种方式不直接依赖于 qiankun 的 API。
  4. 状态管理库

    • 如果主应用和子应用都使用相同的状态管理库(如 Redux、Vuex),可以通过共享一个状态存储来进行通信。
  5. 消息通道(Message Channel)

    • 可以使用 window.postMessage 和监听 window 上的 message 事件来在主应用和子应用之间进行跨域通信。
  6. Observable 对象

    • 使用诸如 RxJS 这类响应式编程库中的 Observable 对象,可以在应用之间创建一个可观察的数据流。

这些通信方式可以根据应用的实际需要和开发习惯进行选择和使用。在微前端架构中,选择合适的通信机制对于确保应用的独立性和协调一致性非常重要。

Qiankun 是一个基于 single-spa 的微前端框架,提供了更简单的 API 和配置方式,让开发者能够构建大型的、由多个前端应用组合而成的应用系统。在 Qiankun 中,每个子应用都有自己的生命周期,这些生命周期钩子允许主应用在特定时刻介入子应用的状态,实现控制和资源管理。

Qiankun 子应用的生命周期

以下是 Qiankun 子应用的主要生命周期钩子及其用途:

  1. bootstrap

    • 时机:子应用初次加载时调用。
    • 用途:用于启动子应用,通常包括初始化资源、依赖项或其它只需执行一次的设置。
  2. mount

    • 时机:每次子应用需要被激活(即展示到视图中)时调用。
    • 用途:用于挂载子应用的实例到 DOM,处理状态恢复和事件监听器的添加等。
  3. unmount

    • 时机:每次子应用需要被卸载(从视图中移除)时调用。
    • 用途:用于卸载子应用,清除相关资源和事件监听器,保证内存释放,避免内存泄漏。
  4. update(可选):

    • 时机:主应用或自身传入新的 props 时调用。
    • 用途:响应 props 变化,执行相关更新逻辑。

生命周期的实现

在子应用中,你通常需要导出这些生命周期钩子的实现。这可以通过单独的函数或对象方法实现。例如,如果你的子应用是用 React 构建的,你的生命周期实现可能如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export async function bootstrap() {
console.log('React app bootstraped');
}

export async function mount(props) {
ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}

export async function unmount(props) {
ReactDOM.unmountComponentAtNode(props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}

export async function update(props) {
// 响应 props 更新
}

使用场景

这些生命周期方法对于管理应用状态、资源分配和优化性能至关重要。例如,通过 unmount 方法清理事件监听器和外部插件,可以避免内存泄漏。通过 mountunmount 方法,可以确保子应用只在必要时占用浏览器资源,从而优化整体的应用性能。

了解和合理利用这些生命周期钩子,是使用 Qiankun 微前端框架有效管理多个前端应用的关键。这有助于确保应用的稳定性、响应性和高效性。

CSS的动画了解吗:比如transition

事件循环机制:宏任务和微任务

浏览器的存储,cookie,localstorage,sessionstorage

说一下Websocket

WebSocket 是一种在单个TCP连接上进行全双工通信的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。他不是HTTP协议,

节流和防抖的使用场景,闭包

强缓存和协商缓存:cache-control+Expires ,协商缓存:E-TagLast-Modified

Promise和一些静态方法(promise.all,promise,race等)

Promise 是JavaScript中用于异步编程的一个重要概念。它代表了一个最终可能完成或失败的操作及其结果值。一个Promise对象有三种状态:

  • pending(等待状态):初始状态,既不是成功,也不是失败状态。
  • fulfilled(成功状态):操作完成,且成功返回值。
  • rejected(失败状态):操作完成,但失败,返回拒绝的原因。

Promise提供了一种更加优雅的处理异步操作的方法,相比于传统的回调函数,它可以减少代码的嵌套,并且更易于理解和维护。

Promise的静态方法

  1. **Promise.all(iterable)**:当你需要并行执行多个异步操作,并且只有当所有异步操作成功时才继续执行,可以使用Promise.all
  2. **Promise.allSettled(iterable)**:如果你需要知道一系列异步操作的每一个的结果,不管是成功还是失败,可以使用Promise.allSettled
  3. **Promise.race(iterable)**:当你有多个异步操作,并且你只关心哪个操作最先完成(或失败),可以使用Promise.race
  4. **Promise.resolve(value)和Promise.reject(reason)**:这两个方法可以用来创建已经处于完成或拒绝状态的Promise,用于测试或者与其他Promise操作结合使用。

Vue2的生命周期

创建前后,挂载前后,更新前后,销毁前后

异步请求的阶段:created,mounted

组件通信的方式

  1. props,$emit;
  2. provide,inject;
  3. Event Bus
  4. vuex
  5. Vue3 Composition API(Vue3):setup
  6. Refs 和 $parent / $children:

路由鉴权

路由守卫routerConfig.beforeEachmeta.access的值决定是否允许访问某个路由

微前端生命周期钩子

  1. bootstrap:子应用的启动
  2. mount:挂载
  3. 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.useVue.mixin 等)的使用方式,使得它们更适合模块化的项目结构。

尽管 Vue 3 带来了很多新特性和改进,Vue 2 仍然是一个非常稳定和功能丰富的框架,被广泛用于生产环境中。迁移到 Vue 3 是一个值得考虑的步骤,尤其是对于新项目或正在考虑重构的项目。

代码输出:

1
2
3
4
5
6
for(let i=1;i<=3;i++){
setTimeout(function(){
console.log(i)
},0)
}
//1 2 3

这段JavaScript代码定义了一个名为Fn的构造函数,它没有参数和执行体。接着,在Fn的原型(Fn.prototype)上定义了一个名为add的方法和一个名为count的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Fn() {}//`function Fn() {}`:这行代码定义了一个空的构造函数`Fn`。

Fn.prototype.add = function () {
//这行代码在`Fn`的原型对象上添加了一个方法`add`。
//这个`add`方法的作用是将实例上的`count`属性值增加1,并且在控制台打印出来。这里的`this`指的是调用`add`方法的实例。
this.count++;
console.log(`this.count: ${this.count}`);
};

Fn.prototype.count = 0;

let fn1 = new Fn();
fn1.add(); // this.count: 1

let fn2 = new Fn();
fn2.add(); // this.count: 1
//this.count:1
//this.count:1
  1. 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();:同样地,创建了另一个名为fn2Fn实例。

  • 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
2
3
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

2. 使用 Set 和 Array.from()

与使用展开运算符类似,但是通过 Array.from() 方法将 Set 转换成数组。

1
2
3
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = Array.from(new Set(array));
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

3. 使用 filter 方法

通过数组的 filter() 方法结合 indexOf() 方法来过滤重复元素。这种方法不需要额外的数据结构。

1
2
3
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = array.filter((item, index) => array.indexOf(item) === index);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

4. 使用 reduce 方法

使用 reduce() 方法累加数组中的元素,结合条件判断实现去重。

1
2
3
4
5
6
7
8
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = array.reduce((acc, current) => {
if (!acc.includes(current)) {
acc.push(current);
}
return acc;
}, []);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

5. 使用 Map 数据结构

利用 Map 的键唯一的特性来去重,适合于数组元素是对象这类比较复杂的情况。

1
2
3
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = Array.from(new Map(array.map(item => [item, item])).values());
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

6. 循环比较(传统方法)

传统的双重循环检查方法,性能较低,但在一些特定情况下仍然有其用途。

1
2
3
4
5
6
7
8
const array = [1, 2, 2, 3, 4, 4, 5];
let uniqueArray = [];
for(let i = 0; i < array.length; i++) {
if(uniqueArray.indexOf(array[i]) === -1) {
uniqueArray.push(array[i]);
}
}
console.log(uniqueArray); // 输出: [1, 2, 3, 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

左右浮动,上面设置右边的flex:1,右边的文字设置超过两行隐藏

css的动画,transitionanimation

transition:只能在初始状态和结束状态之间提供动画效果,不支持中间状态或复杂的动画序列。

1
2
3
4
5
6
7
8
div {
transition: background-color 0.5s ease;
background-color: blue;
}

div:hover {
background-color: red;
}

**animation:**用于更复杂的动画效果,它允许你通过关键帧(@keyframes 规则)定义动画的中间步骤。你可以控制动画的中间状态、循环次数、方向、延迟等。

1
2
3
4
5
6
7
8
9
@keyframes example {
from {background-color: blue;}
to {background-color: red;}
}

div {
animation-name: example;
animation-duration: 2s;
}

webpack、vite,代码中如何配置,举例说明

分成不同的环境:生产、灰度、线上,

ts的泛型、接口、枚举

  1. 定义泛型函数
    1
    2
    3
    4
    5
    6
    function identity<T>(arg: T): T {
    return arg;
    }

    let output1 = identity<string>("myString"); // 类型为 string
    let output2 = identity<number>(100); // 类型为 number
  2. ts的泛型有属性的时候可以使用extends关键字进行约束,在这个例子中,泛型 T 被约束了,它必须符合 Lengthwise 接口,这意味着 T 必须有一个 length 属性。
    1
    2
    3
    4
    5
    6
    7
    8
    interface Lengthwise {
    length: number;
    }

    function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length); // 它有一个 .length 属性
    return arg;
    }
  3. 枚举遍历,for … in
    1
    2
    3
    4
    5
    6
    7
    8
    9
    enum 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-CredentialsAccess-Control-Allow-Origin响应头,并在AJAX请求中设置withCredentialstrue

服务器端设置(以Node.jsExpress为例):

1
2
3
4
5
6
7
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "http://example.com"); // 允许的域
res.header("Access-Control-Allow-Credentials", "true"); // 允许cookies
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); // 允许的方法
res.header("Access-Control-Allow-Headers", "X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept"); // 允许的头
next();
});

客户端设置(以JavaScriptXMLHttpRequest为例):

1
2
3
4
5
6
7
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://example.com/path", true);

// 设置withCredentials为true来发送cookies
xhr.withCredentials = true;

xhr.send();

确保服务器响应头中的Access-Control-Allow-Origin与实际请求的域相匹配,并且如果withCredentialstrue,则Access-Control-Allow-Credentials也必须设置为true

0415DeepLang

postmessage和http通信

浏览器原理,事件循环和url,

ES6创建的函数我不想他被new创造新的实例

使用new target:

1
2
3
4
5
6
7
8
9
function myFunction() {
if (new.target) {
throw new Error('myFunction cannot be called as a constructor.');
}
console.log("This is my function.");
}

myFunction(); // 正常输出 "This is my function."
// new 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { useState } from 'react';

function useWestate(initialState) {
const [state, setState] = useState(initialState);

const customSetState = (newState) => {
setState(prevState => ({
set:"1111",
}));
};

return [state, customSetState];
}

export default useWestate;

现在我有一个文件,下载需要8s,如何使用协商缓存和强缓存进行优化?

对于文件下载缓慢的问题,正确地使用HTTP缓存策略可以显著提高性能。在HTTP缓存中,主要分为强缓存协商缓存。这两种缓存策略可以有效减少不必要的网络请求,加快文件访问速度。以下是如何选择和使用这两种缓存策略来优化你的文件下载问题:

强缓存 (Strong Caching)

强缓存直接从浏览器缓存中获取资源,不与服务器进行交互,除非缓存过期。设置强缓存的方法主要是通过Cache-ControlExpires 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-Controlmax-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-Controlmax-age为一个相对较长的值来实现。
  • 实时性要求:如果文件更新不频繁但需要保证用户能快速获取最新版本的文件,可以结合使用强缓存和协商缓存。强缓存确保在文件不变时快速加载,协商缓存确保文件一旦更新,用户能立即获取最新版本。

实施示例

1
2
Cache-Control: public, max-age=86400
ETag: "686897696a7c876b7e"

这种设置意味着文件将被缓存一天,每天浏览器会检查一次文件是否更新。这样可以显著减少不必要的下载时间,同时保持文件的相对新鲜度。如果你的文件是用户每次都需要最新版的,可以适当减少max-age值,或者仅依赖ETag进行更频繁的检查。

通过这种方式,你可以有效利用HTTP缓存提高文件的访问速度和用户体验。

二面

http的head有哪些

http协议

不使用递归实现中序遍历:栈

mysql的事务有哪些

杭州某外包

react的hooks

useMemo和useEffect的区别

在React中,useMemouseEffect都是钩子(Hooks),它们用于增强函数组件的功能。这两个钩子虽然都与组件的渲染周期密切相关,但它们的用途、功能和执行时机有明显的不同。理解这些差异对于编写高效和可预测的React应用至关重要。

useMemo

useMemo是用于优化性能的钩子,通过记忆复杂函数的计算结果来避免在每次渲染时都执行这些计算。useMemo接收一个“创建”函数和一个依赖数组,仅当依赖项改变时,才会重新计算记忆值。

主要用途:

  • 性能优化:避免在每次渲染时进行高开销的计算。
  • 记忆化复杂派生数据:基于依赖计算得到的数据,只有当依赖改变时,才重新计算。

示例:

1
2
3
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);

在这个例子中,只有当ab变化时,函数computeExpensiveValue才会被调用。

useEffect

useEffect是用于在函数组件中执行副作用的钩子。副作用指的是那些对外部世界产生影响的操作,比如数据获取、手动修改DOM和订阅事件。useEffect同样接收一个函数和一个依赖数组,但它的工作方式与useMemo大不相同。

主要用途:

  • 与外部世界的交互:执行与渲染无关的操作,如API调用、订阅或定时器。
  • 资源清理:在组件卸载时进行清理,如取消订阅或清除定时器。
  • DOM更新后的操作:在组件和DOM更新后执行操作。

示例:

1
2
3
4
5
6
7
useEffect(() => {
const subscription = dataSource.subscribe();
return () => {
// 清理订阅
subscription.unsubscribe();
};
}, [dataSource]);

这个例子中,useEffect用于订阅一个数据源,依赖数组确保只在dataSource变化时重新订阅。

执行时机的区别

  • useMemo

    • 在组件渲染过程中执行,即同步执行。因为它可能会影响渲染输出的内容,必须在渲染流程中完成。
  • useEffect

    • 在组件渲染到屏幕之后执行,即异步执行。这意味着它不会阻塞浏览器的绘制过程,适用于那些对渲染结果无直接影响的操作。

总的来说,useMemo主要用于计算优化,以减少组件的重渲染成本;而useEffect主要用于处理副作用,实现与应用的其他部分的交互。正确的使用这两个钩子,可以提高应用的性能和响应速度。

父子传值:ref

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react';

class Child extends React.Component {
alertSomething = () => {
alert('这是子组件的方法!');
}

render() {
return <h1>子组件</h1>;
}
}

export default Child;

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { useRef } from 'react';
import Child from './Child';

function Parent() {
const childRef = useRef(null);

const handleClick = () => {
if (childRef.current) {
childRef.current.alertSomething();
}
}

return (
<div>
<Child ref={childRef} />
<button onClick={handleClick}>调用子组件的方法</button>
</div>
);
}

export default Parent;

vue不仅仅是使用路由进行鉴权

promise.all如何在接受到rejected也成功返回?promise.race

答:通过catch

箭头函数

let const

vuex全局状态管理

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式和库。它以集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 非常适用于大型单页应用,它可以帮助你更好地在组件外部管理状态,让状态的变化更加可追踪和易于调试。

Vuex 的核心概念

  1. State

    • Vuex 使用单一状态树 —— 用一个对象就包含了全部的应用层级状态。每个应用将仅仅包含一个 store 实例。
    • 状态存储是响应式的,Vue组件从 store 中读取状态,并且当状态发生变化时,相应的组件也会相应地得到高效更新。
  2. Getters

    • 类似于 Vue 的计算属性,getters 可以用于从 store 的 state 中派生出一些状态,例如过滤列表、计数等。
    • Getters 会接受 state 作为其第一个参数,也可以接受其他 getters 作为第二个参数。
  3. Mutations

    • 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutations 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和一个 处理函数 (handler)。
    • Mutation 必须是同步函数。
  4. Actions

    • Action 类似于 mutation,不同在于:
      • Action 提交的是 mutation,而不是直接变更状态。
      • Action 可以包含任意异步操作。
    • Action 函数接收一个与 store 实例具有相同方法和属性的 context 对象,因此可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
  5. Modules

    • Vuex 允许我们将 store 分割成模块(module),每个模块拥有自己的 state、mutation、action、getter,甚至是嵌套子模块 —— 从而允许我们对复杂的应用进行状态管理。
    • 模块内部的 mutation 和 getter 接收的第一个参数是模块的局部状态对象。

Vuex 的使用场景

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

Vuex 的使用示例

以下是一个简单的 Vuex store 的定义示例:

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
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
state: {
count: 0
},
getters: {
doubleCount: state => state.count * 2
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
incrementIfOdd({ commit, state }) {
if (state.count % 2 !== 0) {
commit('increment');
}
}
}
});

export default store;

在组件中使用:

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>
<div>
<p>{{ count }}</p>
<p>{{ doubleCount }}</p>
<button @click="increment">Increment</button>
<button @click="incrementIfOdd">Increment if odd</button>
</div>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'vuex';

export default {
computed: {
...mapState([
'count'
]),
...mapGetters([
'doubleCount'
]),
},
methods: {
...mapActions([
'increment',
'incrementIfOdd'
])
}
}
</script>

结论

Vuex 提供了一个集中存储所有组件状态的方式,并以规范的方式更新状态,易于维护和监控状态变化,特别适合处理大型、复杂的应用中多个组件共享状态的情形。

完美世界

介绍一下fiber

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

箭头函数是在ES6中引入的,提供了一种更简洁的方式来写函数表达式。箭头函数与普通函数(如函数声明或函数表达式)相比,有几个主要区别:

  1. 语法更简洁

    • 箭头函数提供了更短的函数语法。例如,一个普通的函数表达式 function(x, y) { return x + y; } 可以用箭头函数写为 (x, y) => x + y
  2. **没有自己的 this**:

    • 在箭头函数中,this 关键字具有其封闭执行环境的上下文,这与普通函数不同,普通函数的 this 指向在运行时被调用时的上下文(如由一个对象调用)。
    • 这使得箭头函数特别适合用在需要维持上下文的情况,如在回调函数和方法链中。
  3. 不绑定 arguments 对象

    • 箭头函数不提供 arguments 对象。如果你需要访问传给函数的参数列表,应使用剩余参数语法(...args)代替。
  4. 不可以使用 new 操作符

    • 箭头函数不能用作构造函数,不可以使用 new 操作符,因为它们没有 [[Construct]] 方法。尝试对箭头函数使用 new 会抛出一个错误。
  5. **不绑定 super**:

    • 箭头函数不绑定 super 关键字。在使用类的继承时,这一点需要注意。
  6. 不能用作生成器函数

    • 箭头函数不能使用 yield 关键字,在其内部使用会导致语法错误。

总结来说,箭头函数主要用于那些不需要绑定自身 this、不用作构造函数、并且更注重简洁性的场合。由于它们在功能上的这些限制,它们并不总是可以替代普通函数。

hooks可以写在if判断语句里吗

在React中,Hooks不能写在条件语句(如if)、循环、嵌套函数或任何不是React函数组件顶层的地方。这是因为Hooks必须按照相同的顺序在每次组件渲染时被调用,以保证Hooks状态的正确性。将Hooks放在条件语句中使用可能会破坏这种顺序,导致不可预测的结果。

为什么Hooks不能放在条件语句中

React的Hooks功能依赖于调用顺序。每次组件渲染时,React按照Hooks被调用的顺序来维护和更新状态。如果你将Hook放入一个条件语句中,那么在某些渲染时Hook可能被调用,而在另一些渲染时可能不被调用,这将破坏React对Hooks调用顺序的跟踪。

错误示例

以下是一个错误的使用示例,展示了将useState放在一个if语句中的情况:

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

function MyComponent({ condition }) {
if (condition) {
const [count, setCount] = useState(0); // 错误用法
}

// 其他代码...
}

这种用法会导致React无法正确追踪count状态的变化,可能会引发运行时错误。

正确用法

如果你需要根据条件初始化状态或进行其他操作,你应该将条件逻辑放在Hook内部,而不是将Hook放在条件语句中。以下是一个改进的示例:

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

function MyComponent({ condition }) {
const [count, setCount] = useState(condition ? 10 : 0); // 在Hook内部使用条件

useEffect(() => {
if (condition) {
// 做一些只有在条件成立时才需要做的事情
}
}, [condition]); // 将条件作为依赖

// 其他代码...
}

在这个改进的例子中,useStateuseEffect都被放在组件的顶层,并根据条件以适当的方式使用。这保证了Hooks的调用顺序是一致的,同时也能根据不同的条件执行不同的逻辑。

结论

总的来说,React Hooks应始终在函数组件的最顶层使用,无论是条件、循环还是嵌套函数都不应包含Hooks。遵守这一规则将帮助你避免在组件状态管理中遇到复杂和难以调试的问题。

computed与wacth有什么区别

ES6

原型和原型链

原型:prototype,在 JavaScript 中,几乎所有的对象都有一个“原型”。原型本身也是一个对象,被作为一个模板对象,它存储了可以被其他对象继承的属性和方法。当你创建一个对象时,这个对象自动引用一个原型,从中可以继承方法和属性。

例如,所有的 JavaScript 函数都有一个 prototype 属性,这个属性指向一个对象。当你使用构造函数创建一个新对象时(使用 new 关键字),新对象的内部 [[Prototype]] 属性(在大多数浏览器中可以通过 __proto__ 访问)将被设置为构造函数的 prototype 对象。

例如:

1
2
3
4
5
6
7
8
9
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hello, my name is ${this.name}`;
};

const alice = new Person("Alice");
console.log(alice.greet()); // Outputs: Hello, my name is Alice

原型链:prototype chain,原型链是一个对象通过其内部 [[Prototype]] 属性链接到其原型,而这个原型本身也可能有自己的原型,并从中继承方法和属性,这样的链条一直向上直到一个对象的原型为 null。通常,原型链的终点是 Object.prototype,这是所有纯粹对象默认的原型。

当你尝试访问一个对象的属性时(包括方法),JavaScript 首先查找对象本身是否有这个属性。如果没有找到,JavaScript 将沿着原型链向上查找,直到找到该属性或到达原型链的末端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(name) {
this.name = name;
}

Person.prototype.greet = function() {
return `Hello, my name is ${this.name}`;
};

const alice = new Person("Alice");

// 查找过程:
// 1. 检查 alice 对象本身是否有 greet 方法。
// 2. 没有找到,因此查找 alice.__proto__(即 Person.prototype)。
// 3. 在 Person.prototype 中找到了 greet 方法。
console.log(alice.greet()); // 输出: Hello, my name is Alice

JavaScript 中的继承是通过原型和原型链实现的。这种机制允许对象共享方法和属性,而无需在每个对象上重新定义它们,这提高了效率并节省了内存。理解原型和原型链是深入理解 JavaScript 对象、继承和类行为的关键。

new的过程都干了些什么

在 JavaScript 中,new 操作符用于创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型的实例。当你使用 new 操作符时,实际上会执行以下几个步骤:

  1. 创建一个新对象

    • new 操作符首先会创建一个空的简单 JavaScript 对象;即一个新的空对象。
  2. 设置原型

    • 接着,这个新对象的内部 [[Prototype]](也就是 __proto__)属性会被赋值为构造函数的 prototype 属性。这意味着新对象将继承构造函数原型上的方法和属性。
  3. 执行构造函数

    • 构造函数被调用,并将 this 绑定到新创建的对象上。这意味着在构造函数中你可以使用 this 关键字来引用新对象,并给这个新对象添加属性和方法。
  4. 返回新对象

    • 如果构造函数返回一个对象,则 new 表达式结果为这个对象;如果构造函数返回的不是对象(包括 undefined 或任何原始类型的值),则返回新创建的对象。

通过这些步骤,new 操作符不仅仅创建了一个对象实例,还正确设置了对象的原型链,这对于实现基于原型的继承非常关键。这使得实例可以访问构造函数原型上定义的方法和属性。例如:

1
2
3
4
5
6
7
8
9
function Person(name) {
this.name = name;
this.greet = function() {
return `Hello, my name is ${this.name}`;
};
}

var person1 = new Person("Alice");
console.log(person1.greet()); // "Hello, my name is Alice"

在这个例子中,new Person("Alice") 创建了一个新的 Person 实例,并设置了 name 属性和 greet 方法。每次使用 new Person 时,都会按照这个模式创建一个新的对象。

事件循环

箭头函数和普通函数有什么区别

假设vue组件里一个ref的变量 然后再