前端常见问题

发布时间:2024年01月20日

项目问题
一、公司的项目开发会经历哪些阶段和整体流程
1、需求评审&设计评审:产品团队发起,需求相关产品经理(PM)、UI设计师(UI/UE/UED)、研发(RD/Dev)、测试(QA)团队共同参与对待开发需求进行评审,会议中各团队对待评审需求中不明确的功能点提出疑问,产品经理对疑问做出解释回答,如果当此评审无法达成一致的、形成结论的,需要再次进行需求评审

2、需求排期会:确认该需求研发、测试投入的工时和参与人,明确各个关键性时间节点

3、技术设计评审:如果项目开发周期大于 3人/日,由前端研发发起技术设计评审,具体需要阐述项目模块划分、组件划分、状态管理、代码设计、逻辑设计、与后端联调和测试需要注意的关键点,有可能导致bug或者项目延期的风险点,如何解决

4、开发阶段:如果承担项目负责人角色需要每天统计成员开发进度(早会形式),提前预知项目风险,遇到问题及时与产品或者后端进行沟通(拉群),如果承担项目普通开发成员,需要每日给自己制定开发进度计划,严格执行每个计划时间节点需要完成的开发内容

5、提测阶段:需要测试同学产出测试用例,评审,由产品、研发、测试共同参加,主要关注用例case有没有覆盖到所有新增的需求点,测试、产品、研发同步需求逻辑,保持一致

6、冒烟测试:由测试在用例中标注 p0 级用例,需要研发自测,冒烟测试通过后正式提测 测试阶段 上线

二、项目开发中遇到影响项目进度的问题该如何解决
1、分析问题类型,开发中新增需求点,需要产品延长项目周期或者加人

2、未能在需求评审中发现的突发问题,需要与产品协商增加开发周期或者删减些功能点,保证按规定的时间节点上线主要流程中的需求

3、与后端联调的问题,双方应基于前后端联调规范进行需求联调与问题点定位,如果是非规范性的问题,应该注意前后端的修改成本,成本小的一侧进行问题修改,还要进行风险评估以及影响范围评估,风险小和影响范围小的一侧进行修改

4、需求卡点,需要产品修改需求逻辑

5、技术卡点,提前预知,预留调研时间

三、提测的标准
1、产品走查,开发完成之后需要测试、产品、研发进行新需求逻辑展示,在走查过程中的操作要覆盖

2、产品文档中所阐述的需求逻辑、整体流程,并在走查过程中三方达成一致

3、UI走查,UI 同学根据开发前提供的UI图进行走查,提出问题

4、冒烟测试,由测试提供 p0 级测试用例,研发进行测试覆盖

面试提问基础必问概念及原理
常问问题包括:数据类型、判断方法、 执行上下文 原型、原型链 作用域、作用域链 闭包、垃圾回收 this 指向 继承 同步、异步、事件循环机制 数组原型方法、以及可以改变原数组的方法

(这里的总结只是简短总结,不全面)

一、数据类型及判断方法
类型:基本类型(原始类型)、引用类型
基本类型:Number、String、Boolean、Null、 Undefined、Symbol(六种,Symbol为ES6新增)。这些类型可以直接操作保存在变量中的实际值。(基本数据类型是指存放在栈中的简单数据段,数据大小确定,内存空间大小可以分配,它们是直接按值存放的,所以可以直接按值访问)。

引用类型:引用数据类型也叫对象数据类型,包括function,object,array,date,RegExp等可以使用new创建的数据,又叫对象类型,他们是存放在堆(heap)内存中的数据。

JS中堆与栈:

堆:动态分配的内存,大小不定也不会自动释放,存放引用类型,指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量,实际上保存的不是变量本身,而是指向该对象的指针。(引用类型中可拆分为一个个基本类型,存在栈中,指针指向栈)

比喻:堆就如同保险柜的密码,你要存东西时,就需要去银行申请一个保险柜存东西,只有密码是指向保险柜并打开的,指针就如同是密码。

栈:栈会自动分配内存空间,会自动释放,存放基本类型,简单的数据段,占据固定大小的空间。

延伸:深拷贝与浅拷贝,基本类型不存在深拷贝与浅拷贝,因为存在栈中,赋的是数据。

