- Published on
春招面经
- Authors

- Name
- Stone
春招面经
超星集团
hight min-height max-height的生效规则?
height 设置元素的高度,min-height 确定高度的最小值,max-height 确定高度的最大值;当 min-height 和 max-height 与 height 冲突时,min-height 优先于 height,而 max-height 限制了可能超过此值的 height 或 min-height。
说一下text-align
text-align CSS 属性用于设置文本的水平对齐方式。它可以应用于块级元素和表格单元格,影响其内部的行内内容(例如,文本或内联元素)的对齐方式。text-align 属性主要有以下几个值:
left:文本左对齐。right:文本右对齐。center:文本居中对齐。justify:文本两端对齐。这意味着文本会被拉伸或压缩以确保每行的左右两端都与容器的边缘对齐,通常用于报纸和杂志的排版以提高可读性。start:文本对齐到起始边缘。在从左向右阅读的语言(如英语)中等同于左对齐,在从右向左阅读的语言(如阿拉伯语)中等同于右对齐。end:文本对齐到结束边缘。在从左向右阅读的语言中等同于右对齐,在从右向左阅读的语言中等同于左对齐。
text-align存在继承关系吗
text-align 属性是可以继承的。这意味着如果你在一个父元素上设置了 text-align 属性,那么它的子元素会继承这个对齐方式,除非在子元素上显式地设置了另一个 text-align 值。
例如,如果你在一个 <div> 上设置了 text-align: center;,那么所有未显式设置 text-align 的子元素也将会将其文本居中对齐。
box1有一些元素 比如image标签,会生效吗
text-align 属性虽然主要用于文本的对齐,但它也会影响到内联元素(包括 <img> 标签)的对齐方式。在一个容器(如 div 或 span)内,text-align 不仅对文本有效,也对图片、链接或其他内联元素的水平对齐方式生效。
例如,如果你有一个容器 box1 并设置了 text-align: center;,那么在这个容器内的 <img> 标签(作为内联元素)也会居中显示,因为它们遵循同样的对齐规则。
怎么获取图片高度
直接获取图片元素的高度
如果图片已经被加载,你可以直接通过图片元素的属性来获取高度。
const img = document.getElementById('imageId'); // 假设图片有一个ID为'imageId'
console.log(img.offsetHeight); // 获取图片的高度,包括边框
console.log(img.naturalHeight); // 获取图片的自然高度,不包括边框,即图片原始高度
使用 load 事件监听器
如果图片是动态加载的,或者你需要在页面加载时立即获取图片的高度,最好是在图片的 load 事件触发后再获取高度。这样可以确保图片已经完全加载。
const img = new Image();
img.onload = function() {
console.log(img.height); // 在图片加载完成后获取高度
};
img.src = 'path/to/your/image.jpg';
如果是已经在HTML中声明的图片,可以这样添加监听器:
const img = document.getElementById('imageId');
img.onload = function() {
console.log(this.height); // 使用this引用当前元素
};
// 如果图片在添加监听器时已经加载完成,则直接获取高度
if (img.complete) {
console.log(img.height);
}
注意事项
naturalHeight属性返回图片的原始高度,而height属性返回当前显示的高度,这可能会受到CSS样式的影响。- 如果图片是通过CSS背景设置的,你不能直接使用这些方法获取高度。背景图片的尺寸需要通过其他方式计算,可能涉及到容器的尺寸或通过JavaScript动态调整。
- 在使用
load事件时,确保在设置src属性之前添加事件监听器,避免错过加载事件。
vue中局部修改第三方库的样式,怎么改
使用深度选择器
当使用scoped样式时,Vue会为每个样式规则添加一个唯一的属性,以确保它们只作用于当前组件。但这会阻止你修改第三方组件的样式,因为这些样式不会匹配第三方组件的根元素。为了穿透这个作用域,你可以使用**::v-deep(或>>>和/deep/**,后两者已被废弃但可能在旧项目中仍然见到)。
/* Vue 2 */
<style scoped>
::v-deep .third-party-class {
color: red;
}
</style>
/* Vue 3 */
<style scoped>
::v-deep(.third-party-class) {
color: red;
}
</style>
2. 增加选择器的特异性
有时,你可以通过增加CSS选择器的特异性来覆盖第三方样式。这不需要使用任何特殊的Vue特性,只需确保你的选择器比原来的更具体。
<div class="my-component">
<!-- 第三方组件 -->
</div>
<style>
.my-component .third-party-class {
color: red;
}
</style>
这种方法的缺点是,如果第三方库使用了**!important**声明,增加特异性可能无效。
3. 使用全局样式文件
另一个方法是在Vue项目的全局样式文件(例如**App.vue或专门的assets/styles.css**)中覆盖第三方样式。这种方法不受Vue的作用域CSS影响,但会全局影响所有使用该类名的组件,所以需要谨慎使用。
/* 在全局样式文件中 */
.third-party-class {
color: red;
}
4. 使用JavaScript动态添加样式
在某些情况下,你可能需要根据组件的状态或属性动态改变第三方组件的样式。这时,你可以使用JavaScript动态地添加样式。
export default {
mounted() {
this.$nextTick(() => {
let elements = this.$el.querySelectorAll('.third-party-class');
elements.forEach(el => {
el.style.color = 'red';
});
});
}
}
每种方法都有其适用场景。使用**::v-deep**可以直接穿透scoped样式的限制,增加选择器特异性是最简单直接的方法,全局样式文件适合对全站样式进行统一设置,而JavaScript方法提供了最大的灵活性
说一下样式穿透
样式穿透是一个CSS技巧,它允许在Vue组件中使用**scoped样式时,样式能够“穿透”该作用域,影响到子组件或第三方组件的内部。由于Vue的scoped**样式默认只作用于当前组件的根元素及其子元素,不会影响到嵌套的子组件,因此在需要对这些内嵌组件进行样式定制时,样式穿透技术就显得非常有用。
使用::v-deep
Vue提供了几种方式来实现样式穿透,其中**::v-deep**是最常用的方法。它允许你定义的样式规则穿透到深层的子组件中。
在Vue 2和Vue 3中,**::v-deep**的使用略有不同:
Vue 2:可以直接使用**
::v-deep**,后跟你想要穿透样式的选择器。<style scoped> ::v-deep .third-party-class { color: red; } </style>Vue 3:推荐使用**
::v-deep()**函数,并将目标选择器放在括号内。<style scoped> ::v-deep(.third-party-class) { color: red; } </style>
使用>>>和/deep/
在较早的Vue版本中,**>>>和/deep/也被用于样式穿透,但它们已被::v-deep替代。尽管在某些情况下你可能还会见到它们,但建议使用::v-deep**来确保兼容性和未来的维护性。
vue3中defineExpose

允许子组件显式地向父组件(或任何其他组件)暴露其状态、方法或任何其他响应式属性。这种方式主要用于组件间的通信,特别是在使用**<script setup>**时,由于其封装性较高,不会像传统的Vue组件那样自动暴露其方法和属性。
简而言之,defineExpose 用于子组件向外界(如父组件)公开接口,而不是用于父组件向子组件传递数据。父组件向子组件传递数据通常是通过props或提供/注入(provide/inject)机制实现的。
如何使用
在子组件中使用 <script setup> 语法时,你可以通过**defineExpose**来公开组件内部的状态和方法。这样,父组件就可以通过子组件的引用来访问这些被公开的属性或方法了。
<!-- 子组件 -->
<script setup>
import { ref } from 'vue'
const internalCount = ref(0)
function increment() {
internalCount.value++
}
// 使用defineExpose公开increment方法和internalCount状态
defineExpose({
increment,
count: internalCount
})
</script
在父组件中,你可以通过模板引用(ref)或者其他方式获取到子组件的实例,然后访问这些公开的方法和属性。
<!-- 父组件 -->
<template>
<ChildComponent ref="childComp" />
<button @click="incrementChild">Increment Child</button>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const childComp = ref(null)
function incrementChild() {
childComp.value.increment()
}
</script>
vue3中定义一个reactive对象,怎么把Proxy失去响应式
因为有时候开发的时候我们某个地方需要响应式,但这个数据在另一个地方又不需要,这时候就可以用toRaw()

vite怎么如何基础路径
// vite.config.js 或 vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
// 设置基础路径
base: '/yourBasePath/',
});
这里的**/yourBasePath/应该替换为你的实际基础路径。例如,如果你的应用将被部署在https://example.com/myapp/下,那么你应该将base设置为'/myapp/'**。
FCP你怎么计算的?
FCP (首次内容绘制)
First Contentful Paint,从开始加载网页到第一个文本、图像、svg、非白色的 canvas 渲染完成之间的耗时。
可以通过 performance 的 api 计算出来:
const paint = performance.getEntriesByType('paint')
const FCP = paint[1].startTime
[
{
"name": "first-paint", // FP
"entryType": "paint",
"startTime": 1494.5999999940395,
"duration": 0
},
{
"name": "first-contentful-paint", //FCP
"entryType": "paint",
"startTime": 1494.5999999940395,
"duration": 0
}
]
也可以通过 PerformanceObserver 的 api 拿到:
newPerformanceObserver((entryList) => {
for (const entryof entryList.getEntriesByName('first-contentful-paint')) {
console.log('FCP candidate:', entry.startTime, entry);
}
}).observe({type: 'paint', buffered: true});
怎么使用puppeteer对页面进行预渲染

