百度
刀刀
3/20/2025
0 字
0 分钟
一面
cookie、localstorge、sessionStorge 区别?并且使用 localstorge、sessionStorge有什么问题
Cookie、localStorage 和sessionStorage 是前端用于在客户端存储数据的三种机制,它们在用途和特性上有一些区别。
- Cookie:
- Cookie 是存储在客户端的小型文本数据,会随着每次 HTTP 请求发送到服务器。
- 通常用于存储会话信息、用户偏好设置等,具有一定的过期时间。
- 可以通过 JavaScript 的
document.cookie
来访问和设置。
- localStorage:
- localStorage 是 HTML5 提供的持久化存储机制,数据保存在客户端,不会随着 HTTP 请求发送到服务器。
- 通常用于长期存储,数据在同一个域名下的所有页面间共享。
- 可以通过 JavaScript 的
localStorage
对象来访问和设置。
- sessionStorage:
- sessionStorage 也是 HTML5 提供的存储机制,数据也保存在客户端,不会发送到服务器。
- 与 localStorage 不同的是,sessionStorage 中的数据在当前会话结束时会被清除,因此只在当前会话中有效。
- 可以通过 JavaScript 的
sessionStorage
对象来访问和设置。
使用 localStorage 和sessionStorage 时可能存在一些问题,包括:
- 存储容量限制:localStorage 和sessionStorage 的存储容量通常比较大,但仍然受到浏览器的限制,可能会导致存储空间不足的问题。
- 安全性:存储在 localStorage 和sessionStorage 中的数据相对较容易被恶意网站或浏览器插件访问和修改,存在一定的安全风险。
- 跨域限制:localStorage 和sessionStorage 中的数据默认只能在同源(相同协议、域名和端口)的页面中共享,跨域访问会受到限制。
- 兼容性:虽然大多数现代浏览器都支持 localStorage 和sessionStorage,但在一些旧版本浏览器中可能存在兼容性问题。
因此,在使用 localStorage 和sessionStorage 时,需要注意处理存储容量、数据安全性和跨域访问等问题,以确保数据的有效性和安全性。
BFC概念,如何使元素变成BFC
BFC 指的是 Block Formatting Context(块级格式化上下文),它是 CSS 中用来控制块级盒子布局及浮动元素相互影响的一种机制。BFC 具有以下特性:
- 盒子垂直方向的边距会发生重叠。
- BFC 区域不会与浮动盒子重叠。
- BFC 内部的块级盒会根据规则一个个放置,而不会发生浮动。
- BFC 的区域不会被浮动盒覆盖,从而确保盒子内部的元素不会随着浮动元素的变化而移动。
要使一个元素成为 BFC,可以通过以下方法之一:
- 浮动:给元素添加
float
属性可以使其成为 BFC,因为浮动元素会创建一个新的 BFC。 - 定位:使用
position: absolute
或position: fixed
也可以创建一个 BFC。 - 设置 overflow 属性:将元素的
overflow
属性设置为非visible
的值(如auto
、hidden
、scroll
)也可以触发元素的 BFC 特性。 - 使用 display 属性:将元素的
display
属性设置为table-cell
、table-caption
、inline-block
、flex
、inline-flex
、grid
或inline-grid
也可以使其成为 BFC。
通过这些方式,可以使元素成为 BFC,从而控制元素的布局和外部元素的交互。选择哪种方式取决于具体的布局需求以及对元素的影响范围。
选择器种类和优先级,n个低优先级的能否超过高优先级的权重?
在 CSS 中,选择器的优先级用于确定当多个规则应用于同一个元素时,哪个规则将具有最高优先级。选择器的优先级按照特定的权重规则进行计算。
以下是选择器权重的一般排序(从高到低):
- 内联样式(例如:
style="..."
)- 权重值为 1000。 - ID 选择器 - 权重值为 100。
- 类选择器、属性选择器和伪类选择器 - 权重值为 10。
- 元素选择器和伪元素选择器 - 权重值为 1。
- 通配符选择器、子选择器、相邻兄弟选择器和后代选择器 - 权重值为 0。
在计算优先级时,会将每个选择器的权重值相加,然后比较大小以确定优先级。如果两个或多个选择器具有相同的总权重,则后面出现的规则将覆盖前面的规则。
请注意,选择器的数量不会影响优先级,而只考虑选择器的类型和权重。
举个例子:
div p span {
/* 权重值为 3 */
}
#myId {
/* 权重值为 100 */
}
.class1.class2 {
/* 权重值为 20 */
}
p {
/* 权重值为 1 */
}
在上面的例子中,#myId
具有最高的优先级,因为它的权重值为 100。即使有多个低优先级的选择器(例如:.class1.class2
和 p
)相加起来的权重值大于 100,也不能超过 #myId
的权重。
总结来说,选择器的权重决定了 CSS 规则的优先级,而不是选择器的数量。如果存在相同权重的规则,则后面出现的规则会覆盖前面的规则。
js 实现数组去重 ,要求时间复杂度为 0(n)
要实现时间复杂度为 O(n) 的数组去重算法,可以使用哈希表来实现。具体步骤如下:
function uniqueArray(arr) {
var hash = {};
var result = [];
for (var i = 0; i < arr.length; i++) {
if (!hash[arr[i]]) { // 如果哈希表中没有当前元素
hash[arr[i]] = true; // 将当前元素添加到哈希表中
result.push(arr[i]); // 将当前元素添加到结果数组中
}
}
return result;
}
var arr = [1, 2, 3, 4, 2, 3, 1, 5];
var uniqueArr = uniqueArray(arr);
console.log(uniqueArr); // [1, 2, 3, 4, 5]
在这个算法中,使用一个哈希表 hash
来记录已经出现过的元素,然后遍历原始数组 arr
。对于每个元素,如果它不在哈希表中,则将它添加到哈希表和结果数组中,这样就可以保证结果数组中不会有重复元素。由于哈希表的查找操作的时间复杂度为 O(1),所以整个算法的时间复杂度为 O(n)。
这种算法适用于需要快速去重并且不关心元素顺序的情况。如果需要保持原始数组的顺序,可以考虑双指针来实现。具体步骤如下:
function uniqueArray(arr) {
var result = [];
var seen = {}; // 用于记录已经出现过的元素
for (var i = 0; i < arr.length; i++) {
var current = arr[i];
if (!seen[current]) { // 如果当前元素没有出现过
seen[current] = true; // 将当前元素标记为已经出现过
result.push(current); // 将当前元素添加到结果数组中
}
}
return result;
}
var arr = [1, 2, 3, 4, 2, 3, 1, 5];
var uniqueArr = uniqueArray(arr);
console.log(uniqueArr); // [1, 2, 3, 4, 5]
这两个方法的主要区别在于去重后的数组的顺序是否保持不变。
- 使用哈希表的方法:
- 时间复杂度为 O(n)。
- 不关心数组元素的顺序,只关心去重后的结果。
- 哈希表的特性使得重复元素很容易被判断出来,并且添加和查找操作的时间复杂度都是 O(1)。
- 结果数组的元素顺序可能会改变,因为哈希表是无序的。
- 使用双指针的方法:
- 时间复杂度为 O(n)。
- 保持了原始数组的顺序,去重后的数组与原始数组中第一次出现的元素顺序相同。
- 需要额外的空间来记录已经出现过的元素,空间复杂度略高于使用哈希表的方法。
- 双指针遍历数组,对于每个元素通过查询已出现的元素来判断是否重复,查询操作的时间复杂度为 O(1)。
实现一个异步并发控制的方法
实现异步并发控制可以使用Promise、async/await和信号量等方式。下面是一个基于Promise的异步并发控制方法示例:
function asyncConcurrentControl(tasks, limit) {
const results = [];
let runningCount = 0;
let index = 0;
function runTask(task) {
return new Promise((resolve) => {
task().then((result) => {
results.push(result);
resolve();
});
});
}
function next() {
while (runningCount < limit && index < tasks.length) {
const task = tasks[index];
index++;
runningCount++;
runTask(task).then(() => {
runningCount--;
next();
});
}
}
return new Promise((resolve) => {
next();
// 等待所有任务完成
const checkInterval = setInterval(() => {
if (index >= tasks.length && runningCount === 0) {
clearInterval(checkInterval);
resolve(results);
}
}, 10);
});
}
使用示例:
// 模拟异步任务
function asyncTask(id) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Task ${id} finished`);
resolve(id);
}, Math.random() * 1000);
});
}
// 创建一组异步任务
const tasks = [];
for (let i = 1; i <= 10; i++) {
tasks.push(() => asyncTask(i));
}
// 控制并发数量为3
asyncConcurrentControl(tasks, 3).then((results) => {
console.log("All tasks finished");
console.log(results);
});
上述代码中,asyncConcurrentControl
方法接受一个任务数组 tasks
和并发限制数 limit
。它会按照限制的并发数执行任务数组中的异步任务,并返回一个包含所有任务结果的 Promise。在内部,通过递归调用 next
函数来控制并发执行,当任务完成时,会调用 resolve
方法通知主Promise任务完成。最后,通过定时器检查所有任务是否都已完成,如果是则调用 resolve
方法返回结果数组。
async和await是做什么的,async 声明的函数一定是异步函数吗?
async
和 await
是用于处理异步操作的 JavaScript 关键字。
async
关键字用于声明一个函数是异步函数,即该函数内部可能包含异步操作。异步函数会返回一个 Promise 对象,可以通过 await
关键字来等待 Promise 对象的状态变为 resolved,并获取其结果值。
await
关键字用于暂停异步函数的执行,等待一个 Promise 对象的状态变为 resolved。在 await
后面跟着一个 Promise 对象,代码会一直等待该 Promise 对象的状态变为 resolved,并返回 Promise 对象的结果值。
async
声明的函数不一定是异步函数。实际上,async
函数内部可以包含同步的代码。然而,如果在 async
函数内部使用了 await
关键字来等待 Promise 对象,那么该函数就会变成异步函数,因为 await
会阻塞函数的执行,直到 Promise 对象的状态变为 resolved。
普通函数和箭头函数的区别
普通函数和箭头函数在以下几个方面有区别:
- this 指向:在普通函数中,this 的值是动态的,根据函数的调用方式决定。而在箭头函数中,this 的值是继承自外层作用域的,与箭头函数本身无关。
- 匿名函数:箭头函数通常是匿名函数,没有函数名。普通函数可以是具名函数,也可以是匿名函数。
- 构造函数:箭头函数不能用作构造函数,不能使用 new 关键字来创建实例,也没有自己的原型对象。普通函数可以用作构造函数,并且可以通过 new 关键字创建实例。
- 语法简洁:箭头函数具有更简洁的语法,可以省略 function 关键字和大括号,并且有隐式的返回值。普通函数需要使用 function 关键字声明,以及明确的 return 语句来返回值。
- arguments 对象:箭头函数没有 arguments 对象,它们继承了外层作用域的 arguments 对象。普通函数则有自己的 arguments 对象,可以访问传入函数的参数列表。
下面是一些示例代码来演示这些区别:
// 普通函数
function regularFunction() {
console.log(this); // this 指向调用者
console.log(arguments); // 访问参数列表
}
// 箭头函数
const arrowFunction = () => {
console.log(this); // this 继承自外层作用域
console.log(arguments); // arguments 不存在
};
regularFunction(); // 普通函数调用,this 指向全局对象,arguments 存在
arrowFunction(); // 箭头函数调用,this 继承自外层作用域,arguments 不存在
const obj = {
regularMethod: function() {
console.log(this); // this 指向对象本身
},
arrowMethod: () => {
console.log(this); // this 继承自外层作用域,不是指向对象本身
}
};
obj.regularMethod(); // 普通函数作为方法调用,this 指向对象本身
obj.arrowMethod(); // 箭头函数作为方法调用,this 继承自外层作用域
// 构造函数
function RegularConstructor() {
this.name = "Regular";
}
// const instance1 = new RegularConstructor(); // 可以用作构造函数,创建实例
const ArrowConstructor = () => {
this.name = "Arrow";
};
// const instance2 = new ArrowConstructor(); // 不能用作构造函数,会抛出错误
总之,普通函数和箭头函数有不同的语法和行为,使用时需要根据具体的需求选择合适的函数类型。
二面
列表 数据量很大, 如何优化?
当列表数据量很大时,为了提升性能和用户体验,可以考虑以下优化方法:
- 虚拟滚动:虚拟滚动是一种技术,可以实现只渲染可见区域内的列表项,而不是渲染整个列表。这可以显著减少页面中的 DOM 元素数量,从而提高性能。可以使用一些第三方库或框架(如 react-virtualized、vue-virtual-scroller)来实现虚拟滚动。
- 分页加载:将列表数据进行分页,每次只加载当前页需要展示的数据。这样可以减少一次性加载大量数据的压力,提升页面加载速度。用户滚动到下一页时,再异步加载下一页的数据。
- 懒加载:对于列表中的图片或其他资源,可以采用懒加载的方式。只有当列表项进入视口时才加载对应的资源。这可以减少初始加载时的网络请求和页面加载时间。
- 数据缓存:对于静态的列表数据,可以将其缓存在客户端,避免重复的网络请求。可以使用浏览器的缓存机制或者使用本地存储技术(如 localStorage、IndexedDB)来实现数据缓存。
- 增量更新:当列表数据需要频繁更新时,可以考虑采用增量更新的方式,只更新发生变化的部分数据,而不是重新渲染整个列表。
- 虚拟化组件:除了虚拟滚动外,还可以使用其他虚拟化组件来优化列表性能。例如,对于复杂的列表项结构,可以将其拆分为更小的可重用组件,减少 DOM 元素的数量。
- 数据筛选和排序:如果列表支持筛选和排序功能,可以在后端或者前端对数据进行筛选和排序,避免加载大量无用的数据。
以下是一个简单的示例代码,演示了如何使用原生的 Vue 来手动实现虚拟滚动:
<template>
<div
class="virtual-scroll-container"
ref="scrollContainer"
@scroll="handleScroll"
style="height: 300px; overflow-y: auto;"
>
<div :style="{ height: totalHeight + 'px' }">
<div
v-for="(item, index) in visibleItems"
:key="index"
class="virtual-scroll-item"
:style="{ height: itemHeight + 'px', lineHeight: itemHeight + 'px' }"
>
{{ item }}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [], // 数据列表
itemHeight: 50, // 每个列表项的高度
visibleItemCount: 6, // 可见列表项的数量
scrollTop: 0, // 滚动条位置
};
},
computed: {
totalHeight() {
return this.items.length * this.itemHeight;
},
visibleItems() {
const startIndex = Math.floor(this.scrollTop / this.itemHeight);
const endIndex = Math.min(startIndex + this.visibleItemCount + 1, this.items.length);
return this.items.slice(startIndex, endIndex);
},
},
methods: {
handleScroll(event) {
this.scrollTop = event.target.scrollTop;
},
},
};
</script>
<style>
.virtual-scroll-container {
width: 100%;
position: relative;
}
.virtual-scroll-item {
position: absolute;
left: 0;
right: 0;
}
</style>
在上述代码中,我们创建了一个名为 VirtualScroll
的 Vue 组件,它包含一个滚动容器和根据滚动位置计算并渲染可见列表项的逻辑。
在模板中,我们使用一个 <div>
元素作为滚动容器,并根据 totalHeight
动态设置其高度,以确保滚动条的滚动范围正确。然后,我们根据计算属性 visibleItems
返回的可见列表项,使用 v-for
渲染每个列表项。
在 JavaScript 部分,我们定义了一些数据和计算属性,用于跟踪滚动位置、计算可见列表项等逻辑。我们还编写了一个 handleScroll
方法来处理滚动事件,更新滚动位置的状态。
在样式中,我们使用绝对定位将每个列表项放置在正确的位置,并设置滚动容器的样式以及列表项的样式。
通过这种方式,我们手动实现了虚拟滚动的逻辑,只渲染可见区域内的列表项,从而提高了性能。
Map、Set、Object区别
Map、Set 和 Object 都是 JavaScript 中的数据结构,但它们之间有一些不同之处。
Map 是一种键值对集合,其中每个键唯一对应一个值。它的 key 可以是任何类型,包括原始类型和引用类型。Map 的 API 包含了以下方法:
- set(key, value):向 Map 中添加一个键值对;
- get(key):获取指定 key 对应的值;
- has(key):判断 Map 中是否存在指定的键;
- delete(key):删除指定 key 对应的键值对;
- clear():清空 Map 中所有的键值对;
- keys():返回一个包含所有键的数组;
- values():返回一个包含所有值的数组;
- entries():返回一个包含所有键值对的数组;
- forEach(callbackFn, thisArg):遍历 Map 中的所有键值对,并执行指定的回调函数。
Set 是一种元素的集合,其中每个元素都是唯一的。Set 的 API 包含了以下方法:
- add(value):向 Set 中添加一个元素;
- has(value):判断 Set 中是否存在指定的元素;
- delete(value):删除指定的元素;
- clear():清空 Set 中所有的元素;
- values():返回一个包含所有元素的数组;
- forEach(callbackFn, thisArg):遍历 Set 中的所有元素,并执行指定的回调函数。
Object 是一种键值对集合,其中每个键唯一对应一个值。它的 key 必须是字符串或符号类型。Object 的 API 包含了以下方法:
- Object.keys(obj):返回一个包含所有可枚举属性的键的数组;
- Object.values(obj):返回一个包含所有可枚举属性的值的数组;
- Object.entries(obj):返回一个包含所有可枚举属性的键值对的数组;
- Object.assign(target, ...sources):将多个对象合并为一个对象,并返回合并后的对象。
总的来说,Map 和 Set 更适合处理需要快速查找和去重的数据,而 Object 则更适合处理具有固定属性结构的数据。当然,在实际使用中,我们可能需要根据具体需求选择使用不同的数据结构。
display:flex 介绍
display: flex;
是 CSS 中的一个属性,用于创建一个弹性布局容器。当将 display
属性设置为 flex
时,元素的子元素会按照一定规则布局。
实际上,display: flex;
做了以下几件事情:
- 创建一个弹性容器:将元素的
display
属性设置为flex
会创建一个弹性容器,将其子元素作为弹性项目进行布局。 - 设置主轴方向和交叉轴方向:弹性容器有一个主轴和一个交叉轴。通过设置
flex-direction
属性可以控制主轴的方向(水平或垂直),默认值是row
(水平方向)。交叉轴的方向则由主轴的方向决定。 - 控制项目的排列方式:弹性容器中的子元素称为弹性项目。通过设置
justify-content
属性可以控制项目在主轴上的排列方式,如居中、顶部对齐、底部对齐等。通过设置align-items
属性可以控制项目在交叉轴上的排列方式,如居中、顶部对齐、底部对齐等。 - 调整项目的大小和顺序:通过设置
flex
属性可以控制项目在弹性容器中的相对大小。默认情况下,每个项目的flex
值为 0,即各项目等宽或等高。可以通过设置不同的flex
值来调整项目的大小比例。同时,使用order
属性可以改变项目的显示顺序。 - 自动换行:默认情况下,弹性容器中的项目会在一行上排列。当项目的总宽度超过了容器的宽度时,项目会自动换行到下一行。若想禁止自动换行,可以将
flex-wrap
属性设置为nowrap
。
总之,display: flex;
属性将元素转换为一个弹性容器,通过设置一系列相关的属性,可以控制容器中子元素的布局、顺序和大小,从而实现灵活的布局效果。
手写数组map方法
Array.prototype.myMap = function(callback) {
// 创建一个新数组,用于存储映射后的结果
const mappedArray = [];
// 遍历原数组中的每个元素,依次调用回调函数并将结果添加到新数组中
for (let i = 0; i < this.length; i++) {
mappedArray.push(callback(this[i], i, this));
}
// 返回映射后的新数组
return mappedArray;
};