引用类型的深拷贝就是拷贝了变量的存储数据,比如:深拷贝声明变量数组,找到要拷贝的数组,拿到数组的值,一个个push进去。浅拷贝就是拷贝了变量的指针(地址),浅拷贝声明变量了,但是拷贝了地址,一起共同指向一个内存空间。比如:声明变量数组,找到要拷贝的数组,直接把数组赋值给拷贝的数组。

判断类型的方法:
1、typeof可以测试出number、string、boolean、undefined及function,
而对于null、数组、对象,typeof均检测出为object,不能进一步判断它们的类型。

原因:在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null的类型标签也成为了 0,typeof null就错误的返回了"object"。

2、instanceof不能区别undefined和null,而且对于基本类型如果不是用new声明的则也测试不出来,对于是使用new声明的类型,它还可以检测出多层继承关系。

原理:查找构造函数的原型对象是否在实例对象的原型链上,如果在返回true,如果不在返回false。instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false。null和undefined用instanceof跟任何类型比较时都是false。

二、执行上下文
关键点阐述:多个执行上下文:全局代码 函数代码 eval 代码 函数调用栈,

栈底是全局上下文,栈顶则是当前正在执行的上下文

定义:当函数执行时,会创建一个称为执行上下文的内部对象。一个执行上下文定义了一个函数执行时的环境;

执行上下文的类型:
(1)、全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。

(2)、函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。

(3)、Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用。

执行栈:
定义;也叫调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。

(1)、首次运行JS代码时,会创建一个全局执行上下文并Push到当前的执行上下文栈中。每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并push到当前执行栈的栈顶。

(2)、当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中pop出,上下文控制权将移到当前执行栈的下一个执行上下文。
————————————————
来源 CSDN博主「余光、」的原创文章
原文链接:(2条消息) JavaScript中的执行上下文,既然遇见了这篇图文并茂的文章,干脆看完吧!(系列四)_余光、的博客-CSDN博客

三、原型及原型链
必答的关键点:原型本身也是一个对象,原型的顶端 Object.prototype 原型链,由原型所构成的访问链,

链式查找,一个JS对象的原型指向其父类对象,而父类对象的原型又指向父类对象的父类对象,这种通过原型层层连接起来的关系就是原型链

1、原型定义:
被用于复制现有实例来生成新实例的函数,实例是类在实例化之后一个一个具体的对象.

原型链:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成了实例与原型的链条。

————————————————
来源 CSDN博主「yyuand」的原创文章

原文链接:(2条消息) 原型及原型链详解_yyuand的博客-CSDN博客_原型链

四、作用域、作用域链
关键点阐述:查找方式和原型链原理大致相同,都是按照链式向上查找 全局作用域,块级作用域、暂时性死区 定义函数会创建作用域节点,作用域节点嵌套,向上逐级查找 作用域链的顶端是全局作用域,作用域链在函数定义时就已经创建了。

作用域定义:每个函数都会有自己的执行环境,当执行流进入一个函数时,函数的执行环境就会被推入一个环境栈中。同样,函数在执行完毕之后,栈会将函数环境退出,把控制权交给下一个执行环境。

作用域链定义:当函数在一个执行环境中执行时,会创建变量对象的一个作用域链。
作用域链的用途:保证对执行环境有权访问的所有变量和函数的有序访问。

?————————————————
来源 CSDN博主「yyuand」的原创文章

原文链接:(2条消息) 作用域和作用域链_Distance"的博客-CSDN博客_作用域和作用域链

五、闭包、闭包的特性、垃圾回收机制
闭包 :对闭包的阐述不要过于繁琐,先要把概念阐述简单明了 ,有权访问另一个函数作用域内变量的函数都是闭包 描述闭包的时候要结合实际应用场景进行观念的阐述,比如防抖、截流、函数功能集的封装

闭包的特性 :函数返回嵌套的函数形成闭包 闭包内部可以访问外部的参数和变量 ,外部参数和变量在被闭包引用时不会被垃圾回收机制回收

垃圾回收机制 :利用浏览器垃圾收集器,周期性的回收那些程序中,不被其他引用所指向的变量的内存资源 由于闭包的使用是相互引用的关系,所以不会被回收,良好的使用闭包不会导致内存泄漏

? ————————————————
引入 CSDN博主「BUG使我疯狂」的原创文章

原文链接:(2条消息) 闭包:什么是闭包、闭包的作用、闭包的解决_BUG使我疯狂的博客-CSDN博客_闭包

