2023面试题
一、JS
- js的循环机制
- js是单线程的,为了防止代码阻塞会将代码分成同步和异步;
- 同步代码交给js引擎执行,异步代码交给宿主环境(浏览器、node);
- 同步代码放入执行栈中,异步代码等待时机成熟送入任务队列排队;
- 执行栈执行完毕,会去任务队列看是否有异步任务,有就送到执行栈执行,反复循环查看执行代码;
- js的事件委托
- 事件委托就是把原本需要绑定在子元素上的事件(onclick、onkeydown 等)委托给它的父元素,让父元素来监听子元素的冒泡事件,并在子元素发生事件冒泡时找到这个子元素。(减小内存消耗、动态绑定事件)
- 宏任务和微任务
- JS 里的一种任务分类方式分为: 同步任务和异步任务(微任务和宏任务,存在微任务的话,那么就执行所有的微任务,微任务都执行完之后,执行下一个宏任务);首先判断 js 代码是同步还是异步,不停的检查调用栈中是否有任务需要执行,如果没有,就检查任务队列,从中弹出一个任务,放入栈中,如此往复循环,要是同步就进入主进程,异步就进入事件表,同步任务进入主线程后一直执行,直到主线程空闲时,才会去事件队列中查看是否有可执行的异步任务,如果有就推入主进程中。
- 宏任务:script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI render;
- 微任务:proccess.nextTick、Promise、Async/await(实际上就是promise)、MutationObserver(html5新特性)
- js判断数据类型的方法以及区别
typeof检测的Array和Object的返回类型都是Object,因此用typeof是无法检测出来数组和对象的;
instaceof只可以用来判断数组和对象,不能判断string和boolean类型;
- 使用
constructor方法(a.constructor == Array)使用constructor是不保险的,因为constructor属性是可以被修改的,会导致检测出的结果不正确;
Object.prototype.toString.call()是最好的
- 有哪些操作数组的方法?
join():方法用于把数组中的所有元素转换一个字符串,不改变原数组
push(): 方法从数组末尾向数组添加元素,可以添加一个或多个元素,返回数组长度。
pop(): 方法用于删除数组的最后一个元素并返回删除的元素。
shift(): 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
unshift(): 方法可向数组的开头添加一个或更多元素,并返回新的长度。
sort(): 方法用于对数组的元素进行排序,改变原数组。
reverse(): 方法用于颠倒数组中元素的顺序,改变原数组。
concat(): 方法用于连接两个或多个数组,不改变原数组。
slice():返回从原数组中指定开始下标到结束下标之间的项组成的新数组。
splice():很强大的数组方法,它有很多种用法,可以实现删除、插入和替换。
indexOf():从数组的开头(位置 0)开始向后查找,在没找到的情况下返回-1。
- `forEach(遍历的数组内容,第对应的数组索引,数组本身)
map(): 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值,不会改变原数组。
filter():“过滤”功能,数组中的每一项运行给定函数,返回满足过滤条件组成的数组。
every():判断数组中每一项都是否满足条件,只有所有项都满足条件,才会返回 true。
some():判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回 true。
includes(): 方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则 false。
reduce(前一个值,当前值,项的索引,数组对象):方法从数组的第一项开始,逐个遍历到最后。
Array.keys()是对键名的遍历、Array.values()是对键值的遍历,Array.entries()是对键值对的遍历
- map和forEach的区别?
- map速度比forEach快;
- map会返回一个新数组,不对原数组产生影响,foreach不会产生新数组,forEach返回undefined;
- map因为返回数组所以可以链式操作,forEach不能;
- map里可以用return(return的是什么,相当于把数组中的这一项变为什么(并不影响原来的数组,只是相当于把原数组克隆一份,把克隆的这一份的数组中的对应项改变了) ,而forEach里用return不起作用,forEach不能用break,会直接报错;
- es6+的新语法
- var let const的区别
- var 存在变量提升 而 let 与 const 不存在变量提升;
- var定义的变量可以声明多次,而let、const定义的变量只能声明;
- var、let声明的变量可以再次赋值,而const声明的变量不能再次赋值(注意常量里面所包含的内容还是可以改的);
- var声明的变量没有自身的作用域,而ler、const声明的变量有自身的作用域
- async和await的联系以及作用、原理
- 在做异步请求时,我们通常用async await的方式,可以将异步转为同步的写法,async函数内部,代码是一行一行执行的(同步执行),然后async函数会返回一个promise对象,并把最后的返回值作为此promise的resolve结果的值。
- 对this的理解以及改变this的方法
- this永远指向函数运行时所在的对象;
call():第一个参数表示要把this指向的新目标,第二个之后的参数其实相当于传参,参数以,隔开(性能较apply略好);
apply()第一个参数同上,第二个参数接受一个数组,里面也是传参,只是以数组的方式,相当于arguments;
bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数(返回对应函数,便于稍后执行,上两个立即执行);
- object.prototype的使用场景
- ts type和interface的区别
- type - 类型别名,可以为原始类型重命名,而 interface 不可以 type A = number;
- type 定义的类型可以使用一些操作符,但是 interface 不行 type A = typeof obj / type A = keyof obj
- type 可以定义元组类型 type A = [number, string];
- type 可以使用交叉类型和联合类型 type A = A1 | A2 / type B = B1 & B2
- interface 可以重复定义会进行声明合并,但是 type 不可以
- commonjs和esmodule的区别
- 两者的模块导入导出语法不同,CommonJs是通过module.exports,exports导出,require导入;ESModule则是export导出,import导入。
- CommonJs是运行时加载模块,ESModule是在静态编译期间就确定模块的依赖;
- ESModule在编译期间会将所有import提升到顶部,CommonJs不会提升require。
- CommonJs导出的是一个值拷贝,会对加载结果进行缓存,一旦内部再修改这个值,则不会同步到外部。ESModule是导出的一个引用,内部修改可以同步到外部。
- CommonJs中顶层的this指向这个模块本身,而ESModule中顶层this指向undefined。
- CommonJS加载的是整个模块,将所有的接口全部加载进来,ESModule可以单独加载其中的某个接口;
- export和export default的区别
- 在一个文件或模块中,export可以有多个,export default仅有一个;
- 通过export方式导出,在导入时要加{ },且不能自定义名字,export default不用加{ },且可以自定义名字
- 闭包使用场景以及为什么变量不会被回收
- 返回值(最常用),IIFE,函数柯里化,节流
- 垃圾回收机制
- promise的优缺点?如何实现promise.all()
- 优点:
- 缺点:
- 无法取消,一旦新建立就会立即执行
- 如果不设置回调函数,内部抛出的错误不会反应到外部
- 当处于pending状态时,无法得知目前进展到哪一个阶段
- Map对象和普通对象有啥区别
- 普通对象可以直接使用字面量初始化,而Map需要Map()构造函数初始化,如果想要初始值,则需要传递一个数组或其他元素为键值对的可迭代对象;
- 普通对象只接字符串和符号作为键值,其他类型将被强制转换为字符串类型,而Map可以接受任何类型的键值;
- 普通对象从原型继承了许多属性键,例如构造函数等,所以自己的密钥很可能与原型上的密钥发生冲突。但Map默认不包含任何键;
- 普通对象不能直接迭代;
- 普通对象支持JSON序列化,但Map默认无法获取正确的数据
- Map对象在涉及频繁添加和删除键值对的场景中表现更好,而普通对象没有优化;
- ...是啥拷贝方式?还有哪些拷贝方式?数据为null时咋拷贝
- 普通函数和箭头函数的区别?箭头函数如何获取arguments
- 箭头函数没有自己的this,arguments,super或new.target。
- 它的this、arguments都是在定义函数时绑定外层的this和arguments,而不是在执行过程中绑定的,所以不会因为调用者不同而发生变化;
- 箭头函数若想得到自身的入参列表arguments,必须使用剩余参数表示法。
- 箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
- new的过程
- 首先创建一个空对象;
- 将空对象指向构造函数的原型链
- 将this绑定给空对象,并执行构造函数
- 返回构造函数的值或者this
- 数组平铺
- 递归处理:遍历每个元素,元素是基本数据类型的话便放进新数组中,如果是数组类型则进行递归处理;
- 通过es6的reduce()方法遍历每个元素,再结合concat()方法进行拼接;
- 使用String()将多维数组处理字符串,无论有多少层,都会处理成普通字符串,然后再将字符串分割成数组(注number最后需要再转换一下)
- flat(Infinity)
- ts和js比较
- ts如何强制转换数据类型
- 原型和原型链
- js中所有引用类型都有一个__proto__(隐式原型属性),属性值是一个对象
- 引用类型__proto__属性指向它的构造函数prototype
- 所有函数都有一个prototype原型属性,属性值是一个对象
- 当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去他的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没找到就会在构造函数的prototype的__proto__中查找,直到找到OBject原型为止,这样一层一层向上查找会形成一个链式结构
- 强缓存和协商缓存
- 防抖和节流
- 防抖:单位时间内,频繁触发一个事件,以最后一次触发为准(监听input输入框)
- 节流:单位时间内,频繁触发一个事件,只会触发一次(监听scroll滚动事件,按钮点击)
- any和unknown的区别
- ts的模块化
- ts的泛型
- class怎么继承?super的作用
二、css
- 盒模型
- 标准W3C盒子模型:包括 margin、border、padding、content组成,并且content部分不包含其他部分
- IE盒子模型:包括 margin、border、padding、content组成,和标准的W3C盒子模型不同的是,IE盒模型的content部分包含了border和padding。
- 盒子总宽度/高度 =
width/height + padding + border + margin。( 即 width/height 只是内容高度,不包含 padding 和 border 值 )
- IE盒子模型:盒子总宽度/高度 =
width/height + margin = (内容区宽度/高度 + padding + border) + margin。( 即 width/height 包含了 padding 和 border 值 )
- 水平垂直居中的方法
- flex:1属性是啥?有啥作用?
- flex:1(占满剩余的父级空间)==>
flex:1 1 auto;flex-grow(放大比例);flex-shrink(缩小比例);flex-basic(flex元素的内容盒(content-box)的尺寸,默认为auto);
- 常用的flex属性
flex-direction:决定主轴的方向;
flex-wrap:决定如何换行;
flex-flow:flex-direction和flex-wrap属性的简写形式等等...
- 定位
static:静态定位,用于取消定位,静态定位的盒子处于网页的最底层
relative: 相对定位,不脱离文档流,参考自身静态位置定位
absolute:脱离文档流,选取其最近的父级定位元素,当父级 position 为 static 时,absolute元素将以body坐标原点进行定位
fixed:固定定位,脱离文档流,相对浏览器进行偏移
sticky:粘性定位,IE不兼容
- px em和rem的区别
- px是固定像素,一旦设置就无法因为适应叶念而改变
- em/rem: 用于做响应式页面,不过我更倾向于rem,因为em不同元素的参照物不一样(都是该元素父元素),所以在计算的时候不方便,相比之下rem就只有一个参照物(html元素),这样计算起来更清晰。
- css3新特性
- 伪类和伪元素的区别
- 伪元素:不存在在DOM文档中,是虚拟的元素,是创建新元素,在一个选择器中只能出现一次,并且只能出现在末尾;(
::before,::after)
- 伪类:存在DOM文档中,(无标签,找不到, 只有符合触发条件时才能看到 ), 逻辑上存在但在文档树中却无须标识的“幽灵”分类。(
:visited,:active,:first-child)
- 浏览器如何解析css
三、Vue
- 为什么v-for不能和v-if放在同一层级?v-for的key有啥作用?什么场景下不能用索引值?
- v-for的优先级比v-if高,一起使用会造成性能浪费。解决方案把v-if放在v-for的外层或者把需要v-for的属性先从计算属性中过滤一次;
- key的作用主要是为了高效的更新虚拟DOM;
- 防止不必要的bug出现
- Vue 采用“就地更新”的策略来更新DOM,当数据项的顺序发生改变,Vue不会随之移动DOM元素的顺序,而是就地更新每个元素,如果使用 index 有时候会导致渲染异常或者错位的现象
- 说说vuex
state:vuex的基本数据,用来存储变量(this.$store.state、mapState辅助函数)
getters:从基本数据派生的数据,相当于state的计算属性(this.$store.grtters.x、mapGetters)
mutations:提交更新state数据的方法,同步(this.$store.commit('方法')、mapMutations 辅助函数)
action:提交的是mutations,而不是直接变更状态,异步(this.$store.dispatch('方法)、mapActions辅助函数)
module:模块化vuex,使得结构非常清晰,方便管理
- 组件通信(祖孙通信方式的优缺点)
props:父组件传递数据给子组件,子组件设置props,定义接受父组件传递过来的参数,父组件再使用子组件标签中通过字面量来传递值
$emit:子组件传递数据给父组件,子组件通过emit触发自定义事件,emit第二个参数为传递的数值,父组件绑定监听器获取子组件传递过来的参数
ref:父组件再使用子组件的时候设置ref,通过设置子组件ref来获取数据
evenBus:兄弟组件传值,创建一个中央事件总线evebBus,兄弟组件通过emit触发自定义事件,emit第二个参数为传递的数值,另一个兄弟组件通过$on监听自定义事件
$parent或 $root:兄弟组件传值,通过共同祖辈$parent或 $root搭建通信桥梁
$attrs与$listenners:祖先传递数据给子孙,设置批量向下传属性attrs和listeners,包含了父级作用域中不作为prop被识别(且获取)的特性绑定(class和style除外),可通过v-bind="$attrs"传入内部组件
provide与inject:在祖先组件定义provide属性,返回传递的值;在后代组件通过inject接受组件传递过来的值
- 优点:在父组件只要声明了
provide,在其子组件,孙组件,曾孙组件等能形成上下游关系的组件中交互,无论多深都能通过inject来访问provider中的数据
- 由于支持任意层级都能访问,导致数据追踪比较困难。不知道那一层级声明了
provide又或是哪些层级使用了inject。造成比较大的维护成本
- 公共组件布局不一致怎么解决
- computed和watch的区别以及性能?watch的属性
computed:计算属性
- 属性值默认会缓存计算结果,在重复的调用中,只要依赖数据不变,直接取缓存中的计算结果,只有依赖型数据发生改变,computed 才会重新计算
- 在computed中的,属性都有一个 get 和一个 set 方法,当数据变化时,调用 set 方法
watch:监听器
- 主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作
- watch支持异步
- 不支持缓存,监听的数据改变,直接会触发相应的操作
- 监听函数有两个参数,第一个参数是最新的值,第二个参数是输入之前的值,顺序一定是新值,旧值。
- 监听复杂数据类型就需要用到深度监听 deep
- 父组件向子组件动态传值时,子组件props首次获取到父组件传来的默认值时,此时也需要执行watch监听函数。则需设置immediate:true
- created和mounted的区别
- created:已创建
- 实例已经被初始化,但是还没有挂载至 $el上,所以我们无法获取到对应的节点,但是此时我们是可以获取到vue中data与methods中的数据的
- mounted:已挂载
- vue的template成功挂载在$el中,可进行dom操作
- $nextTick理解以及原理,为什么要用它?
- 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
- data为啥是一个对象
- vue2操作数组试图不更新咋解决,底层原理是啥?
- vue2和vue3比较
- 父组件与子组件的生命周期顺序
- diff算法
- 当数据发生改变时,set方法会调用Dep.notify通知所有订阅者Watcher,订阅者就会调用patch给真是的DOM打补丁,更新相应的视图
- 同层级比较,不会跨层级比较
- 在diff比较的过程中,循环从两边向中间比较
- elementui是如何实现按需加载的以及如何二次封装ui组件
- keep-alive原理
- 是vue的内置组件,会缓存不活动的组件实例
- include:指定哪些组件被缓存,可缓存多个以逗号隔开
- exclude:指定不被缓存
- 钩子执行顺序:
- 初始进入和离开 created ---> mounted ---> activated --> deactivated
- 后续进入和离开 activated --> deactivated
- ref和reactive的区别
- 从定义数据方面:
- ref通常用来定义基本类型数据
- reactive用来定义对象或者数组类型
- 从原理方面
- ref通过Object.defineProperty()的get和set实现数据代理
- reactive使用Proxy实现数据代理,并且通过Reflect操作源对象内部的数据
- 从使用方面
- ref操作数据都需要.value,template模板中不需要
- reactive不需要.value
- hash和history的原理和区别
- hash:
- hash带有#号,hash值变化不会导致浏览器向服务器发起请求,不会重新加载页面
- 通过监听浏览器的onhashchange()事件变化,查找对应路由规则
- history:
- history每次刷新会重新向后端请求整个网址,也就是重新请求服务器;如果后端没有及时响应,就会报错404;history的好处是可以进行修改历史记录,并且不会立刻向后端发起请求
- history利用h5的pushState和replaceState和一个事件onpopState监听url变化
- slot
四、HTTP
- http请求过程
- http和https区别
- 常见状态码
- 1XX:服务器收到请求
- 2XX:请求成功
- 200:正常的返回成功
- 201:已创建,通常用在POST
- 3XX:重定向
- 301:永久转移
- 302:资源被找到
- 304:没有修改、缓存
- 4XX:客户端错误
- 401:未授权
- 403:禁止访问
- 404:没有找到
- 5XX:服务端错误
- 500:内部服务器错误
- 502:网关错误
- 503:服务不可用(内存用光、线池溢出、服务正在启动)
- 504:网关超时
- 常见header头
- Content-Length
- User-Agent
- 帮助区分客户端特性的字符串(操作系统、浏览器、制造商、内核信息、版本号)
- Content-Type
- Origin
- Accept
- Referer
- 告诉服务器打开页面的上一张页面的url;
- 如果是ajax请求就告诉服务端发送的url是什么
- Connection
- cookie和token的区别
- http复杂请求和简单请求的区别
- 强缓存和协商缓存
- 强缓存
- 强缓存是在网页数据被请求之前,浏览器会先检查本地缓存是否过期,如果没有过期就直接使用本地缓存,不需要向服务器发送请求。一般通过expires和cache-control来控制
- 协商缓存
- 在强缓存失效后,浏览器会向服务器发起请求,服务器收到请求后不会直接返回数据,先根据请求中携带的头部字段信息验证缓存的有效性,如果本地缓存仍然可用,则返回304并告诉浏览器继续使用缓存。用last-modified/if-modified-match(利用服务器上文件最后修改时间来判断缓存是否过期)和etag/if-none-match控制(用一个hash算法生成唯一标识来判断缓存是否过期)
五、打包工具
- webpack和vite的区别
- 热更新
- webpack运行流程以及用它做了些什么
- webpack如何解决跨域
六、项目中问题
- 向下跨级传递数据
- 向上跨级处理事件
- 大文件上传处理
- 如何让几十万条数据请求完后再进行其他操作
- 大图片如何渲染更快
- 封装组件需要注意什么
七、必问
- 性能优化
- 项目中遇到的难点以及怎么解决
- 项目中的亮点
- 和别人相比你的优势