预渲染和SSR的区别
预渲染(Prerendering) 和 服务器端渲染(SSR, Server-Side Rendering) 是两种不同的技术,它们都用于改进单页面应用(SPA)的初始加载性能和搜索引擎优化(SEO),但它们的实现方式和使用场景有所不同。
- 预渲染 (Prerendering):
预渲染通常是指在构建阶段生成静态的HTML文件,对于每个路由预先生成一个对应的HTML文件。
它适用于那些没有太多动态内容,或者动态内容不是在首次加载时显示的网站。
对于每个预渲染的路由,搜索引擎和用户首次访问时会立即获得完全构建好的HTML页面。
需要更新内容时,可能需要重新执行整个构建过程。
使用预渲染技术通常可以节省服务器资源,因为服务器不需要实时生成页面。
关键字: 构建阶段、静态HTML、SEO
- 服务器端渲染 (SSR, Server-Side Rendering):
服务器端渲染是指服务器实时动态地为每个用户或请求生成HTML页面。
这种方法非常适合具有高度动态内容的网站,可以确保用户和搜索引擎总是获取到最新内容。
SSR可以提升首屏加载性能,因为用户会直接从服务器接收到渲染好的页面。
需要服务器有足够的处理能力来应对渲染时的负载,并且可能涉及更复杂的缓存策略。
如果需要频繁更新页面内容,SSR可以提供更好的用户体验,因为不需要等到整站重新构建。
关键字: 实时渲染、动态内容、服务器负载、首屏性能
总结来说,预渲染是生成静态HTML文件的过程,这些文件在构建时创建,并在每次请求时直接服务给用户。而服务器端渲染是一个动态的过程,服务器在每次用户访问时实时生成HTML页面,这为用户提供了最新的内容。两者选择的依据通常基于应用程序的动态性、实时性需求以及服务器资源等因素。
微派一面
自我介绍
然后就根据实习经历一条一条的问
你说你做的是跨端开发,你还了解其他的跨端框架吗?他们都有什么区别,各自的优缺点又是什么?
- React Native:
- 由Facebook开发,可以使用React和JavaScript来构建近乎原生的移动应用。
- 优点:高性能,大社区和生态系统,以及丰富的第三方库。
- 缺点:原生模块的依赖可能导致升级困难,需要对原生语言有一定了解。
- Flutter:
- 由Google开发,使用Dart语言,其绘制UI的方式使得它在跨平台上有一致性和高性能。
- 优点:良好的性能,丰富的组件和库,以及强大的UI能力。
- 缺点:社区较小,Dart语言的普及度较低。
- Xamarin:
- 由微软支持,使用.NET和C#来构建应用,适用于构建Windows, iOS, 和Android应用。
- 优点:与Microsoft生态系统紧密集成,共享逻辑代码的能力。
- 缺点:较大的应用体积,UI不如React Native或Flutter流畅。
- Apache Cordova(以前称为PhoneGap):
- 使用HTML5, CSS3和JavaScript,适用于创建性能要求不高的移动应用。
- 优点:可以使用传统的前端技能,且有大量插件可用。
- 缺点:性能低于原生应用或其他框架,可能需要更多针对平台的调整。
- Ionic:
- 基于Cordova,使用Angular, React或Vue与Web技术来构建应用。
- 优点:使用熟悉的Web技术栈,组件丰富,有用于构建PWA(渐进式Web应用)的能力。
- 缺点:性能通常不如完全原生的解决方案
介绍一下Http2的优势
HTTP/2 提供了比HTTP/1.x更高的效率和更快的加载时间,主要优势包括:
- *多路复用 (Multiplexing)**:在同一个连接中同时发送多个请求和响应,减少了因多个TCP连接而产生的延迟。
- *头部压缩 (Header Compression)**:HTTP/2 使用HPACK压缩格式减少了请求和响应头的大小,进一步减少了延迟。
- *服务器推送 (Server Push)**:服务器可以对一个客户端请求发送多个响应。这允许服务器在客户端需要之前就提前发送资源,缩短了页面加载时间。
- *请求优先级 (Request Prioritization)**:不同的资源可以设置不同的优先级,使得关键资源(如CSS文件)先于其他资源加载。
- *流控制 (Flow Control)**:针对每个流的流量控制可以更好地管理并发,以防止接收方被发送方淹没。
- 更好的安全性:HTTP/2 的设计鼓励使用TLS(传输层安全性协议),提高了通信的安全性。
原子化CSS的好处和坏处
好处:
- 可维护性:小的、专一的工具类使得代码更易于维护,因为你通常不需要编写新的CSS,只需重新组合已有的类。
- 一致性:重用相同的原子类可以保持样式的一致性。
- 性能:因为类的重用率高,文件大小相对较小,可以提高页面加载速度。
- 响应式设计更简单:轻松添加或删除原子类来调整不同屏幕尺寸的布局。
- 避免过度复杂的CSS:降低了与层叠、继承相关的复杂性和意外的副作用。
坏处:
- 可读性:由于大量使用短类名,HTML结构阅读起来可能会感觉冗杂且难以理解。
- 学习曲线:理解和熟悉所有的原子类名需要时间。
- 自定义化困难:可能难以应对一些特殊的设计要求,且在某些情况下需要退回到传统的CSS。
- 工具类的过量使用:可能导致HTML中类的数量急剧增加,使得从视觉上区分元素变得困难。
tree-shaking原理
Tree-shaking 的原理是通过工具(如Webpack或Rollup)在构建过程中静态分析模块之间的依赖关系,识别并移除JavaScript项目中未被使用的代码。关键步骤包括:
- *导入分析 (Import Analysis)**:工具分析ES6模块的导入语句,建立起模块间的依赖图。
- *未使用代码识别 (Dead Code Identification)**:分析代码,确定哪些导出被导入并使用,哪些没有。
- *代码剔除 (Unused Code Elimination)**:自动删除未被使用的导出,减少打包后文件的体积。
Tree-shaking 的前提是使用ES6模块语法(import 和 export),因为这是目前JavaScript中唯一静态的模块系统,它允许在编译时分析代码,从而实现tree-shaking。
用flex实现居中布局,flex怎么对子元素排序
Flex布局可以通过**order属性对子元素进行排序。默认情况下,子元素的order值为0,可以通过修改order**属性来改变子元素的排序顺序
transiton和animation的区别
- 实现方式:**
transition是通过变换CSS属性的值来实现过渡效果;而animation**是通过定义关键帧来描述动画序列的每一帧。 - 控制程度:**
transition控制程度较低,只能在CSS属性值发生变化时生效,并且只能指定开始状态和结束状态;而animation**可以精确地控制动画的每一帧,包括开始、中间和结束状态。 - 复杂度:**
animation相对于transition**更加复杂,因为它需要定义关键帧和动画序列,但同时也更加灵活,可以实现更复杂的动画效果。 - 兼容性:**
transition在各种浏览器中的兼容性较好,而animation**在一些旧版本的浏览器中可能不够稳定。
git怎么代码回滚,两个的区别是什么?