六、this指向
关键点阐述:this指向在执行上下文创建的阶段,就会建立this指向

this的指向,是在函数被调用的时候确定的

更改this指向的方式方法,原理

需要多刷一些考察this指向的题,搞清楚原理,万变不离其中

1、作为对象的方法调用
当函数作为对象的方法被调用时,this指向该对象

2、作为普通函数调用
当函数不作为对象的属性被调用,而是以普通函数的方式,this总是指向全局对象(在浏览器中,通常是Window对象)

3、构造器调用
除了一些内置函数,大部分Js中的函数都可以成为构造器,它们与普通函数没什么不同

构造器和普通函数的区别在于被调用的方式:
当new运算符调用函数时,总是返回一个对象,this通常也指向这个对象

4、call或apply调用
跟普通的函数调用相比,用call和apply可以动态的改变函数的this

5.箭头函数
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。

因此,在下面的代码中,传递给getVal函数内的this并不是调用者自身,而是外部的this~

?————————————————
来源 CSDN博主「yyuand」的原创文章

原文链接:(2条消息) this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一)_余光、的博客-CSDN博客

七、继承
关键点阐述:继承 原型继承 借用构造函数继承 组合继承 寄生组合继承 class 首先需要描述继承的方式,以及自己常用的方法,搞清楚每个继承方式的原理及优缺点,总结一篇关于讲解各个继承原理的笔记,充分理解之后面试继承的问题就没有可以难倒你的了

定义:继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。

class 子类 extends 父类 {}
?————————————————
来源 CSDN博主「吴迪98」的原创文章

原文链接:(2条消息) JS实现继承的几种方法总结_吴迪98的博客-CSDN博客_js继承的几种方法

八、同步、异步、事件循环机制
关键点阐述:同步,原意就是程序的执行顺序与书写顺寻保持一致

异步,指的是程序并非按照书写的顺序来执行,会存在“跳过”执行的现象 event loop,先执行主线程代码,再执行缓冲队列中的代码 缓冲队列,宏任务、微任务

?————————————————
来源 CSDN博主「吴迪98」的原创文章

原文链接:(2条消息) 同步与异步的事件机制_Tammyhlf的博客-CSDN博客_同步事件和异步事件

九、数组原型方法、以及可以改变原数组的方法
关键点阐述:需要总结关于数组的所有原型方法

改变原数组可以归类为堆栈的处理、填充、排序,就很好记了

reducer、map、splice 需要搞清楚原理,常见应用场景 类数组转换

1、JS数组方法中,会改变原数组的方法 (push、pop、shift、unshift、reverse、sort、splice)

2、JS数组方法中,不会改变原数组的方法 (indexOf 、lastIndexOf 、every 、some 、filter 、map ?、concat 、slice )

?————————————————
来源 CSDN博主「_linWang」的原创文章

原文链接(2条消息) JS(ES5)的数组原型方法中,会改变原数组和不改变数组的方法总结__linWang的博客-CSDN博客_js数组方法哪些会改变原数组

十、react、hooks、fiber
前端高阶常问:

react vue 框架原理以及区别

手写代码

首屏加载性能优化、统计 任务缓冲队列

懒加载

路由模式

浏览器渲染原理及过程

异步加载 async ?defer 作用和区别

最大请求并发数控制

基础算法

1、react、hooks、fiber
1)总结新版本 class fields 组件生命周期钩子,查看组件加载钩子函数执行流程图,不用过多关注16.8版本之前的钩子函数

class类组件的生命周期;

① constructor( ):组件对象被创建了

② render( ):渲染组件内容

③ componentDidMount( ):组件完成挂载,类似于mounted/onLoad ? ?did-do的完成时

? 阶段2:更新阶段(props或state改变)

④ shouldComponentUpdate( newProps, newState ): 此次更新应该重新渲染到视图吗?默认返回true

② render( ):渲染组件内容

? ⑥ componentDidUpdate( ):组件完成更新,类似于updated

? 阶段3:卸载阶段

? ⑦ componentWillUnmount( ):组件即将卸载,类似于beforeMount

function函数组件的生命周期;

//生命周期方法1:任意数据发生改变(从无到有 + 从1到2)

useEffect( ()=>{

? ?//此方法 = 组件挂载 + 任意数据发生改变

} ) ? ?//没有依赖列表

