forEach遍历时,第一个是value, 第二个是key
for of遍历数组时,第一个是value。遍历map时,第一个是key, 第二个是value
当a是一个数字。不能直接通过a[0]拿到第一个数字。
let a = 123
log(a[0]) // und
let arr = '123'
[arr[0], arr[1]] = [arr[1], arr[0]]
console.log(arr); // err
这是因为字符串是一个不可变的数据类型,只有数组才能向上面那样。
let a = '012'
console.log(Number(a)) // 12
filter和map都不会改变原数组,都会返回一个改变后的新数组。
filter返回一个数组,数组里有所有符合条件的值。
find返回一个符合条件的值。
pc端和移动端展现登录的页面。pc端是左右布局的,移动端是上下布局的。我希望写一套代码,实现pc端和移动端都有对应的效果
要实现PC端和移动端展示不同布局(PC端左右布局,移动端上下布局)的登录页面,可以采用响应式设计技术,结合CSS媒体查询(Media Queries)进行样式适配。以下是一个简单的示例:
HTML结构(假设为左右布局):
<div class="login-container">
<div class="login-form-left">
<!-- 左侧内容,比如用户头像、logo等 -->
</div>
<div class="login-form-right">
<!-- 右侧表单内容 -->
<form>
<input type="text" placeholder="用户名" />
<input type="password" placeholder="密码" />
<button type="submit">登录</button>
</form>
</div>
</div>
CSS样式:
/* 默认情况下的左右布局 */
.login-container {
display: flex;
}
.login-form-left,
.login-form-right {
flex: 1;
}
/* 移动设备下的上下布局 */
@media screen and (max-width: 768px) { /* 您可以根据需要调整这个断点 */
.login-container {
flex-direction: column;
}
.login-form-left,
.login-form-right {
width: 100%;
}
}
在上述代码中,.login-container 通过 display: flex; 实现了左右布局。当屏幕宽度小于或等于768px时(您可以自定义这个阈值),CSS媒体查询生效,将 .login-container 的 flex-direction 改为 column,从而实现了上下布局。
这样,同一套HTML结构通过CSS响应式设计即可适应不同的设备和屏幕尺寸。
热更新是什么
热更新(Hot Update或Hot Reload/Replacement)是一种在应用程序运行时无需重启程序就能实时加载并应用代码更改的技术,常见于现代Web开发和某些桌面、移动应用开发领域。它使得开发者可以立即看到代码修改的效果,显著提高了开发效率。
在Web开发中:
- 前端:通过工具如Vite、Webpack的HMR(Hot Module Replacement)功能,当开发者修改JavaScript、CSS或HTML文件后,框架能够检测到这些变化,并自动将新的模块重新编译、替换到正在运行的应用中,同时保持应用状态不变。
- 后端:Node.js等服务端环境也可以实现热更新,例如通过nodemon这样的工具监视文件变动,当检测到代码更改时自动重启服务器进程。
扩展运算符只能对有迭代属性的对象使用。类数组没有这个属性。对象可以使用扩展运算符,但单独使用会报错,必须结合对象结构,只有这样才不会报错。({...obj})
能解释下递归和迭代吗?重点解释下不同点
递归是一种编程方法,其中函数或子程序会直接或间接地调用自身来解决问题。每次函数调用自身时,都会尝试将原始问题简化成一个或多个规模更小的同类问题。
ef factorial(n):
if n == 0: # 基本情况
return 1
else:
return n * factorial(n - 1) # 递归步骤
迭代是指通过循环结构(如for、while等)来不断更新变量状态,逐步逼近最终结果的过程。迭代过程中不会产生新的函数调用,而是通过在循环体内修改变量的值来连续计算出一系列中间结果,直至达到预期目标。
那么回溯呢?
回溯是一种基于试探性的搜索算法思想,通常利用递归来实现。回溯算法会尝试构建问题的解,通过试探不同的路径,当发现当前路径不能导致有效解或者不符合约束条件时,它会撤销(或回溯)到上一步,改变之前的决策,并继续探索其他可能的路径。回溯的过程中包含了剪枝机制,即在搜索过程中排除那些不可能导向最优解或任何解的部分搜索空间,从而提高算法效率。
例子: 找到最小路径和
怎么实现图片的批量下载? 以js代码为例
以下是一个使用JavaScript实现网页内图片批量下载的示例。由于浏览器的安全限制,JavaScript不能直接将文件保存到用户的硬盘上,但可以通过创建a标签并模拟点击来触发浏览器的下载行为。这里展示的是在浏览器环境中通过JavaScript批量下载页面内所有img标签的图片:
// 获取页面内的所有img元素
var imgs = document.querySelectorAll('img');
// 遍历所有图片
for (var i = 0; i < imgs.length; i++) {
// 创建一个隐藏的可下载链接(a标签)
var a = document.createElement('a');
// 将图片src作为下载链接的href属性
a.href = imgs[i].src;
// 设置下载名称,可以是图片URL的最后一个部分
a.download = imgs[i].src.split('/').pop() || 'image';
// 模拟点击以触发下载
document.body.appendChild(a);
a.click();
// 下载完成后移除该链接
document.body.removeChild(a);
}
// 如果需要考虑兼容性问题,可以添加setTimeout防止点击太快导致某些浏览器无法响应
(function(i) {
setTimeout(function() {
a.click();
document.body.removeChild(a);
}, i * 100); // 延迟间隔可以根据实际情况调整
})(i);
注意:此方法适用于同源策略允许的情况,并且不是所有的图片资源都能被下载(比如设置了CORS的图片)。
另外,如果需要更复杂的操作,如压缩、打包或从不同源下载图片等,可能需要结合HTML5的File API和Blob对象,或者在服务器端用Node.js等技术实现批量下载功能。对于现代浏览器,还可以利用Service Worker配合Cache Storage进行离线存储和批量下载,以及使用fetch()函数处理跨域请求等问题。
遇到了一个线上bug,该怎么调试找问题
遇到线上bug时,调试和定位问题的步骤可以按照以下流程进行:
- 复现问题:
-
- 首先,尝试在自己本地环境或者测试环境中重现问题。了解用户是如何触发这个bug的,包括用户的操作步骤、输入数据、设备信息、浏览器版本等。
- 收集信息:
-
- 查看错误日志:查看服务器端和前端(如浏览器控制台)的日志记录,寻找错误信息、堆栈跟踪或异常报告。
- 用户反馈:与出现问题的用户沟通,获取他们提供的截图、错误描述以及详细的操作过程。
- 使用监控工具:利用性能监控、错误追踪服务(如Sentry、New Relic等)收集更详尽的数据。
- 分析错误:
-
- 根据收集到的错误信息初步判断问题可能出在哪里,例如代码逻辑错误、资源加载失败、API接口调用异常等。
- 分析堆栈信息,找出抛出错误的具体代码行。
- 缩小范围:
-
- 逐步定位问题模块:从全局到局部,通过注释掉可能有问题的部分代码,观察是否还存在同样的问题来缩小问题范围。
- 单元测试/集成测试:编写或运行针对相关功能的测试用例,确认问题是否能够被某个特定的测试用例覆盖并重现。
- 修复验证:
-
- 在开发环境中修复疑似问题,并进行充分的测试以确保问题已经解决且没有引入新的问题。
- 如果可能的话,在一个较小规模的真实环境或灰度发布环境中验证修复方案的有效性。
- 上线部署与回滚策略:
-
- 将修复后的代码部署上线,并密切关注系统的实时状态和用户反馈。
- 确保有完备的回滚策略,一旦发现新问题,能迅速恢复到之前稳定的状态。
- 总结经验教训:
-
- 问题解决后,整理详细的bug修复过程,更新文档,并反思为什么会出现这样的问题,如何避免类似的问题再次发生,比如改进代码质量、优化监控系统、加强测试等。
总之,处理线上bug是一个细致且需要耐心的过程,需要结合实际情况灵活应用各种调试方法和技巧。
当一个移动端的项目第一次发版,在线上环境出现了白屏,是什么原因呢?
移动端项目(无论是移动Web应用还是原生App)首次发布到线上环境出现白屏现象,可能由多个原因引起。以下是一些常见的原因和相应的解决方案:
- 网络问题:
-
- 网络连接不稳定或服务器响应慢导致资源加载失败。
- CDN节点故障或未缓存新版本资源。
- 资源加载失败:
-
- 静态资源路径错误,如JS、CSS文件或图片等加载不到。
- 发布时更新了资源文件但客户端仍使用旧的缓存资源(如带有哈希值的文件名策略没有正确执行)。
- 代码编译/打包问题:
-
- Webpack或其他构建工具配置错误,导致生成的bundle文件无效或过大,影响加载速度。
- Vue.js项目中,如果vendor.js等主文件加载缓慢,可以尝试优化打包结果,比如通过懒加载或者代码分割减少初次加载资源量。
- 服务端配置问题:
-
- 服务器配置不正确,例如Nginx服务器缓存设置不当,返回的是过期资源。
- 前端代码错误:
-
- 页面入口JS文件中存在语法错误,导致脚本无法正常执行。
- 脚本运行时出错,如全局变量未定义、引用的模块找不到等。
- 原生App启动过程中的白屏:
-
- Android或iOS原生APP在启动过程中,由于初始化耗时较长,在真正渲染第一个Activity或ViewController之前会出现短暂的白屏或黑屏。
- 如果是主题背景问题,可以在主题样式里设置合适的windowBackground以改善初始显示效果。
- 权限问题:
-
- 对于原生App,如果第一次安装没有获取必要的系统权限,如网络访问权限,也可能导致内容无法加载而显示白屏。
解决这类问题通常需要排查以下几个方面:
- 检查日志和错误报告,查看是否有具体的错误信息。
- 测试不同网络环境下的加载情况。
- 清理或调整服务器及CDN缓存策略。
- 确保前端资源正确打包且加载路径无误。
- 对于原生App,优化启动流程并检查权限申请是否及时恰当。
首屏加载过慢呢?
移动端项目首屏加载过慢可能由以下几个主要原因导致:
- 资源体积过大:
-
- JavaScript、CSS和图片等静态资源文件的大小如果过大,会显著增加网络传输时间。需要通过代码分割、懒加载、压缩优化等方式减小主文件和非关键资源的大小。
- 未合理使用缓存策略:
-
- 缺乏有效的缓存机制,每次访问都需要重新下载全部或大部分资源。可以通过设置HTTP缓存头部(如Cache-Control和ETag)以及合理的资源版本控制来利用客户端缓存提高加载速度。
- 请求过多:
-
- 首屏渲染所需的关键路径上存在过多HTTP请求,增加了网络延迟。可以合并CSS和JavaScript文件,减少请求次数,并对非关键资源采用异步加载。
- 代码执行效率低:
-
- 代码中可能存在不必要的同步操作或者复杂计算,导致页面阻塞无法快速渲染。应尽可能避免阻塞渲染流程的操作,尤其是主线程上的长时间计算。
- 服务器响应时间长:
-
- 后端服务处理请求耗时过长,或是CDN节点响应速度慢。需优化后端服务性能并考虑使用高效稳定的CDN服务。
- 渲染优先级不合理:
-
- 页面结构设计不理想,导致浏览器在解析HTML文档时不能立即渲染出可视内容。应遵循“内容优先”原则,先加载和渲染用户最关注的内容。
- SPA应用初次加载问题:
-
- 单页应用(SPA)首次加载时通常会加载大量框架和库,这可能导致首屏显示较慢。可以采取预渲染、骨架屏(Skeleton Screen)或服务端渲染(SSR)等技术改善用户体验。
- 原生App中的启动画面处理:
-
- 原生APP首次打开时,若没有合理处理加载过程中的等待状态,也可能造成首屏加载慢的感觉。可以通过设置合适的启动画面(Splash Screen)并在后台加载数据以掩盖加载延迟。
针对以上原因,可采取以下措施进行优化:
- 对资源进行压缩和按需加载。
- 使用Tree Shaking、Scope Hoisting等手段精简代码包。
- 设计合理的缓存策略,确保频繁访问的资源能有效利用缓存。
- 提高后端API接口响应速度,优化数据库查询等操作。
- 对于Web应用,实现预加载、懒加载和渐进式渲染等功能。
- 对于原生App,做好启动画面过渡及后台数据预加载的设计。
静态资源怎么做按需加载
静态资源按需加载是一种优化网页性能的技术,它确保只有在用户需要时才加载相关的CSS、JavaScript或图片等资源,从而减少初始加载时间、节省带宽并提升用户体验。以下是一些常见的框架和技术中实现静态资源按需加载的方法:
React.lazy:在React中,可以使用React.lazy结合Suspense组件来动态加载组件作为按需加载静态资源的一种方式。
import { lazy, Suspense } from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}
- Loadable Components:这是一个第三方库,为React提供了更高级别的代码分割和按需加载功能。
- 图片懒加载(Lazy Loading Images) :通过监听滚动事件,在图片进入视口后再请求图片资源。
// 基于Intersection Observer API的简单示例
let observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
let img = entry.target;
img.src = img.dataset.src; // 将实际src赋给图片
observer.unobserve(img); // 观察一次后移除观察者
}
});
});
// 观察所有带有data-src属性的图片
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
const App = () => {
const [a, setA] = useState(-1) ;
console.log(a);
useEffect(() => { setA(0) setA(1) setA(2) }, [0])
return ( <div>{a}</div> ) }
为什么会打印-1和2
由于React的状态更新是批次处理的,当短时间内连续调用多次setA时,React会合并这些更新,只会触发一次组件的重新渲染。
const [a, setA] = useState(-1);
console.log(a);
setA(2);
return <div>{a}</div>;
// 报错,setA是异步函数。通常都放在useEffect或者触发某次事件时执行(点击按钮后)
HTML AND CSS
html5
语义化: header, footer, nav, section
新增多媒体: audio, video
缓存: localStorage, sessionStorage
websockets
web worker, servier worker
表单增强:新增多种类型的输入控件,如日期、邮箱、URL、颜色选择器等,并且对表单验证进行了改进。
写flex时,最后一行它的元素无法对齐的问题。
在使用CSS Flex布局时,最后一行元素对齐问题通常发生在容器内的项目数量不固定且容器设置了flex-wrap: wrap;(允许换行)的情况下。当最后一行的元素数量不足时,可能会导致这一行的元素无法按照预期与其他行元素对齐。
可以加伪元素占位置,或单独设置。
flex 1
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0%;
// flex-basis: 0%; 设置了项目的初始大小(主轴方向)。这里设置为0%,意味着项目在分配多余空间或收缩尺寸之前,其基础大小为0。
所以,当你给一个flex子元素设置flex: 1;时,该子元素会尽可能填满剩余的空间,与其他设置了同样flex: 1;的兄弟元素平分父容器的可用空间,并且在空间不足时按比例缩小。这对于实现自适应布局和响应式设计非常有用。
移动端怎么做适配,缩放之类的
移动端适配主要包括以下几个方面,以确保网页或应用在不同尺寸、分辨率和像素密度的移动设备上都能良好展示:
1. 设置viewport元标签
在HTML文档的中添加viewport元标签来控制视口大小和缩放行为:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
- width=device-width:让页面宽度等于设备屏幕的宽度。
- initial-scale=1:初始缩放比例为1,即不让浏览器默认缩放页面内容。
- maximum-scale=1 和 user-scalable=no(可选):禁用用户手动缩放。
2. 响应式设计与媒体查询
使用CSS3的媒体查询(Media Queries),根据不同的设备特性(如宽度、高度、分辨率等)来应用不同的样式规则:
@media screen and (max-width: 768px) {
/* 在小于或等于768px宽度的设备上的样式 */
}
@media screen and (min-width: 769px) {
/* 在大于768px宽度的设备上的样式 */
}
3. 单位选择
- 使用相对单位如rem、em或者vw/vh替代绝对单位px进行布局。例如,通过设置根元素html的字体大小(如:html { font-size: 10vw; }),然后用rem作为容器和其他元素的尺寸单位,可以实现基于视窗宽度的自适应布局。
- %单位用于基于父元素宽度或高度的百分比布局。
4. Flexbox与Grid布局
- 利用CSS Flexbox布局模型可以方便地处理元素的水平和垂直对齐以及灵活伸缩。
- CSS Grid布局则可以创建更加复杂的二维网格系统,尤其适用于需要更精细布局控制的场景。
5. 图片和字体图标适配
- 使用响应式图片技术,比如
或者 CSS 的 background-image + media 查询。
- 字体图标通常会自动适应文本大小,不需要额外的适配工作。
javaScript
for in 和 for of
对象只能被for in直接遍历,遍历的是key。
for...in循环主要用于遍历对象自身的(不含继承的)可枚举属性。
for...in会遍历到原型链上的可枚举属性,如果只想遍历对象自身的属性,需配合Object.hasOwnProperty()方法使用。
let obj = new Object()
obj = {
a: 1
}
Object.prototype.b = 4
for (let o in obj) {
if (obj.hasOwnProperty(o)) {
console.log(o); // a
}
}
不能被for of遍历,会报错,因为对象不可迭代。
数组能被for in and for of遍历。前者遍历index,后者遍历value。
解释下可枚举和可迭代
可枚举(Enumerable) :
- 当一个对象的属性是可枚举的,这意味着它可以通过for...in循环、Object.keys()、Object.getOwnPropertyNames()(但不包括Object.getOwnPropertySymbols())等方法列举出来。
- 默认情况下,自定义添加到对象自身的属性都是可枚举的。然而,内置的一些属性和通过原型链继承来的属性往往是不可枚举的,如数组的length属性,或者Object.prototype上的方法。
- 可以使用Object.defineProperty()或Object.assign()时指定enumerable: false来创建不可枚举的属性。
可迭代(Iterable) :
- 在JavaScript中,一个对象如果实现了可迭代协议,那么它就是可迭代的。
- 一个可迭代对象必须拥有一个默认的迭代器,即其内部实现了一个[Symbol.iterator]方法,返回一个迭代器对象。当调用这个迭代器对象的next()方法时,会依次返回包含当前值和是否结束遍历信息的对象 {value, done}。
- 数组、字符串、Map、Set、TypedArray、生成器函数以及其他一些内置类型都是原生可迭代的。
- 使用for...of循环可以遍历可迭代对象的元素,同时Spread operator (...)、Array.from()以及yield*语法也依赖于可迭代协议。
总结来说,可枚举性关注的是对象属性能否被编程方式列举出来,而可迭代性则关乎对象能否支持一种标准的方式来逐个访问其内部的值或子项。
map怎么遍历
1, 直接遍历
const map = new Map()
map.set('a', 1)
map.set('b', 2)
map.set('c', 3)
for (let ma of map) {
const [key, value] = ma
console.log(key, value);
}
// a, 1
// b, 2
// c, 3
2, map
for (let [key, value] of map) {
console.log(key, value);
}
// a, 1
// b, 2
// c, 3
3, map.keys()
for (let key of map.keys()) {
console.log(key);
}
// a
// b
// c
4, map.values()
for (let value of map.values()) {
console.log(value);
}
// 1
// 2
// 3
5, forEach, 第一个参数是value, 跟遍历数组一样
map.forEach((value, key) => {
console.log(value, key)
})
// 1, 'a'
// 2, 'b'
// 3, 'c'
6, 将map变为真正的数组,
console.log(Array.from(map)); // [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]
此外,for...of循环配合生成器(generator)可以实现更复杂的异步遍历,结合async/await可以实现串行化地等待异步操作完成:
async function processItems(items) {
for (let item of items) {
let result = await someAsyncFunction(item);
console.log(result);
}
}
在这个例子中,每个item都会等待someAsyncFunction的结果后再继续下一个循环
Object.create
Object.create()是JavaScript中用于创建新对象的一个内置方法。它接收一个原型对象作为参数,并返回一个新的对象,这个新对象的[[Prototype]](内部原型)被设置为传入的对象。
使用Object.create()可以实现基于已有对象创建子对象的目的,从而实现继承。
类数组
类数组对象(Array-like Object)是一种特殊类型的对象,它虽然不具备完整的数组行为和方法,但具有与数组相似的结构特征,通常包含以下特点:
- 索引键:拥有连续整数(或可转化为整数的字符串)作为键名,例如 0, 1, 2 等。
- length属性:具有一个名为length的属性,表示对象可以访问到的元素个数。
let arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
// 键必须是索引号,必须要有length属性
// 使用 Array.from 方法转换
let arr1 = Array.from(arrayLike);
console.log(arr1); // 输出 ['a', 'b', 'c']
// 使用扩展运算符转换
let arr3 = [...arrayLike];
console.log(arr3); // error, 扩展运算符只能对有迭代属性的对象使用。类数组没有这个属性
//注意
let arrayLike = {1: 'a', 2: 'b', length: 2};
let arr1 = Array.from(arrayLike);
console.log(arr1); // 输出 [undefined, 'a']
对象的键的类型和weakmap的键的类型
对象的key只有两种类型,字符串和Symbol
但是你在定义的时候,其实可以将key写成其他类型,数组或者就直接一个字母。
对象内部会自己将非Symbol进行自动转换为字符串类型。此外,由于语言特性,不能直接用null或者undefined作为键值。
const aa = Symbol('keys')
const obj = {
'a': 1,
3: 7,
r: 100,
}
obj[aa] = 'symbol'
console.log(obj);
// { '3': 7, a: 1, r: 100, [Symbol(keys)]: 'symbol' }
weakmap
- 键必须是对象。这意味着你不能使用基本数据类型(如字符串、数字、布尔值、null或undefined)作为WeakMap的键。
- 特别的,除了常规的对象外,从ES6引入的Symbol也可以作为WeakMap的键,但同样要求该Symbol不是全局注册的,也就是说它应该是通过Symbol()函数创建的非全局符号。
- 由于WeakMap的键与值之间的关系是弱引用,当键所指向的对象没有其他强引用时,垃圾回收机制会自动回收这些对象,而不会因为WeakMap中还存在对该键的引用而导致内存泄漏。
Object.entries()
Object.entries() 是一个JavaScript的内建方法,用于将对象自身可枚举的属性转换为数组返回。每个属性的键值对都会被转换为一个包含两个元素的数组:第一个元素是属性名(键),第二个元素是对应的属性值。这个方法返回一个由这些键值对数组组成的数组。
Object.keys(obj) and Object.values(obj) 返回的是一个数组,需要用for of遍历。 而map中是如下使用, map.keys() and map.values()
Symbol(符号)
Symbol()函数创建的是非全局符号。Symbol.for()创建的是全局符号
使用 Symbol() 构造函数直接创建的每一个Symbol都是独一无二的,并且只在当前作用域内有效。即使两个Symbol具有相同的描述符,它们也是不同的Symbol实例。
Symbol.for and Symbol.keyFor
前者接受一个键名作为参数,返回一个Symbol。在全局符号注册表中查找一个给定键名的符号。如果找到该键名对应的符号,则返回该符号;如果没有找到,则会创建一个新的符号,并将其与给定键名关联后存入全局符号注册表,然后返回这个新创建或已存在的符号。
后者接受一个Symbol作为参数,返回键名。并在全局符号注册表中查找与之关联的字符串键名。如果提供的Symbol是在全局注册表中通过 Symbol.for() 方法创建的,则返回其原始键名,否则返回 undefined。
const aa = Symbol('keys')
const bb = Symbol('keys')
console.log(aa === bb); // false
const cc = Symbol.for('key')
const dd = Symbol.for('key')
console.log(cc === dd); // true
const ee = Symbol.keyFor(cc)
console.log(ee); // 'key'
const ff = Symbol.keyFor(aa)
console.log(ff); // undefined
在JavaScript前端开发中,迭代器(iterator)接口和生成器(generator)是ES6引入的两种关键特性,它们为处理集合、数组和其他可迭代对象提供了新的方法。
迭代器(Iterator Interface)
迭代器是一种协议,它定义了如何遍历一个集合(例如数组、Set、Map等)中的元素。任何实现了Symbol.iterator属性的对象都可视为可迭代对象,这个属性应返回一个迭代器对象。迭代器对象具有一个next()方法,每次调用该方法时会返回一个结果对象,其格式如下:
Javascript
1{
2 value: any, // 当前迭代位置的值
3 done: boolean // 如果所有值都已经遍历完,则为true,否则为false
4}
例如,你可以使用内置的迭代器来遍历数组:
Javascript
1let array = [1, 2, 3];
2let iterator = array[Symbol.iterator]();
3console.log(iterator.next()); // {value: 1, done: false}
4console.log(iterator.next()); // {value: 2, done: false}
5console.log(iterator.next()); // {value: 3, done: false}
6console.log(iterator.next()); // {value: undefined, done: true}
生成器(Generator)
生成器是JavaScript中一种特殊的迭代器生产者,但它更加强大,因为它允许暂停和恢复函数执行,使得函数能够“记住”它上次离开的位置,并继续从那里开始。生成器通过function*关键字定义,使用yield表达式来产出一个值给迭代过程:
Javascript
1function* generatorExample() {
2 yield 'a';
3 yield 'b';
4 return 'c'; // 可选的最终返回值
5}
6
7let gen = generatorExample();
8console.log(gen.next()); // {value: "a", done: false}
9console.log(gen.next()); // {value: "b", done: false}
10console.log(gen.next()); // {value: "c", done: true}
生成器不仅简化了迭代器的创建过程,还支持协程功能,可以用于异步编程场景,如处理复杂的回调逻辑或者配合async/await实现同步风格的异步代码。通过yield*表达式,生成器还可以委托另一个生成器或可迭代对象的迭代过程。
constructor
const obj = new Object()
console.log(obj.constructor === Object) // true
const aar = [1,2,34]
const aa = new aar.constructor()
console.log(aa); // []
const a = {a: 20}
console.log(new a.constructor()); // {}
const
const arr = 999
const a = arr--
console.log(arr, a); // 报错
const arr = 999
const a = arr - 1
console.log(arr, a); // 999, 998
event loop(事件循环)
JavaScript 事件循环(Event Loop)是 JavaScript 运行时环境中的核心机制,尤其是对于异步编程模型而言。在单线程的JavaScript中,由于同一时间只能执行一个任务,事件循环确保了异步操作能够有序、及时地执行回调函数。
不要说dom渲染
Promise新理解
1,任何值都可以被封装成promise, 只需要使用Promise.resolve(传递的值),就可以返回一个状态是成功态,值是传递的值。
const p1 = Promise.resolve(1)
const p2 = Promise.reject(() => {
console.log(2)
})
console.log(p1, p2)
//p1
//[[Prototype]]: Promise
//[[PromiseState]]: "fulfilled"
//[[PromiseResult]]: 1
// p2
//[[Prototype]]: Promise
//[[PromiseState]]: "rejected"
//[[PromiseResult]]: () => { console.log(2) }
2,promise.allSettled
用法跟all一样,只是返回结果跟all不一样
Promise.allSettled() 方法是 promise 并发方法之一。在你有多个不依赖于彼此成功完成的异步任务时,或者你总是想知道每个 promise 的结果时,使用 Promise.allSettled() 。
3, promise.all通常接受一个可迭代的对象作为参数,不过对象里的元素不一定都是promise,还可以是值或者数组。你可以将回调函数作为数组中的元素,all方法会内部将回调函数作为Promise.resolve()的参数,转为promise。
const p2 = () => {
console.log(2);
}
console.log(Promise.all([p2]))
// 返回一个成功的promise, 值为一个数组,数组中只有箭头函数一个元素。
const p1 = 1
const p2 = () => {
console.log(2);
}
const p3 = Promise.resolve(3)
Promise.alls = function(lists) {
if (!Array.isArray(lists)) {
return false
}
const len = lists.length
return new Promise((resolve, reject) => {
const res = []
let sum = 0
for (let i = 0; i < len; i++) {
let values = lists[i]
if (lists[i].constructor !== Promise) {
values = Promise.resolve(lists[i])
}
values.then(value => {
sum++
res[i] = value
if (sum === len) {
resolve(res)
}
}, reason => {
reject(reason)
})
}
})
}
console.log(Promise.alls([p1, p2, p3]));
// 成功态
const obj = {
x: 20
}
console.log(obj.constructor === Object) // true
console.log(new obj.constructor()) // {}
const obj = 3
console.log(obj.constructor === Number) // true
console.log(new obj.constructor()) // Number {0}
基于原型链,对引用数据类型和promise, 包装数据类型都适用。
网络请求
option预检请求
在HTTP通信中,预检请求(Preflight Request)是CORS(跨源资源共享,Cross-Origin Resource Sharing)机制的一部分。当浏览器发起非简单请求(比如使用了PUT、DELETE方法或者设置了自定义头部等)时,为了防止跨站脚本攻击(XSS)和跨站请求伪造(CSRF)等问题,浏览器会先发送一个OPTIONS预检请求到服务器,询问服务器是否允许实际的请求。
OPTIONS预检请求的主要目的是确认以下信息:
- 浏览器确认服务器是否支持特定的方法(如POST、PUT、DELETE等)。
- 检查服务器是否允许在实际请求中携带自定义HTTP头部。
- 询问服务器是否允许来自该源(Origin)的请求。
服务器收到OPTIONS预检请求后,会在响应头中返回相应的Access-Control-Allow-Methods、Access-Control-Allow-Headers以及Access-Control-Allow-Origin等字段来表明哪些请求方式、头部字段和来源是可以接受的。只有当预检请求通过后,浏览器才会继续发送真正的请求。
介绍下网络请求中的轮询
在网络请求中,轮询(Polling)是一种客户端主动向服务器发送请求获取数据的机制,客户端周期性地发送请求,询问服务器是否有新的数据或状态更新。这种方式常用于实时数据更新场景,如即时通讯、股票报价、新闻更新等。
介绍下服务端推送
服务端推送(Server-Sent Events, SSE)是一种允许服务器向客户端单向推送实时数据的技术,它是基于HTTP长连接实现的,允许服务器在有新事件发生时,主动向已经建立了连接的客户端发送更新消息,而无需客户端反复发起请求去查询是否有更新。
是对轮询的优化
服务端推送是对轮询的优化吗?
服务端推送(Server-Sent Events, SSE)和长轮询(Long-Polling)都是对传统轮询(Polling)技术的优化,旨在解决实时数据更新问题。
轮询是一种简单但效率相对较低的技术,客户端周期性地向服务器发送请求以查询是否有新的数据。这种机制会导致不必要的网络流量和服务器负载,尤其是在没有新数据的情况下。
长轮询是对轮询的一种改进,它通过保持一个挂起的HTTP连接,直到服务器有数据需要发送为止。这样一来,虽然从客户端角度看仍然是“拉取”模式,但实际上减少了无用的请求次数,并在一定程度上实现了近似“推送”的效果,降低了延迟并提高了带宽利用率。
而服务端推送(SSE)是另一种推送技术,它允许服务器单向地向客户端发送更新,而无需客户端发起请求。SSE建立在HTTP协议之上,利用了持久连接来实现实时通信。与长轮询不同的是,一旦建立了SSE连接,服务器可以持续不断地向客户端发送数据流,而不需要每次重新建立连接或等待客户端询问。
WebSocket则是更进一步的全双工通信协议,提供了真正的双向通信通道,不仅支持服务器推送到客户端,也支持客户端向服务器发送消息,且性能和实时性优于SSE和长轮询。
所以,可以说长轮询是对传统轮询的优化,而服务端推送(SSE)则是在此基础上进一步优化了服务器到客户端的数据传输方式,尤其针对那些只需要单向通信、不需要复杂交互场景的需求。
场景题一
手机扫描微信二维码进行登录,可以使用什么协议呢
- 轮询
- websocket的长连接
- http的长连接
请求是请求哪些东西
文件(html模板,用于页面的加载)
数据(ajax请求数据)
连接(请求之前建立连接)
with credential 带凭据
withCredentials 是与 XMLHttpRequest 和 Fetch API 相关的一个属性,用于控制跨域请求时是否携带 cookie 等用户凭据(credentials)。
- XMLHttpRequest: 在 XMLHttpRequest 对象中,可以通过设置 xhr.withCredentials = true; 来允许跨域请求携带凭据。默认情况下,跨域请求是不发送凭据的。启用这个选项后,浏览器在 CORS 请求中会包括任何适用的 cookies、HTTP 身份验证信息等。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://other-domain.com/api/data', true);
xhr.withCredentials = true;
xhr.send();
请注意,服务器端需要正确配置 CORS(跨源资源共享),以允许特定来源携带凭据进行访问。具体来说,响应头需要包含 Access-Control-Allow-Credentials: true 以允许客户端携带凭据发起请求。
204, 206, 304, 502, 504
- 204 No Content:
-
- 表示服务器已成功处理请求,但没有返回任何实体内容(如网页、图像等)。通常用于只需进行操作而无需返回数据的场景,例如DELETE请求成功后。
- 206 Partial Content:
-
- 适用于范围请求(Range Request),表示服务器仅返回了所请求资源的一部分。这常见于视频流或文件下载中,用户可以请求资源的部分范围而不是整个资源。
- 304 Not Modified:
-
- 客户端发送了一个条件GET请求(带有If-Modified-Since, If-Match等头部信息),服务器判断资源自上次请求以来未发生变化,则返回此状态码,并且不包含资源内容。这样可以减少不必要的数据传输,提高性能。
attachment
当涉及到attachment时,它通常与另一个响应头字段 Content-Disposition 相关。
在 Content-Disposition 头部字段中,可以设置其值为 attachment,这表示服务器希望浏览器将接收到的内容作为附件处理,而不是直接展示在页面上。具体示例如下:
Http
1Content-Disposition: attachment; filename="example.pdf"
在这个例子中,服务器指示浏览器将内容作为一个名为 "example.pdf" 的附件进行下载,而不是尝试在浏览器窗口内打开或渲染该内容。这对于PDF文件、Office文档等非HTML内容尤其常见,因为这些内容不适合直接在网页中展示,用户通常期望将其下载到本地查看。
网页上的Pdf, 当点击的时候,有一些是直接在浏览器页面打开直接看的,有一些是作为资源进行下载的,为什么会有这两种情况?
网页上的PDF文件在点击后可能直接在浏览器页面打开,也可能作为资源下载到本地,这是因为这主要取决于以下因素:
- 浏览器设置:
-
- 用户的浏览器设置决定了PDF文件如何处理。例如,在浏览器的设置中可以指定PDF文件默认在浏览器内部的PDF阅读器插件或组件(如Chrome内置的PDF查看器)中打开,或者总是下载到本地。
- 服务器响应头信息:
-
- 服务器通过HTTP响应头中的Content-Disposition属性控制了客户端应如何处理该资源。如果设置了Content-Disposition: inline; filename="filename.pdf",则浏览器通常会尝试在窗口内显示PDF;若设置为Content-Disposition: attachment; filename="filename.pdf",则浏览器会提示用户将其作为附件下载。
- 网页链接标签属性:
- 浏览器支持:
-
- 不同浏览器对PDF文件的支持程度不同,有些浏览器可能无法直接预览PDF内容,所以默认选择下载。
- PDF大小和复杂性:
-
- 对于大型或高度复杂的PDF文件,浏览器可能会因为性能原因而选择默认下载,而非在线预览。
应用层协议(http)和传输层协议 (tcp, udp)
http2.0
二进制(0 和 1的情况) | 多路复用 | 包头压缩 | 服务端推送
https是怎么工作的
堆成加密和非对称加密相结合。
最开始握手阶段,服务端生成公钥和私钥后,将公钥发送给客户端是非堆成加密。
而客户端会随机生成一个密钥,被公钥加密后发送给服务端。这个密钥是对称加密。之所以用对称加密,是因为相较非对称,速度更快。
移动端注重性能优化,跨平台,体验,兼容性。
https怎么优化
整合请求,长连接。
请求总是数据很大怎么优化?=→>内容压缩
请求头中加入 Accept-Encoding -期望返回内容被压缩=>减少网络流量,提升性能
追问:
a,Accept-Encoding要求非强制,服务端不支持压缩、开启压缩、第三方api
b. 支持压缩也开启了压缩,响应头里具有Content-Encoding: gzip
const obj = {name: 'tang'}
Object.assign({}, obj)
{...obj} // 两者等价
react
React的老版本(16及之前)的diff算法实现采用了递归的方式来遍历并比较虚拟DOM树。React会在更新过程中的reconciliation阶段,通过深度优先遍历(Depth-First Search, DFS)算法来递归地比较新旧两棵虚拟DOM树的节点,找出最小化的变更集,并据此来高效地更新实际的DOM。
之前使用递归,react一旦开始,就不能中断
16以后使用链表的这种数据结构,可以中断
计算diff可以中断——把之前vdom树的树形结构变成链表式,被中断时能知道中断的具体位置,等待执行优先级任务过后的空闲时间继续进行diff。
fragments(减少层级嵌套)
在React中,Fragments(片段)是一种特殊的抽象元素,允许你在不引入额外DOM节点的情况下返回多个子元素。在React 16及更高版本中引入,Fragments用 <></> 或 标签表示。
Jsx
function Example() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
}
如上,平常一般用div对a,b,c进行包裹。不过引入div这个DOM节点没有任何意义,还多了一个层级。为此可以使用Fragments。
react各个版本
React 16 特性:
- 错误边界 (Error Boundaries):允许组件捕获并处理在其子组件树任意位置发生的渲染错误,避免整个应用崩溃。 通过componentDidCatch抓取。
- Fragments (<>...</>):允许返回多个子元素而不必包裹在一个额外的DOM元素中,解决了之前必须有一个顶层元素的要求。
3.React.memo实现性能优化。通过React.lazy + suspense实现按需加载。
4. 提出React Fiber,提高渲染性能。
5.引入hooks。
React 18 特性:
18之前: ReactDOM.reder创建页面。这是react17采用的默认模式。
18后,concurrent模式
ReactDOM.createRoot(rootNode).render()。这个模式开启了所有的新功能。
react18正式迁移到了concurrent 模式,同时,用户也可以继续使用react17下的旧API(但是会有警告提示)。
1,自动批处理。
批处理是 react 将多个状态更新分组到一个渲染中以获得更好的性能。react18 之前只能在react 事件处理程序中批处理更新。默认情况下,Promise、setTimeout、本机事件处理程序或任何其他事件内部的更新不会在 React 中批处理。
18之前,当我们在Promise, setTimeout里面执行多个setState时。每个state发生改变时,页面都会重新渲染。也就是有多少个setState,就会重新渲染多少次。
而在18里,多个setState会被集中到一块进行处理,然后处理完成后只会触发后续一次的DOm渲染。
- 并发渲染:带来了新的并发特性,包括Suspense for Data Fetching和Server-Side Suspense,优化了初始渲染和更新期间的数据获取和渲染过程。
- 自动批处理:默认启用并发更新,React会自动批处理状态更新,改善性能。
- createRoot API:推荐使用新的API来替换之前的ReactDOM.render,以利用新的并发特性。
React 19 预期特性(假设已发布):
根据2024年3月7日的信息,React 19的一大亮点可能是新的编译器特性,旨在进一步改变开发范式,但没有详细描述“React Forget”。
请留意,上述信息基于历史记录和预测,具体的React 19版本可能会有更多的新特性或变化,建议查阅官方发布的具体文档以获取最新和完整的信息。
transitions
紧急更新反映了直接交互,例如键入、单击、按下等。
非紧急(过渡)更新将 UI 从一个视图转换到另一个视图。更过的关注结果,而不是过程。
紧急任务和非紧急任务。
1,
包装在 startTransition 中的更新被视为非紧急更新,如果出现更紧急的更新(如点击或按键),则会中断。
2,
useTransition的核心其实就是通过useState维护了一个pending,然后将setPending作为参数传递给startTransition。
3,
新增useId,用于Ssr,用于在客户端和服务器上生成唯一 ID,同时避免hydration mismatches。
react会渲染两次,这是为什么呢,能举一些代码例子吗?
- 初始渲染与状态更新后的渲染:
- 当组件初次挂载时,React会进行一次渲染。
- 如果组件内部使用了useState或useEffect等hook,并且在组件加载后立即修改了状态,这会导致第二次渲染。
- StrictMode:
-
- 在React.StrictMode标签内包裹的组件会故意在开发模式下渲染两次,目的是为了揭示潜在的问题,例如不安全的生命周期方法使用等。
<React.StrictMode>
<ExampleComponent />
</React.StrictMode>
3. props变更:
- 当父组件传递给子组件的props发生改变时,即使新的props与旧props相等,也可能引起子组件的重新渲染。
- 上下文(context)变化:
-
- 如果组件订阅了React Context,当Context Provider的value发生变化时,所有消费该context的组件都会重新渲染。
react hook底层如何实现?
React Hooks 是 React 16.8 中引入的一项新特性,允许你在不编写类的情况下使用 state 以及其他 React 特性。理解 Hooks 的底层实现需要深入到 React 的内部工作原理。以下是对 React Hooks 底层实现的一个简化解释:
存储状态和引用
- 链表结构:React 使用类似于链表的数据结构来存储组件中的 Hooks。每个 Hook 都链接到下一个 Hook,形成一个链表。这个链表挂在组件实例的内部状态上。
- 保持顺序:由于 Hooks 必须按照它们在组件中声明的顺序来调用,React 依靠这个链表的结构来保持 Hooks 的顺序和状态。
更新和渲染
- 调度更新:当使用 useState 或 useReducer 等 Hooks 设置状态时,React 将安排该组件进行重新渲染。
- 渲染时重用状态:在组件的每次渲染期间,React 遍历这个链表,根据先前的渲染结果重用状态。
闭包和副作用
- 副作用的处理:useEffect 和 useLayoutEffect Hooks 允许你在组件渲染后执行副作用操作。React 将这些副作用函数存储起来,在 DOM 更新后调用它们。
- 闭包:Hooks 依赖于 JavaScript 的闭包机制,这意味着每个 Hook 可以“记住”它的状态和作用域,即使在多次渲染之间。
优化和规则
- 依赖列表和优化:useEffect 和 useMemo 等 Hooks 接受一个依赖项数组,React 用它来确定何时跳过或重新执行副作用或计算。
- Hooks 规则:为了保证这种链表结构的完整性和一致性,React 强制执行 Hooks 规则,如“只在顶层调用 Hooks”和“只在 React 函数中调用 Hooks”。
hooks为什么不能写在条件判断语句中?
React Hooks 不能写在条件判断语句中,原因如下:
- 执行顺序一致性: React 需要保证 Hooks 在每个组件渲染时按照相同的顺序执行。这是因为 Hooks 依赖于调用顺序来正确地关联和管理内部状态(如 useState、useEffect 等)。如果将 Hooks 放入条件或循环中,每次渲染时这些条件可能满足与否,导致 Hooks 的调用顺序发生改变,从而破坏了这一规则。
- 状态管理复杂化: 当 Hooks 出现在条件语句中时,React 将无法确保在不同渲染之间状态的正确管理和更新。例如,某个 Hook 可能因为条件不满足而没有被调用,但在下一次渲染时由于条件变化又被调用,这会导致该 Hook 对应的状态丢失或者与预期不符。
- 违反Hooks规则: React 定义了一套关于如何使用 Hooks 的规则,其中明确指出 Hooks 必须始终在函数组件顶层调用,且不能在条件、循环或其他控制流结构中调用。
要在条件判断语句中写hooks,怎么做呢
在React中,虽然不能直接在条件判断语句中写Hooks,但可以通过一些设计和重构来实现类似的功能。例如,如果你想要根据某个条件决定是否使用一个Hook,可以尝试以下方法:
- 拆分组件: 将包含Hook的逻辑部分拆分成独立的子组件,然后根据条件判断来决定是否渲染这个子组件。
function ParentComponent() {
const shouldRenderHook = someCondition;
return (
<div>
{/* 其他代码 */}
{shouldRenderHook && <ChildComponentUsingHook />}
</div>
);
}
function ChildComponentUsingHook() {
// 在这里可以安全地使用 Hook
const [state, setState] = useState(...);
// ...
}
2. 管理共享状态: 如果需要根据条件使用同一个Hook的状态,可以将该状态提升到父组件或者使用 useContext 和自定义上下文(Context)来管理共享状态。 3. 控制副作用的执行: 对于 useEffect 等与副作用相关的Hook,可以在Hook的依赖数组中传递条件变量,这样当条件改变时,副作用函数才会被执行。
diff算法中,key是如何比较的
在React的虚拟DOM diff算法中,key属性扮演着非常关键的角色,特别是在处理列表或者数组类型的子元素时。当React需要比较新旧两个虚拟DOM树时,对于由map函数遍历生成的列表项目,其比较过程涉及到key属性的具体工作原理如下:
- 基于Key的映射: React会基于子元素的key构建一个内部映射表(类似哈希表)。新旧两棵树中的子元素都会根据它们的key被关联起来。
- 高效查找: 利用这个映射表,React能够迅速找到旧树中与新树中具有相同key的元素,这一步骤极大地提高了比较的速度和准确性。
- 移动与更新: 当比较过程中发现两个元素key相同,React则会认为这是同一个元素的不同版本,这时只会更新该元素的属性,而不会直接移除并重新创建。如果元素在列表中的位置发生变化(即key相同的元素在新树中出现在了不同的位置),React会执行移动操作,而非全部删除然后重新插入。
- 新增与删除: 对于新树中存在的、但在旧树中找不到对应key的元素,React会将其视为新增元素并创建新的DOM节点插入到正确的位置。反之,旧树中有而新树中无的key所对应的元素将被标记为待删除。
通过这种方式,React利用key属性实现了对列表项的高效、精确的更新策略,减少不必要的DOM操作,提升了整体性能。特别强调的是,在列表项频繁变动时,使用有意义且稳定的key至关重要,因为依赖数组索引作为key会在列表项顺序发生变化时导致React无法有效地复用已有元素,从而增加不必要的开销。
react中有一个合成事件的处理机制,能讲一讲它是如何工作的吗
当然可以。在React中,合成事件(SyntheticEvent)机制是对原生浏览器事件的一层封装,旨在解决跨浏览器兼容性问题、提高性能以及提供一致的API接口。以下是React合成事件工作的大致流程:
React内部通过事件代理机制监听实际的DOM事件,并根据组件树结构调用适当的事件处理器。在这个过程中,React不仅封装了原生事件,使其在所有浏览器中表现一致,同时也简化了事件处理的过程。
合成事件的主要特点和原理:
- 事件注册:
-
- React并不直接将事件处理器绑定到具体的DOM元素上。相反,它在根DOM节点(通常是标签包裹的应用层级)上注册少量的事件监听器(如click、mousemove等),这是通过事件委托(Event Delegation)技术实现的。这样做的好处是可以减少直接绑定到每个DOM元素上的事件处理器数量,从而降低内存消耗和提升性能。
- React并不直接将事件处理器绑定到具体的DOM元素上。相反,它在根DOM节点(通常是
- 事件合成:
-
- 当某个原生事件在DOM树中冒泡至顶层(即根节点)时,React会截获这个事件,并根据事件类型和目标组件创建一个新的合成事件对象(SyntheticEvent)。
- 这个合成事件对象是对浏览器原生事件的一个封装,它模仿了W3C标准事件接口,并确保在所有浏览器中表现一致,消除了浏览器间的差异。
- 事件分发:
-
- 合成事件接着被分发到对应的React组件上,即触发相应组件上定义的事件处理器(如onClick、onMouseMove等)。
- React内部维护了一个事件池机制,用来重用合成事件对象以进一步优化内存使用。每次事件处理完毕后,事件对象会被清空并放回池中,等待下次事件触发时复用。
- API一致性:
-
- 即使在不同浏览器中,开发者都可以使用同样的API来操作事件对象,比如event.preventDefault()、event.stopPropagation()等,而无需考虑具体浏览器的实现差异。
- 生命周期阶段:
-
- 在React的生命周期中,合成事件在事件处理器内执行,通常发生在组件的render方法之后,例如在componentDidMount后的交互阶段。
示例:
class MyComponent extends React.Component {
handleClick = (event) => {
console.log('Clicked!');
event.preventDefault(); // 使用合成事件对象的方法阻止默认行为
};
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
在这个例子中,当用户点击按钮时,React并不会直接在按钮元素上绑定onclick事件,而是通过事件委托的方式,在根节点监听click事件。当点击事件冒泡到根节点时,React会创建一个SyntheticEvent对象,并将其传递给handleClick函数处理。这样就保证了无论在哪个浏览器环境下,React应用都能以一致的方式处理事件。
介绍下redux及其工作流程(基于发布订阅模式,当要订阅的事件发生变化后(也就是react中的数据改变后),就会通知订阅者(也就是监听redux的组件,组件被改变))
Redux 是一个流行的JavaScript状态管理库,尤其适用于复杂的应用程序,尤其是那些使用React、Vue、Angular等框架编写的前端应用。Redux 主张的是单一数据源(Single Source of Truth)和不可变数据的理念,通过集中式的状态管理,使得应用的状态更容易理解和维护。
最后是store更新组件,还是reducer更新组件?、
在Redux中,Reducer负责更新状态(State),但并不直接更新组件。具体的工作流程是这样的:
- 当用户交互或者其他触发条件发生时,组件可以dispatch一个Action到Store中。
- Store接收到这个Action后,会调用Reducer函数处理这个Action。
- Reducer根据接收到的Action和当前的State计算出一个新的State,并返回这个新的State。
- Store接收到Reducer返回的新State后,会更新自身的内部状态。
- 由于Store的内部状态发生变化,Store会自动通知所有通过store.subscribe方法注册过的监听器(listener)。
- 在React应用中,通常我们会使用react-redux库提供的connect高阶组件或者useSelector、useDispatch等React Hooks,这些工具帮助我们将Redux Store中的状态映射到React组件的props上,或者提供dispatch Action的方法。
- 当Redux Store的状态更新时,通过上述方式连接Redux Store的React组件会因为其props的变化而重新渲染,从而达到视图层的更新。
所以,最终是Store的状态更新促使React组件重新渲染,而非Reducer直接更新组件。Reducer只负责纯计算新的状态,不涉及任何视图层的操作。
数据结构与算法
前端在平时代码开发中,有哪些场景用到了数据结构与算法 栈,堆,队列,数组,链表,二叉树
栈
1,函数调用
在a函数里面调用b函数,b函数会优于a函数先执行完成。
function fn1() {
console.log(1);
fn2()
console.log(2);
}
function fn2() {
console.log(3);
}
fn1() // 1,3,2
2,浏览器的历史记录可以通过栈数据结构来模拟实现,每次路由跳转都可以视为一次压栈操作,后退按钮触发的则是出栈操作。
3,弹窗
队列
1,任务队列
进入到任务队列的异步任务,会按照先进队列先执行的方式进行。
2,消息通知系统:
消息通知队列的实现常常依赖于队列数据结构,新产生的通知先入队,用户查看时再逐个出队展示。
链表
链表的存储是不连续的,而是使用next指向下一个元素。在链表中,我们需要添加删除元素,只需要修改next指针即可。
1,原型链
在js中原型链的结构与链表结构非常类似。链表中我们使用next属性来链接下一个元素,而原型链则使用__proto__属性来链接原型对象
树
dfs, bfs
虽然浏览器负责大部分内存管理,但在大型应用程序中,开发者需要理解如何有效地分配和回收内存,例如避免循环引用导致的内存泄漏,或者在大量数据场景下利用WeakMap、Set等数据结构减少内存占用。
当使用动态规划是,往往有一个要去达到的条件,比如说找到值最短的路径。当只需要找到数组中的某个值是2,而不要求路径和最短时,这是可以使用搜索。从四个方向上找,可以使用深度搜素或者广度搜索或者递归
深度递归,也就是深拷贝
遍历其实就是搜索。 dfs即时深度优先遍历,也是深度优先搜索
二分查找: l <= r ; 快速排序: l < r
git
git status
git status是Git版本控制系统中的一个命令,用于查看工作区和暂存区的状态。运行此命令后,Git会显示以下信息:
- 未跟踪的文件:列出所有在当前目录下尚未被Git追踪的新文件或新目录。
- 已修改但未暂存的文件:列出已被Git追踪但在工作区中已被修改但尚未执行git add操作的文件。
- 已暂存等待提交的更改:列出通过git add命令放入暂存区(stage)准备提交的更改。
git merge 和 git rebase
git merge 和 git rebase 都是用来将一个分支的更改合并到另一个分支,但它们在操作方式、历史记录管理和解决冲突方面有所不同。以下是两者的主要区别:
git merge
- 合并方式:
-
- git merge 采用的是“三方合并”策略,它会创建一个新的合并提交(merge commit),这个提交有两个父提交:当前分支 HEAD 指向的提交和你要合并进来的分支的 HEAD 指向的提交。
- 历史记录:
-
- 使用 git merge 后,历史记录中会保留原始分支的提交历史,因此可以清楚地看到不同分支的工作轨迹和合并点。
- 冲突处理:
-
- 在合并过程中如果发生冲突,用户需要手动解决冲突并提交新的合并提交。
- 解决冲突后产生的合并提交包含所有冲突解决的结果,这可能会增加额外的提交信息。
- 应用场景:
-
- 当你希望保留完整的分支合并历史时,或者在多人协作且需要追踪分支间合并关系的情况下使用 git merge 更为合适。
git rebase
- 重置提交历史:
-
- git rebase 的工作原理是将当前分支的所有提交提取出来,然后逐个应用到目标分支的最新提交上,就好像这些提交直接基于目标分支进行一样。
- 这样做会改变提交历史,使整个分支看起来像从未分叉过一样,形成一条线性的提交序列。
- 历史记录:
-
- 使用 git rebase 后,提交历史会变得更为简洁,因为它移除了不必要的合并提交,以一个更连续的方式展示开发过程。
- 冲突处理:
-
- 与 git merge 类似,rebase 过程中也会遇到冲突,每次需要解决完一个提交的冲突后再继续下一个提交的重放。
- 一旦所有冲突解决完毕并且重放成功,原分支的提交会被放置在目标分支之后,不会产生额外的合并提交。
- 应用场景:
-
- 当你想清理历史记录,使得项目历史更加整洁,或者准备推送至主线分支时避免引入过多的合并记录,可以选择 git rebase。
- 然而,由于 rebase 会改变提交历史,因此不适用于已经推送到共享仓库且被他人基于之上进行工作的分支。
总结起来,git merge 是一种非破坏性合并,适合团队协作和保持完整的历史记录;而 git rebase 则更适合个人开发时追求简洁历史或用于整合工作到主线前的一种重构式合并。