题目:设计一个的数 fetchAndProcessData,接受一个 URL 数组作为参数,该函数应该实现以下功能:
1.从每个 URL 获取数据(假设每个 URL 返回一个数字)。2.对获取的所有数字进行平均值计算,并返回结果的 Promise。3.如果任何一个 URL 的请求失败(HTTP 状态码不是 200),则应该立即返回一个拒绝的Promise,并输出相应的错误信息
// URL 数组
const urls = [
'https://api.exampie.com/number/1',
'https://api.example.com/number/2',
'https://api.example.com/number/3',
];
// 使用 fetchAndProcessData 函数
fetchAndProcessData(urls)
.then((result) => {
console.log('Average of numbers:', result); // 输出平均值
})
.catch((error) => {
console.error('Error:', error); // 输出错误信息
});
function fetchAndProcessData(urls) {
// 创建一个 Promise 数组来存储每个 URL 的 fetch 请求
const promises = urls.map(url => {
return fetch(url)
.then(response => {
// 如果响应状态码不是 200,抛出一个错误
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 返回响应体的 JSON 数据
return response.json();
})
.then(data => {
// 返回每个 URL 请求的数字
return data.number;
});
});
// 使用 Promise.all 方法等待所有请求完成
return Promise.all(promises)
.then(numbers => {
// 计算平均值并返回结果的 Promise
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
return sum / numbers.length;
})
.catch(error => {
// 如果任何一个 URL 的请求失败,立即返回一个拒绝的 Promise,并输出错误信息
console.error('Error:', error);
throw error;
});
}
// URL 数组
const urls = [
'https://api.example.com/number/1',
'https://api.example.com/number/2',
'https://api.example.com/number/3'
];
// 使用 fetchAndProcessData 函数
fetchAndProcessData(urls)
.then(result => {
console.log('Average of numbers:', result); // 输出平均值
})
.catch(error => {
console.error('Error:', error); // 输出错误信息
});
KK集团一面
自我介绍,然后围绕着简历来问,因为是面试的前端管培生,前面问了很多类似产品经理的问题,比如有没有作为一个项目负责人,然后自己是怎么做的之类的话,后面就问前端相关的一些问题了
低代码平台的缺点
幽灵依赖
幽灵依赖指的是应用程序在运行时会意外引入一些未在package.json中声明的依赖包。这些依赖通常是开发者无意间通过嵌套依赖的方式引入的。幽灵依赖会导致应用程序在不同环境下表现不一致,增加了维护和调试的难度
幽灵依赖问题通常发生在前端项目中,特别是当项目依赖于某些包(npm包或其他)时。问题的成因是,当一个模块A依赖另一个模块B,但是在模块A的package.json文件中没有显式声明对模块B的依赖,而是依靠其他方式间接获得模块B。
这个问题可能是因为以下原因造成的:
- 模块A的开发者在本地开发时,已经全局安装了模块B,所以没有意识到需要将其添加到项目的依赖中。
- 模块B可能是项目中另一个模块的依赖项,因此被间接地安装了。
- 在不同开发环境或者不同版本的包管理工具可能导致依赖安装的不一致性。
解决幽灵依赖的方法:
- 审查
package.json:确保所有用到的包都被正确地添加到dependencies或devDependencies中。 - 使用锁文件(如
package-lock.json或yarn.lock),确保依赖版本的一致性。 - *持续集成(CI)**测试:在干净的环境中运行安装和测试流程,以发现任何未声明的依赖项。
- 运用如
npm ls的命令检查项目的依赖树,来识别和定位未声明的依赖。 - 使用像
depcheck或npm-check这类工具,来帮助识别未使用或缺失的依赖。
pnpm是怎么解决幽灵依赖问题的,pnpm就不会有幽灵依赖问题吗
pnpm 是一个包管理工具,解决幽灵依赖问题的方式是通过使用一个称为内容寻址文件系统的机制。在这个文件系统中,每个包的存储位置是基于其版本的哈希值。这要求项目只能使用其 package.json 中声明的依赖。因此,一个包无法意外地访问到未在其 package.json 显式声明的包。
pnpm 的关键特性包括:
- 硬链接和符号链接:减少重复存储,保持每个版本的包只有一个副本,同时保证了每个项目有独立的
node_modules结构。 - 严格的依赖检查:项目无法访问未声明的依赖,这样就消除了幽灵依赖问题。
还要注意的是,即使使用了 pnpm,开发者仍需维护package.json中的准确性,包括添加、更新或删除依赖。
qiankun实现原理
qiankun 是一个基于 single-spa 的微前端框架,提供了一种在前端架构中整合多个独立团队开发的应用的能力。
qiankun 的实现原理包括以下关键点:
- 沙箱机制:为了确保主应用和微应用之间相互隔离,
qiankun为每个微应用创建了一个JavaScript沙箱环境。当微应用运行时,它们在这个沙箱环境中执行,避免了全局变量的冲突。 - 生命周期钩子:
qiankun提供了一系列的生命周期钩子,如bootstrap,mount,unmount等,允许微应用在不同的阶段执行相应的逻辑。 - 资源加载:利用动态
import或SystemJS,qiankun能够异步加载微应用的代码和资源,并在适当的时机执行它们。 - 样式隔离:
qiankun使用CSS隔离的技术,比如CSS-in-JS或者Shadow DOM,确保微应用的样式不会相互干扰。 - 通讯机制:主应用和微应用之间,或者不同微应用之间,可以通过
qiankun提供的全局状态管理、CustomEvent,Props等方式进行数据和状态的传递和通讯。
qiankun相比ifream
Qiankun
Qiankun 是基于 Single-spa 的一套微前端实现库,提供了更加方便的微前端解决方案。它支持主应用和微应用之间的技术栈无关,使得不同团队可以选择适合自己的框架进行开发。
优点:
- 技术栈无关性:可以在同一个页面中使用 React、Vue、Angular 等不同的前端技术栈。
- 更好的性能:相比于 iframe,qiankun 加载微应用的方式更加高效,因为资源可以共享,且避免了 iframe 的额外开销。
- 样式隔离:通过 CSS 隔离机制(如 Shadow DOM 或样式前缀),避免了样式冲突。
- JavaScript 沙箱:提供了 JavaScript 执行环境的沙箱机制,保证了主应用和微应用之间的隔离性。
- 生命周期管理:管理微应用的加载、卸载、通信等,提供了完整的生命周期管理。
缺点:
- 复杂度:引入 qiankun 增加了项目架构的复杂度,需要更多的配置和管理。
- 集成成本:不同技术栈间可能存在集成问题,需要一定的调试和兼容性工作。
Iframe
Iframe 是一种将另一个 HTML 页面嵌入到当前页面中的技术,常被用来实现页面间的隔离。
优点:
- 简单易用:几乎所有的浏览器都支持 iframe,无需额外的学习成本,简单的 HTML 标签就能实现。
- 隔离性:提供了很好的隔离性,包括样式隔离、JavaScript 运行环境隔离等,避免了不同应用间的直接冲突。
- 安全性:由于隔离性的特点,iframe 可以较好地防止 XSS 攻击等安全问题。
缺点:
- 性能开销:每个 iframe 都是一个独立的页面,会加载独立的 HTML、CSS、JavaScript 文件等,导致资源不能共享,增加了额外的性能开销。
- 样式和交互限制:父页面和 iframe 之间的交互比较有限,样式整合和交互设计可能需要额外的工作。
- SEO影响:搜索引擎可能不会抓取或不正确地抓取 iframe 中的内容,影响 SEO。
子应用和主应用之间怎么样式隔离
样式隔离主要解决的问题是避免CSS冲突,确保一个应用的样式不会意外地应用到另一个应用上。有几种常见的方法可以实现样式隔离:
1. CSS 命名空间(BEM、CSS Modules)
BEM(Block Element Modifier) 是一种CSS命名约定,通过给类名增加特定的前缀或者命名规则来确保样式的唯一性。例如,子应用的所有CSS类名都可以以应用名作为前缀。
CSS Modules 是一种在构建过程中自动生成唯一类名的技术。每个JSX文件引入的CSS文件都会被转换成一个对象,其中包含了转换后的唯一类名。这样,即使两个应用中包含相同的类名,构建后也会生成不同的唯一类名,从而避免冲突。
2. Shadow DOM
Shadow DOM 提供了一种封装技术,允许将隐藏的DOM树附加到元素上,与主文档DOM隔离开来,包括样式和脚本。在支持Web Components的框架中,可以利用Shadow DOM来实现样式隔离。
3. CSS-in-JS 库
CSS-in-JS 是一种将样式直接写入JavaScript中的技术,例如Styled-components或Emotion等库。这些库通常会为每个样式生成唯一的类名,并且可以通过JavaScript控制样式的应用范围,从而实现隔离。
4. Web Components
Web Components 可以实现真正的封装和隔离。每个Web Component都有自己的Shadow DOM,其中的样式不会泄露到外部,外部样式也不会影响到内部,实现了完美的隔离。
5. 使用特定的构建工具或加载器
使用像webpack这样的构建工具,通过配置确保生成的CSS文件包含足够的特异性,以避免命名冲突。例如,可以配置CSS loader以在CSS类名中包含更多的局部信息(如文件路径),从而增加类名的唯一性。
在Qiankun中实现样式隔离
Qiankun 为实现样式隔离提供了一些内置机制,包括:
- 沙箱机制:默认情况下,Qiankun 为每个微应用提供了一个JavaScript沙箱环境。对于样式隔离,虽然JavaScript沙箱本身不直接隔离CSS,但它限制了微应用对全局对象的影响,间接帮助实现隔离。
- 样式隔离策略:开发者可以结合Qiankun的生命周期钩子,在微应用挂载时动态添加CSS,卸载时移除,或利用上述任一技术手段(如CSS Modules、Shadow DOM等)确保样式的隔离性。
前端开发怎么实现埋点
在前端开发中,埋点通常指的是通过代码在特定用户行为或者生命周期事件上收集数据的手段,以便于跟踪和分析用户与应用的交互。这里有几种方法可以实现前端埋点:
手动埋点:直接在代码中插入埋点逻辑,捕获事件并发送到服务器。例如,可以在点击事件处理函数中添加代码来发送数据。
button.addEventListener('click', function() { // 捕获点击事件,并发送数据到服务器 sendDataToServer({ eventType: 'buttonClick', ... }); });自动埋点:通过一段自动化脚本来捕捉用户所有的交互行为,而无需手动为每个事件编写埋点代码。自动化埋点通常会配合工具使用,例如Google Analytics。
可视化埋点:通过可视化工具配置埋点事件,不需要写代码即可生成跟踪代码。例如,通过 Google Tag Manager 配置埋点。
代码插装:通过开发构建环境中的工具自动在代码中注入埋点逻辑。例如,可以使用 Webpack 插件在构建时自动为某些行为添加埋点代码。

前端怎么解决跨域问题
- CORS(Cross-Origin Resource Sharing):最普遍的解决方案是服务器设置
Access-Control-Allow-Origin响应头,允许特定的外部域访问资源。 - JSONP(JSON with Padding):通过
<script>标签获取非同源的JSON数据。这是一种老旧的技术,它限制了只能执行GET请求。 - 代理服务器:在服务端设置一个代理来转发请求,这样请求就会避免直接从浏览器发出,可以绕过同源策略的限制。
- document.domain:这个只能用于一级域名相同的情况,二级域名不同的跨域问题。通过设置两个页面的
document.domain为相同的一级域名来实现。 - window.postMessage:这是一种安全的将数据跨域传输的方法,可以用于不同窗口间的通信。
- CORS任何地方:开发中可以使用一些服务,比如一个Chrome扩展,来允许任意跨域请求,但这不适用于生产环境。
- WebSockets:由于
WebSockets不实施同源策略,因此,一旦建立了连接,就可以通过它跨域通信。
说一下http缓存
客户端缓存
- 浏览器缓存:浏览器会将经过 HTTP 协议传输的资源(如 HTML、CSS、JavaScript、图片等)保存到本地缓存中,以便在之后的访问中直接从缓存中获取资源,而不需要重新请求服务器。
- 缓存控制头:服务器可以通过在响应头中设置缓存控制头来控制客户端缓存行为。常见的缓存控制头包括:
Cache-Control:指定缓存的行为,如**max-age**指定资源在客户端缓存中的有效期。Expires:指定资源的过期时间,是一个绝对时间,不适用于较新的 HTTP/1.1 版本。
服务器端缓存
- 代理服务器缓存:代理服务器(如 CDN、反向代理等)可以缓存服务器返回的响应,以降低对源服务器的请求压力,并加速用户对资源的访问。
- 缓存控制头:服务器通过在响应头中设置缓存控制头来控制代理服务器的缓存行为,常见的缓存控制头包括:
Cache-Control:指定缓存的行为,如**public**表示允许代理服务器缓存响应。Expires:指定资源的过期时间。ETag和Last-Modified:用于标识资源的唯一性和修改时间,配合条件请求进行资源验证。
缓存验证和更新
- 条件请求:客户端可以通过发送条件请求(如 If-None-Match、If-Modified-Since)来验证缓存的响应是否仍然有效,如果有效则服务器返回 304 Not Modified 状态码,客户端可以直接从缓存中获取资源。
- 强制刷新:客户端可以通过一些手段(如在地址栏中按下 F5、使用 Ctrl + F5 强制刷新)来忽略缓存,强制从服务器重新获取资源。
缓存策略
- 强缓存:如果缓存命中,浏览器直接从本地缓存中获取资源,不需要发送请求到服务器,适用于资源稳定不经常更新的情况。
- 协商缓存:如果缓存失效,浏览器发送条件请求到服务器进行资源验证,服务器根据资源的 ETag 或 Last-Modified 判断资源是否已更新,如果未更新,则返回 304 Not Modified 状态码,浏览器继续使用缓存资源。
浏览器事件循环(Even Loop)
- 执行栈(Execution Context Stack):
- 当执行 JavaScript 代码时,会创建一个执行上下文(Execution Context),并将其推入执行栈中。
- JavaScript 是单线程语言,一次只能执行一个任务,执行栈保证了代码的执行顺序。
- 任务队列(Task Queue):
- 当遇到异步任务时(如定时器、事件监听器、Ajax 请求等),浏览器会将该任务加入到任务队列中,而不是立即执行。
- 任务队列分为宏任务队列(macrotask queue)和微任务队列(microtask queue)。
- 事件循环(Event Loop):
- 当执行栈为空时,事件循环会不断地从任务队列中取出任务执行,直到所有任务执行完毕。
- 事件循环会先执行微任务队列中的所有任务,然后再执行宏任务队列中的一个任务,如此循环。
- 微任务队列(Microtask Queue):
- 微任务队列中的任务优先级高于宏任务队列,会在每个宏任务执行完毕后立即执行。
- 常见的微任务包括 Promise 的回调函数、MutationObserver 的回调函数等。
- 宏任务队列(Macrotask Queue):
- 宏任务队列中的任务包括 setTimeout、setInterval、requestAnimationFrame、I/O 操作、用户交互事件等。
- 宏任务队列中的任务会在当前执行栈中的任务执行完毕后执行。
webpack 热更新原理
● 通过 webpack-dev-server 创建两个服务器:提供静态资源的服务(express)和 Socket 服务
● express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
● socket server 是一个 websocket 的长连接,双方可以通信
● 当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest 文件)和.js 文件(update chunk)
● 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
● 浏览器拿到两个新的文件后,通过 HMR runtime 机制,加载这两个文件,并且针对修改的模块进行更新
Webpack热更新优化方式
使用 Hash 命中缓存
- 内容哈希(Content Hashing): 当文件内容发生变化时,文件名中的哈希值会改变,这样可以保证浏览器只有在文件内容实际发生变化时才会重新下载文件。这是利用缓存的一个非常有效的方法。
使用 Hash 命中缓存的具体做法主要涉及到Webpack配置中的输出(output)部分,其中文件名(filename)和块文件名(chunkFilename)的命名策略是关键。通过在文件名中包含一个基于文件内容的哈希值,可以确保每当文件内容变化时,其URL也随之变化,从而使得浏览器缓存失效并重新请求新的文件。下面是一些具体的步骤和示例:
1. 配置输出文件名
在Webpack配置文件(通常是**webpack.config.js**)中,你可以为输出文件设置包含哈希的名称。Webpack提供了几种不同类型的哈希供选择:
[hash]:每次构建过程中生成的唯一的hash值。适用于所有文件共享同一个哈希值。[contenthash]:根据文件内容生成的hash值,文件内容不变,则**contenthash**不变。推荐用于生产环境,因为它可以更精确地控制缓存。[chunkhash]:基于块(chunk)的内容生成的hash值。
示例配置
// webpack.config.js
module.exports = {
// 其他配置...
output: {
path: path.resolve(__dirname, 'dist'), // 输出目录
filename: '[name].[contenthash].js', // 用于入口文件
chunkFilename: '[name].[contenthash].js' // 用于非入口块文件(如使用了代码分割)
},
// 其他配置...
};
2. 清除旧的打包文件
每次构建时,由于文件名包含哈希值,可能会在输出目录中留下旧的文件。使用**clean-webpack-plugin可以在每次构建前自动清理dist**目录,以确保目录中只有最新的文件。
安装 clean-webpack-plugin
npm install clean-webpack-plugin --save-dev
配置插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
// 其他配置...
plugins: [
new CleanWebpackPlugin(),
// 其他插件...
]
};
3. 其他相关配置
确保你的Web服务器或CDN配置正确,以允许浏览器根据文件名中的哈希值来缓存静态资源。此外,如果你使用长期缓存策略,考虑设置合理的**Cache-Control头部,例如max-age或immutable**,来进一步控制缓存行为。
微派二面
基本上就是聊天,没怎么问八股,然后一个代码题卡了很久很久,让实现一个字典树,麻了
HTTP几种请求方式,特别问到了OPTION
- GET:请求指定的资源。通常用于检索信息,而没有副作用。
- POST:向指定资源提交数据进行处理请求(如表单提交)。数据包含在请求体中。POST 请求可能会导致新的资源的创建或已有资源的修改。
- PUT:向指定资源位置上传其最新内容,完整替换指定的资源。
- DELETE:请求服务器删除指定资源。
- HEAD:与 GET 方法相同,但响应中没有具体内容,用于获取报头。
- CONNECT:建立一个到由目标资源标识的服务器的隧道。
- TRACE:回显服务器收到的请求,主要用于测试或诊断。
- PATCH:对资源进行部分修改。
你特别提到 OPTIONS 请求方法,以下是其详情:
- OPTIONS:
- 这个方法用于获取目的资源所支持的通信选项。
- 客户端可以使用 OPTIONS 方法询问服务器或特定的资源有哪些可用的方法,或者进行跨域资源共享(CORS)中的预检请求(preflight request)。
- 预检请求用于在实际发送跨域请求前检查服务器是否允许该跨域请求,包括服务器允许的方法、自定义的头信息等。
例如:
OPTIONS /resource/foo
Access-Control-Request-Method: POST
Access-Control-Request-Headers: origin, x-requested-with
Origin: https://foo.bar.org
代码题:
实现一个搜素建议方法suggest(prefix) 实现通过字符串前缀从数组中找出搜索建议列 表。写出你知道所有前缀匹配的实现方式,代码 尽可能简洁,js实现。 例如: const list = [ 'good', 'googood', 'goods', 'google', 'googleMap', 'googleEarth', 'google+', '百度搜索' ] suggest('good', list) //output [ 'good', 'goods' ]
function insertWord(root, word) {
let node = root;
for (let char of word) {
if (!node[char]) {
node[char] = {};
}
node = node[char];
}Ï
node['end'] = word; // 标记单词结束,并存储单词
}
KK集团二面
全程拷打,感觉有点不尊重人,语气咄咄逼人,HR更是重量级
Http1.0和1.1的区别,前端又是怎么控制长连接的
HTTP/1.0 和 HTTP/1.1 的区别:
- 持久连接(长连接):
- HTTP/1.0:每个HTTP请求/响应完成后,TCP连接会立即关闭,导致每个请求都需要建立新的TCP连接,增加了连接管理和延迟。
- HTTP/1.1:默认启用持久连接,允许多个HTTP请求/响应共享同一个TCP连接,减少了建立和关闭连接的开销,提高了性能。
- 请求头 Host 字段:
- HTTP/1.0:不要求必须包含 Host 请求头字段,但通常仍然会包含。
- HTTP/1.1:必须包含 Host 请求头字段,用于指定目标服务器的域名或IP地址,支持虚拟主机的部署。
- 缓存控制:
- HTTP/1.0:缓存控制依赖于响应头中的 Expires 字段来指定资源过期时间。
- HTTP/1.1:引入了更强大的缓存控制机制,通过 Cache-Control 字段来指定缓存策略,支持更灵活的缓存管理。
- 管道化(Pipeline):
- HTTP/1.0:不支持请求的管道化,即不能同时发送多个请求在同一个连接上,每个请求必须等待前一个请求完成。
- HTTP/1.1:支持请求的管道化,允许客户端在同一个连接上发送多个请求,并且不需要等待前一个请求的响应,提高了并行处理能力。
在HTTP/1.1中,持久连接(Keep-Alive)是一项重要的特性,它允许客户端和服务器在单个TCP连接上发送和接收多个HTTP请求和响应,而不必为每个请求都建立新的连接。
Keep-Alive头字段:为了启用持久连接,客户端和服务器在HTTP请求和响应头中都可以包含 Keep-Alive 字段。例如:
- 客户端请求头:
Connection: keep-alive - 服务器响应头:
Connection: keep-alive
常见的请求头有哪些
- Host:指定服务器的主机名和端口号,告诉服务器请求的目标地址。
- User-Agent:标识客户端的应用程序类型、操作系统、软件厂商等信息。
- Accept:告诉服务器客户端能够处理的媒体类型及其优先级,用于内容协商。
- Accept-Language:指定客户端接受的自然语言(例如:en, zh-CN)及其优先级。
- Accept-Encoding:指定客户端能够接受的内容编码方式(例如:gzip, deflate)及其优先级。
- Connection:控制是否保持持久连接(Keep-Alive),常见的取值有"keep-alive"和"close"。
- Referer:告诉服务器请求的来源页面 URL,用于追踪链接来源。
- Cookie:包含客户端的 Cookies,用于跟踪用户状态和会话信息。
- Authorization:用于在客户端向服务器发送请求时提供身份验证信息。
- Content-Type:指定请求体的MIME类型,用于告诉服务器请求发送的数据类型。
- Content-Length:指定请求体的长度,以字节为单位。
- If-Modified-Since:用于条件请求,告诉服务器只在内容在指定时间后发生变化时才返回资源。
- Cache-Control:用于控制缓存行为,指定缓存是否可以存储、响应是否可以被共享等。
- Pragma:用于设置特定的指令,通常用于控制缓存行为,已被Cache-Control取代。
- Upgrade-Insecure-Requests:告知服务器客户端希望升级到HTTPS,用于保护用户隐私。
跨域问题是因为什么产生的呢?
跨域问题是由浏览器的同源策略(Same-Origin Policy)引起的。同源策略是浏览器的一种安全策略,它限制了一个网页中加载的文档或脚本与另一个源(域名、协议或端口)的交互,只有当两个文档的源完全相同(即同源),浏览器才允许它们之间进行交互。
具体来说,同源策略限制了以下几种跨域行为:
- 跨域请求资源:例如,通过 XMLHttpRequest 或 Fetch API 发起跨域请求,加载不同源的资源(例如图片、脚本、样式表等)。
- 跨域写入Cookie:例如,通过 JavaScript 在不同域的页面中写入Cookie,或者通过跨域请求携带 Cookie。
- 跨域访问DOM:例如,通过 JavaScript 获取不同域页面的DOM元素,或者在不同域的页面中执行脚本。
跨域问题的产生是为了保护用户的隐私和安全,防止恶意网站利用用户的浏览器对其他网站进行攻击或者窃取用户的信息。然而,在一些特定的场景下,例如跨域资源共享(CORS)或者JSONP等机制,可以通过特定的方式来解决跨域问题,允许安全地进行跨域通信。
token存储在localStorage和cookie的区别
- 安全性:
- Cookie:Cookie 存储在客户端的请求头中,在每次请求中都会被发送到服务器,因此存在被窃取的风险。此外,Cookie 存储在客户端的文件系统中,如果网站受到 XSS 攻击,攻击者可能能够获取到Cookie的值。
- LocalStorage:LocalStorage 存储在客户端的浏览器中,只能通过JavaScript来访问。虽然它的数据不会被自动发送到服务器,但如果网站受到 XSS 攻击,攻击者也可以通过JavaScript来获取LocalStorage中的值。
- 可访问性:
- Cookie:Cookie 存储在浏览器的Cookie中,因此可以被浏览器自动管理,并且在每次 HTTP 请求中都会被发送到服务器,可以在服务器端进行处理。
- LocalStorage:LocalStorage 存储在浏览器中,仅能通过JavaScript访问。它不会自动发送到服务器,需要手动将令牌添加到请求头中才能发送到服务器。
- 性能:
- Cookie:Cookie 存储在浏览器中,并且会在每次请求中被发送到服务器,因此对于每个请求来说,Cookie 都会增加请求头的大小,可能会影响到请求的性能。
- LocalStorage:LocalStorage 存储在客户端的浏览器中,不会随每次请求被发送到服务器,因此不会增加请求头的大小,对请求性能的影响较小。
从前端的角度来讲,是怎么实现低代码平台的
- 界面编辑器:可以使用流行的前端框架,如React、Vue.js或Angular等,结合组件库(如Ant Design、Material-UI等)开发一个可视化的界面编辑器。这样的界面编辑器可以使用拖拽、放置和调整组件大小等方式,让用户直观地设计和布局应用程序的界面。
- 组件库:可以使用前端框架自带的UI组件或者定制开发一套丰富的组件库,包括各种常见的UI组件(如按钮、表单、列表等)和业务逻辑组件(如数据表格、图表、地图等),以满足用户对于界面和功能的需求。
- 代码生成器:可以开发一套智能的代码生成器,根据用户在界面编辑器中的操作和配置,自动生成相应的前端代码(如HTML、CSS、JavaScript)和后端代码(如数据库模型、API接口)。代码生成器可以使用模板引擎或者代码生成工具来实现。
- 实时预览和调试:可以使用热重载技术和实时数据绑定等技术,实现界面编辑器和应用程序实时预览的功能。用户可以即时查看他们在界面编辑器中的操作在应用程序中的效果,并进行调试和修改。
- 版本控制和发布管理:可以使用版本控制系统(如Git)和持续集成/持续部署(CI/CD)工具,实现应用程序的版本控制、发布管理和自动化部署。用户可以通过界面编辑器管理应用程序的不同版本,并进行发布、回滚等操作。
- 扩展性和定制化:可以提供插件化或者模块化的架构,允许用户自定义组件、插件或者集成第三方服务。用户可以通过扩展机制来定制和增强平台的功能,以满足不同场景下的需求。
猿辅导一面
被拷打惨了,主要是手写题太多了😭八股问的很简单
说一下vue3和vue2的区别
vite比webpack快的原因
- 开发模式下的快速热更新:
- Vite 使用了基于 ESM(ES Module)的开发服务器,可以在开发模式下实现快速的热更新,无需重新打包整个应用,只需更新修改的部分,这样可以大大提高开发效率。
- ESM 的原生支持:
- Vite 基于原生的 ES 模块,在开发阶段不需要将所有模块打包成一个文件,而是能够利用浏览器原生的模块加载能力,这使得加载速度更快。
- 预构建:
- Vite 在生产环境下使用了预构建的技术,将应用的依赖关系提前解析并且构建成优化的代码,这样可以减少打包时间和构建产物的体积。
- 按需编译:
- Vite 仅在需要时编译所需的代码,而不是像 Webpack 那样在构建时将所有代码都编译,这样可以减少不必要的编译时间。
- 内置优化:
- Vite 内置了对 Vue、React 等框架的优化支持,能够针对不同框架提供更高效的构建和优化。
总的来说,Vite 的快速来自于对现代浏览器特性的充分利用,以及对开发和构建过程的优化,使得它在开发体验和构建速度上比传统的 Webpack 更快。
说一下浏览器输入url到页面渲染出来的过程
- DNS 解析:
- 浏览器首先需要将输入的 URL 解析成对应的 IP 地址,这一过程称为 DNS 解析。浏览器会先检查本地 DNS 缓存,如果未找到,则向操作系统的 DNS 缓存查询,最后会向 DNS 服务器发送请求获取对应的 IP 地址。
- 建立 TCP 连接:
- 一旦浏览器获取到服务器的 IP 地址,它会尝试通过 TCP 协议与服务器建立连接。这个过程经历了三次握手,确保客户端与服务器之间的连接建立稳定。
- 发起 HTTP 请求:
- 一旦 TCP 连接建立成功,浏览器会向服务器发送 HTTP 请求。这个请求包含了要访问的资源的信息,如网页的 HTML 文件、CSS、JavaScript 文件等。
- 服务器处理请求并返回响应:
- 服务器收到 HTTP 请求后,会根据请求的内容进行相应的处理,然后将处理结果封装成 HTTP 响应返回给浏览器。
- 浏览器接收响应并渲染页面:
- 浏览器收到服务器返回的响应后,会根据响应头中的 Content-Type 等信息确定响应的类型(如 HTML、CSS、JavaScript 等),然后开始解析相应类型的内容。
- 如果响应类型是 HTML,则浏览器会解析 HTML 文档,构建 DOM 树,并开始加载文档中引用的外部资源(如 CSS、JavaScript)。
- 解析 HTML 过程中,遇到 CSS 和 JavaScript 会发出新的请求,继续下载这些资源。同时,浏览器会根据 CSS 构建 CSSOM 树,将 DOM 树和 CSSOM 树结合起来,形成渲染树(Render Tree)。
- 最后,浏览器根据渲染树开始进行布局(Layout)和绘制(Paint),将页面内容呈现在用户的浏览器窗口中。
- 页面加载完成:
- 当页面的所有资源都加载完成,并且渲染完成后,浏览器会触发 DOMContentLoaded 事件,表示页面的 DOM 结构已经加载完毕,但可能还有一些外部资源(如图片等)未加载完成。当所有资源都加载完成后,浏览器会触发 load 事件,表示页面加载完成。
script标签的属性
- src:
- 指定外部 JavaScript 文件的 URL,从而让浏览器下载并执行该文件中的 JavaScript 代码。
- type:
- 指定脚本的 MIME 类型,通常为 "text/javascript",但 HTML5 规范中规定该属性可以省略,因为 JavaScript 是 HTML5 中的默认脚本类型。
- async:
- 当该属性存在时,脚本会异步加载,不会阻塞页面的加载和渲染,但脚本加载完成后会立即执行。
- 注意:多个异步脚本之间的执行顺序是不确定的,可能会出现并发执行的情况。
- defer:
- 当该属性存在时,脚本会延迟加载,直到 HTML 解析完成后才会执行,但会在 DOMContentLoaded 事件触发前执行。
- 与 async 不同的是,多个延迟脚本会按照它们在文档中出现的顺序依次执行。
- charset:
- 指定脚本文件的字符编码,通常是 "UTF-8"。
- 注意:这个属性在 HTML5 中已经不再推荐使用,因为现代浏览器会自动识别字符编码。
- nonce:
- 用于 Content Security Policy (CSP) 验证的随机值,确保外部脚本的安全性。
- language:
- 早期用于指定脚本语言的属性,但自 HTML5 起已经废弃不再使用。
- integrity:
- 用于 Subresource Integrity (SRI) 验证,确保脚本在下载和执行过程中不会被篡改。
说一下回流和重绘
改变font-size会触发什么
改变 font-size 属性通常会触发浏览器的重绘(Repaint)操作,而不是回流(Reflow)操作。因为修改字体大小不会影响元素的布局,只会影响元素的可见样式。
具体来说,改变 font-size 属性会导致以下情况发生:
- 重绘(Repaint):
- 浏览器会重新绘制受影响的文本内容,以更新文本的字体大小。
- 由于重绘只涉及到可见样式的改变,而不会影响布局,所以它的代价相对较小,页面的性能影响也较小。
- 文本重新流动:
- 文本内容重新流动是指当字体大小改变时,周围的文本内容可能会重新调整位置,以适应新的字体大小。这个过程不涉及整个页面的重新布局,因此不会引起回流,仅涉及到文本的重新渲染。
说一下协商缓存
ETag对应的字段
ETag(Entity Tag)是HTTP头中的一个字段,用于标识资源的特定版本。ETag 对应的字段是 ETag。当服务器返回资源时,可以在响应头中包含 ETag 字段,用于唯一标识该资源的版本。例如:
ETag: "abcdef123456"
盒模型
说一下事件循环
有一个队列 1 2 3 4 5 ,执行3 产生了一个微任务6 ,任务6放哪个队列
在执行任务3产生微任务6的情况下,微任务6会被放入当前的微任务队列中,而不会被放入其他队列中。
看代码说输出
<script>
async function async1(){console.log('async1 start');await async2();console.log('async1 end');}
async function async2(){console.log('async2');}
console.log('script start');
setTimeout(function(){console.log('setTimeout');},0)
async1();
new Promise(function(resolve){console.log('promise1');resolve()}).then(function(){console.log('promise2');})
console.log('script end');
</script>
- 执行
<script>标签中的同步代码。- 打印 "script start"。
- 调用
setTimeout()创建一个定时器,但不会立即执行其中的回调函数。 - 调用
async1()函数。 - 创建一个 Promise 实例,打印 "promise1",并立即执行
resolve(),触发 Promise 的状态变更。 - 打印 "script end"。
async1()函数开始执行:- 打印 "async1 start"。
- 调用
async2()函数。
async2()函数开始执行:- 打印 "async2"。
async2()函数执行完毕。
async1()函数继续执行:- 打印 "async1 end"。
new Promise()中的 Promise 对象状态已经是 resolved,触发了.then()中的回调函数。- 打印 "promise2"。
- 执行微任务队列中的微任务,即
.then()中的回调函数。- 打印 "promise2"。
- 宏任务队列中的定时器回调函数被调用。
- 打印 "setTimeout"。
实现flatmap
实现一下.flatMap const arr=[1,2,3,4,5] arr.flatMap(() => ([v,v+v])) 输出结果: [1,2,2,4,3,6,4,8,5,10]
const arr = [1, 2, 3, 4, 5];
// 自定义 flatMap 函数
Array.prototype.myFlatMap = function(callback) {
return this.reduce((acc, cur, index, array) => {
const mapped = callback(cur, index, array);
return acc.concat(mapped);
}, []);
};
// 使用自定义的 flatMap 函数
const result = arr.myFlatMap(v => [v, v + v]);
console.log(result); // 输出: [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]