//生命周期方法2:指定数据发生改变(从无到有 + 从1到2)

useEffect( ()=>{

? ?//此方法 = 组件挂载 + 指定数据发生改变

} , [指定数据名, ...] ) ? //指定了依赖列表,且其中有数据

//生命周期方法3:组件挂载(数据从无到有)

useEffect( ()=>{

? ?//此方法 = 组件挂载?

} , [ ] ) ? ?//指定了依赖列表,但其中没有数据

//生命周期方法4:任意数据发生改变直到消亡(从1到2 + 从有到无)

useEffect( ()=>{

? ?return ()=>{

? ? ? ?//此方法 = 任意数据发生改变 + 组件即将卸载

? ?}

} ) ? ?//没有依赖列表

//生命周期方法5:指定数据发生改变直到消亡(从1到2 + 从有到无)

useEffect( ()=>{

? ?return ()=>{

? ? ? ?//此方法 = 任意数据发生改变 + 组件即将卸载

? ?}

} , [指定数据名, ...]) ? ?//指定了依赖列表,且其中有数据

//生命周期方法6:组件卸载(从有到无)

useEffect( ()=>{

? ?return ()=>{

? ? ? ?//此方法 = 组件即将卸载

? ?}

} , [ ]) ? ?//指定了依赖列表,但其中没有数据

(2)useState、useEffect、useCallback、useMemo等常见钩子函数用法以及原理,hooks函数要放在函数组件的顶层,组件每次更新渲染会按照执行顺序进行 hooks 处理,useEffect 每次更新会先执行上一次的函数返回中的内容

useCallback 返回一个函数,只有在依赖项发生变化的时候才会更新(返回一个新的函数),多用于生成一个防抖函数.注意:组件每次更新时,所有方法都会重新创建,这样之前写的防抖函数就会失效,需要使用 useCallback 包裹
useMemo 只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值, 类似于 vue 中的 computed 计算属性
(3)自定义 hooks,常用于独立封装请求处理或者功能类的组件化处理

(4)高阶组件用法,抽象独立组件,采用组合的模式进行组件的二次封装,常见对全系统的权限问题或者封装全局属性和工具类的用法

(5)fiber 树的概念,链式数据模式,每个树的节点包含 虚拟dom(对象)、子节点、相邻节点

解决 react 旧版本,更新页面时会出现丢帧卡顿的问题
React Fiber 是把一个大任务拆分为了很多个小块任务,一个小块任务的执行必须是一次完成的,不能出现暂停,但是一个小块任务执行完后可以移交控制权给浏览器去响应用户操作
核心是通过 requestIdleCallback ,会在利用浏览器空闲时间会找出所有需要变更的节点
阶段一,生成 Fiber 树,得出需要更新的节点信息,这一步是一个渐进的过程,可以被打断
阶段二,将需要更新的节点一次性批量更新,这个过程不能被打断
(6Diff 算法,不必须深入研究其源码,算法等等,可以深入了解官方文档对 diff 操作的定义

什么叫虚拟dom,就是原本我们在页面上展示的dom结构叫做dom树,我们把数据和将要渲染的代码模拟dom结构生成的对象类型的数据结构,就叫虚拟dom树,将真实的DOM的数据抽取出来,以对象的形式模拟树形结构,我们先根据真实DOM生成一颗 virtual DOM ,当 virtual DOM 某个节点的数据改变后会生成一个新的 Vnode ,然后 Vnode 和 oldVnode 作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使 oldVnode 的值为 Vnode 。
原始diff算法就是遍历比较,vue和react的diff算法都是优化过的diff算法,而且有着相同的优化点,就是同级比较,不做跨级比较.同级比较的解释就是:只比较同层的节点,不同层不做比较。不同层的只需要删除原节点,并且新建插入更新节点
Vue与React中diff算法的不同点:核心点就是再优化对比的效率,通过对不同情况的分析,做出每种情况的最优解决办法.
Vue使用的是双向绑定,不是重新构建组件,主要还是进行对比(tree diff)进行对比,根据对比跟新,四种对比方法头->头,尾->尾,头->尾,尾->头(同级策略)
React用的是setState,通过重新渲染组件覆盖之前的数据, (1)同级策略对比(2)同一类型的组件,a变化时,虚拟DOM没有变,可以在这里进行shouldComponentUpdate操作
如果被判定为不同类型的组件,删除原组件,构建新组件3.element diff:元素的diff就是有差别的地方了,react采用的是比较vue而言更加简洁的对比方式,就是同级遍历,拿着新节点列表的节点一个个与旧节点列表做遍历对比,当对比成功的时候,就把旧节点插入到真实dom中当前新节点位置对应的真实dom的前边,若是没有找到对应的,就要在新节点所对应的真实dom的地方插入一个节点
不同点网址:vue与react的diff算法_Absorbed_zhang的博客-CSDN博客_react和vue的diff算法
2、react vue 框架原理以及区别
(1)数据的处理部分,说明 react 单项数据流和 vue 双向数据流

数据流就是:数据在组件之间的传递
单向数据流就是:数据在某个节点被改动后,只会影响一个方向上的其他节点。父组件的数据通过props传递给子组件,而子组件更新了props,导致父组件和其他关联组件的数据更新,UI渲染也会随着数据而更新。毫无疑问,这是会导致严重的数据紊乱和不可控制的。不能是双向的。
vue双向数据流v-model只作用于有value属性的元素:vue页面改变影响内存(js),内存(js)改变影响vue页面,v-bind 单向数据绑定只是内存(js)改变影响vue页面
(2)渲染原理

当组件的状态发生变化时,vue 是响应式,通过对应的 watcher 自动找到对应的组件重新渲染
react 需要更新组件时,会重新走渲染的流程,通过从根节点开始遍历,dom diff 找到需要变更的节点,更新任务还是很大,需要使用到 Fiber,将大任务分割为多个小任务,可以中断和恢复,不阻塞主进程执行高优先级的任务
(3)React 是函数式编程思想all in js 思想,对 js 原生书写应用较为广泛,两种组件模式,class feilds 和 hooks,组合和受控的概念,模版语法没有 vue 丰富,使用更加灵活,对数据的处理和渲染性能方面更为强大,应用场景更适合大型管理后台,一般 toB 的业务应用 react 比较广泛

(4)Vue 是响应式编程模式使用简单,对初级工程师上手更快,模版语法丰富,处理丰富的业务场景更加方便,更加适合 C 端的业务场景

(5)v-model 双向数据绑定相比于 react 受控模式进行了底层封装,对数据的交互更加方便

React中的setState与useState
1.setState(方法)

在 React 中,组件分为 有状态组件 和 无状态组件,有状态组件就是能够定义 state 的组件,比如类组件,无状态组件反之,比如函数组件。state 就是用来描述事物在某时刻的数据,可以被改变,改变后与视图相映射,用来保存数据和响应视图。
虽然状态可以改变,但不是响应式的,动态改变并没有与视图响应,想要改变并响应视图则需要 setState 修改并更新视图。
使用方法:this.setState({ 修改的数据 })
setState并不会影响其他没有进行修改的数据,此方法是从Component中继承过来
原理: 状态有个特性是不可变性,就是不可以直接修改原数据,而是直接产生一个新的数据,通过 setState 方法把新的数据覆盖原有的数据,从而进行响应视图,这么做有利于性能优化和 SCU。setState触发的生命周期钩子rander与componentDidUpdata

同步:虽说 setState 很多人说是异步的,但是它本身的执行过程和代码是同步的,只是它在合并数据与钩子函数的调用顺序在更新后无法拿到值,形成了 异步 ,我们可以通过第二个参数拿到更新后的结果。

2.useState(函数)

const [count, setCount] = useState(initialCount)
它返回一个状态和一个修改状态的方法,状态需要通过这个方法来进行修改;
initialCount 是我们传入的一个初始状态,它是惰性的,我们可以通过传一个函数来返回一个值当作初始状态,并且这个函数只会在初始渲染时执行一次;
十一、Promise 【*】
Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、reject*(拒绝)、resolve(解决)这几个方法,原型上有then、catch等方法。

1、特点:
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

?————————————————
来源 CSDN博主「胖小傻」的原创文章

原文链接:ES6 Promise用法小结_傻小胖的博客-CSDN博客_promise
————————————————
版权声明:本文为CSDN博主「隔窗看世界」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_73416102/article/details/128064924

文章来源:https://blog.csdn.net/m0_68271787/article/details/135699479
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。