一,【css部分面试题】
01,选择器权重
!important>行内样式>id选择器>类选择器>标签选择器>通配符>继承
选择器平级的情况下,下面的css权重,会高于上面的css权重
02,继承
在css中,继承是指的是给父元素设置一些属性,后代元素会自动拥有这些属性
无继承的属性
- display
- 文本属性:vertical-align、text-decoration
- 盒子模型的属性:宽度、高度、内外边距、边框等
- 背景属性:背景图片、颜色、位置等
- 定位属性:浮动、清除浮动、定位position等
- 生成内容属性:content、counter-reset、counter-increment
- 轮廓样式属性:outline-style、outline-width、outline-color、outline
- 页面样式属性:size、page-break-before、page-break-after
03,css动画
04,常见布局
05,flex
06,px,em,rem,vw
px:绝对单位,页面按精确像素展示
em:相对单位,基准点为父节点字体的大小,如果自身定义了font-size按自身来计算,整个页面内1em不是一个固定的值
rem:相对单位,可理解为root em, 相对根节点html的字体大小来计算
vh、vw:主要用于页面视口大小布局,在页面布局上更加方便简单
EM是相对于元素自身字体大小的一个单位,而REM是相对于根元素(html)字体大小的一个单位
07,适配
rem是相对于根元素字体大小的一个单位,他可以用来解决移动端适配的问题,适配的原理就是利用媒体查询或js动态检测适配的宽度,不同宽度下设置对应的字体大小,根元素的字体大小发生了变化,所以用rem做单位的元素也就跟着一起变了,不过在日常开发中,也可用vw做适配,在一定程度上来说,rem在做适配这方面,就是对vw的一种模拟
08,css预处理
扩充了 Css 语言,增加了诸如变量、混合(mixin)、函数等功能,让 Css 更易维护、方便
本质上,预处理是Css的超集
包含一套自定义的语法及一个解析器,根据这些语法定义自己的样式规则,这些规则最终会通过解析器,编译生成对应的 Css 文件
09,常见css代码片段
清除浮动
溢出文本隐藏
居中
二,【js部分面试题】
01,闭包
-
1.闭包是什么 :
- 闭包 是 使用其他函数内部变量的 函数 (闭包 = 函数 + 其他函数内部变量)
- 闭包 = 函数 + 上下文引用
-
2.闭包作用 : 创建私有变量,解决全局变量污染
-
缺点: 容易造成内层泄露,因为闭包中 的局部变量永远不会被回收
-
应用场景:闭包随处可见。
-
手写闭包
function fn() {
var num = 10;
function fun() {
console.log(num);
}
return fun;
}
var f = fn();
f();
02,原型链
每一个实例对象都有一个原型,而原型也是对象,也有自己的原型,以此类推形成的链式结构被称为原型链
对象访问原型链的规则是就近原则
主要作用是实现继承实际应用:vue注册全局组件,之所以能够全局使用,就用到了原型链的继承,instanceof运算符也是通过原型链的作用实现判断数据
03,箭头函数和普通函数的区别
箭头函数是ES6新增的特性,用来简化普通函数的写法,规避普通函数this指向的痛点
- 箭头函数没有原型对象prototype,不能作为构造函数使用(不能被new)
- 箭头函数没有arguments,可以使用
...拿到所有实参的集合数组 - 箭头函数中的this在定义时就已经确定,取决于父级的环境
- 箭头函数不能通过call,bind,apply方法修改this指向(会忽略第一个参数,其他功能正常使用)
- 箭头函数不能用作Generator函数,不能使用yeild关键字(function*)
自己的话:
箭头函数是ES6新增的特性,用来简化普通函数的写法,他与普通函数的区别主要有三点 1,没有this 2,可以简写 3,内部没有arguments,只能使用扩展符
04,this指向
05,防抖和节流
防抖定义:单位时间内,频繁触发事件,只会触发最后一次
使用场景:窗口大小改变、输入框内容校验等
防抖流程:
- 声明一个全局timeID储存定时器id
- 每一次触发点击事件,先清除上一个定时器,以本次触发为准
- 开启本次定时器
代码:以输入框为例
//(1)声明一个全局timeID存储定时器id
let timeID = null
document.querySelector('input').addEventListener('input',function(){
//(2)每一次触发事件,先清除上一次定时器,以本次触发为准
clearTimeout(timeID)
//(3)开启本次定时器
/*
function函数: 定时器this指向window
箭头函数 : 访问上级作用域this-> input
*/
timeID = setTimeout( ()=>{
console.log(`您的搜索内容为${this.value}`)
},500)
})
节流定义:单位时间内,频繁触发事件,只会触发一次
使用场景:鼠标不断点击触发、监听滚动事件、下拉加载等
防抖流程:
- 声明一个全局变量记录当前触发时间
- 每一次触发事件的时候,获取当前时间
- 判断:当前时间 - 上一次时间 >= 节流间隔
- 储存本次触发事件 代码:
//(1)声明一个全局变量记录 当前触发时间
let lastTime = null
//页面滚动条事件
let i = 1
window.onscroll = function(){
//(2)获取现在时间
let currentTime = Date.now()
//(3)判断 当前时间 - 上一次触发事件 >= 节流间隔
if( currentTime - lastTime >= 500 ){
console.log(`第${i++}次触发`)
lastTime = currentTime
}
}
相同点:降低高频事件
06, 递归及应用场景
1.递归 : 在函数内部调用自己 (问题相同, 规模不同) 尾递归
优点:结构清晰、可读性强
缺点:效率低、调用栈可能会溢出,其实每一次函数调用会在内存栈中分配空间,而每个进程的栈的容量是有限的,当调用的层次太多时,就会超出栈的容量,从而导致栈溢出。->性能
2.递归应用 :
- 浅拷贝与深拷贝
- 遍历dom树
- 数据扁平化处理
07,new的背后做哪些事情
- 创建一个新的对象
- 让构造函数的this指向这个新对象
- 执行构造函数(给对象赋值)
- 返回实例(返回这个对象)
08,bind、call、apply 区别
作用:修改this指向
相同点: 第一个参数都是用来修改this指向
都可以利用后续参数传参
不同点:
传参方式不同,call和bind单个传参,可以传任意多个参数,bind可以多次传参,apply通过数组或伪数组传参
执行机制不同,call和apply直接调用函数,bind不会立即执行,而是得到一个新函数在开发中运用比较少,但是见过类似代码: 实际应用:
call可以用于万能数据检测
objject.prototype.tostring.call() 利用apply传参的特点将伪数组转换为真数组,求最大值
math.max.apply(null,arr) bind修改定时器this指向
09,继承
- 原型链继承(适合继承方法)
通过构造函数,实例对象,原型对象三者之间的关系- 构造函数继承(借助 call,继承属性)
通过call的两个做用,调用函数,修改this指向完成
调用构造函数,修改它的this指向,传入参数- 组合继承(原型继承+借用构造函数继承)
原型继承解决了方法的继承问题,借用构造函数继承解决了属性继承问题- 原型式继承
- 寄生式继承
- 寄生组合式继承
继承属性和方法
10,promise的使用
promise三个状态: pending(默认) fulfilled(成功) rejected(失败)
- Promise的构造函数接收一个参数,是函数,并且传入两个参数:resolve,reject
- resolve 函数被执行时, 会将 promise 的状态从 pending 改成 fulfilled 成功
- reject 函数被执行时, 会将 promise 的状态从 pending 改成 rejected 失败
new Promise((resolve, reject) => {
resolve()
})
new Promise((resolve, reject) => {
reject()
})
作用:解决回调地狱的问题
原理:控制异步代码结果处理的顺序
可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作
自己的话:
promise它是es6新增加地特性,是一个函数,一般都当做构造函数来使用,用的时候要new一下来得到一个promise实例,它内部有三种状态,分别是pengding,成功和失败,成功会触发then,失败会触发catch
promise它主要是解决回调地狱的问题,不过我通常会用async和await,因为他们不仅可以解决回调地狱的问题还可以简化代码
promise.race() 接收多个promise实例,可以得到最先处理完毕的结果(可能是成功,也可能是失败)
promise.all([])
两个请求之间没有先后逻辑顺序就用并发
它返回结果的顺序和书写的顺序是一一对应的
全部正确.then捕获结果
有一个错误.catch捕获结果
11,数组常用API
数组添加:
push()
push()方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度
unshift()
unshift()在数组开头添加任意多个值,然后返回新的数组长度
splist()
splist()传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回空数组concat()
concat()首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组
数组删除:
pop()
pop()方法用于删除数组的最后一项,同时减少数组的length值,返回被删除的项
shift()
shift()方法用于删除数组的第一项,同时减少数组的length值,返回被删除的项
splist()
splist()传入两个参数,分别是开始位置,删除元素的数量,返回包含删除元素的数组
slice()slice() 用于创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组
更改数组:
splist()
splist()传入三个参数,分别是开始位置,要删除元素的数量,要插入的任意多个元素,返回删除元素的数组,对原数组产生影响
查找数组:
indexOf()
indexOf()返回要查找的元素在数组中的位置,如果没找到则返回 -1
includes()
返回要查找的元素在数组中的位置,找到返回true,否则false
find()
返回第一个匹配的元素
遍历数组:
map()
map()返回删除的元素,映射数组(将数组每一个元素处理之后,得到一个新数组)
filter()
filter()筛选,根据条件筛选数组,将符合条件的元素放入新数组中
forEach()
forEach()类似于for循环遍历数组
findIndex()
findIndex()找元素下标
12,高阶函数
13,函数柯里化
14,如何判断数据类型
typeof
typeof操作符返回一个字符串,表示未经计算的操作数的类型
对于引用类型数据,除了function会被识别出来,其他都输出object类型
null也输出object类型
instanceof
instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上
instanceof可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型
Object.prototype.toString.call()
万能数据检测类型 实际应用:在vue源码中大量使用
15,for...in,for...of的区别
- 功能不同:for...in可以遍历数组和对象,返回下标和元素,for...of通常用来遍历数组,返回元素
- for...in可以把原型遍历出来
- for...of想要遍历对象,必须搭配Object.keys()使用
16,深拷贝与浅拷贝
- 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
- 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址 在
JavaScript中,实现浅拷贝的方法有:
Object.assignArray.prototype.slice(),Array.prototype.concat()- 使用拓展运算符实现的复制
实现深拷贝的方法:
- 推荐 json : let obj = JSON.parse( JSON.stringify( 对象 ) )
- 递归
17,事件委托
所谓的事件委托,就是把子元素绑定的事件,统一委托(绑定)到祖先身上
原理是事件冒泡
性能高
后续新增的元素同样具有事件绑定的效果
自定义属性解决按需事件
自己的话:
事件委托,就是通过事件冒泡的原理,把子元素绑定的事件,统一委托(绑定)到祖先元素身上,这样做的好处有两点,1是性能高,2是对后续新增的元素同样具有事件绑定的效果.
事件委托在jquery的时代还是有应用场景的,但现在流行前端框架,我们比较少的使用
18,函数传参,传递简单数据类型和复杂数据类型有怎样的差异
简单数据类型传递的是值的拷贝,函数内部对参数的修改不会影响外部
复杂数据类型传递是引用地址,函数内部对参数内容的修改会影响外部,对参数引用地址的修改不会影响外部
三,【浏览器部分面试题】
01,eventLoop
- eventloop又叫事件循环,是单线程语言js在运行胆码时不被阻塞的一种机制
- js代码的执行分为同步代码和异步代码,当碰到同步代码时直接在执行栈中执行
- 当碰到异步代码并且时机符合时(例如定时器事件到了),就会把异步代码添加到任务队列中
- 当执行栈中的同步代码执行完毕后,就去任务队列中把异步代码拿到执行栈中执行
- 这种反复轮训任务队列并把异步代码拿到执行栈中执行的操作,就是eventloop
02,url回车
03,重绘和回流
04,http和https的区别
05,三次握手4次挥手
06,跨域
07,hash模式和history模式
总:
hash和history是vue-router的两种模式
通过触发代码内的监听事件从而改变页面内容,不会刷新页面,不会往服务端发请求 Hash模式:
1.url路劲会出现#字符
2.hash路由跳转只有#后的路径发生改变,不会刷新页面
3.Hash值的改变会触发hashchange事件
History模式:
1.整个地址重新加载,可以保存历史记录,方便前进后退
2.使用HTML5(旧浏览器不支持)和HTTP服务端配置,没有后台配置的话,页面刷新会出现404
3.如果是用户在当前用histroy模式操作切换页面的话,URL会被改变、浏览器不会刷新页面也不会往服务端发请求,但会触发代码内的监听事件从而改变页面内容,所以无需用到服务器也可以自由切换页面了。但是这里有个很核心的点就是URL会改变,即有新的URL诞生,所以如果这时用户主动刷新页面(F5),浏览器发送给服务端的是新的URL,所以服务端要做适配,配置一个合理的规则让这些URL返回的都是同一个index.html
自己的话:
两者都是用来实现前端路由跳转的,他们之前的区别有三点,1:兼容性,hash兼容性较好,能够兼容到ie8,history最低能兼容到ie10,2:实现原理不同,hash模式是通过监听onhashchange事件做的处理,在事件的回调里面做一些路由匹配的操作,history模式是利用h5新增的API实现的。3:刷新页面时,对于后端的表现不一样,hash路由是有一个#号的,页面刷新时,#号后面的内容不会发送到服务端,而history没有#号的,页面刷新时,对于后端来说每次都会拿到一个不同的请求地址,所以需要后端进行配置,否则页面就会报404
后端配置方法:匹配到相关请求,同一返回index.html,index.html加载的有路由相关的代码,所以也就转换为由前端路由来处理
08,本地储存
1.相同点
作用一致 : 用于存储数据- localStorage和sessionStorage存储大小在5M左右
- cookie 数据根据不同浏览器限制,大小一般不能超过 4k
2.不同点: 存储方式不同
localStorage : 硬盘存储(永久存储,页面关闭还在,存在硬盘)sessionStorage :内存存储(临时存储,页面关闭了就消失)- cookie 设置过期时间,与浏览器是否关闭无关 缺点:太小,操作不便,配合插件js-cookie使用
- indexedDB 异步,储存数量大,>250m
3.localStorage与sessionStorage如何存储引用类型数据(数组和对象)
- 转json存储
4 实际应用场景:登录时存储token
本地储存的方式有四种,分别是localStorage,sessionStorage,cookie,indexedDB
09,缓存
10,常见web攻击
四,【VUE部分面试题】
01,响应式原理
1.vue主要是通过:数据劫持 + 依赖收集(这里用到了发布者-订阅者模式)的方式来实现的。
2.Vue2.x
是借助Object.defineProperty()实现的,而Vue3.x是借助Proxy`实现的,3 vue2. 原理:3.1通过
Object.defineProperty为对象添加属性,可以设置对象属性的getter和setter函 数。(遍历生成对应的setter,getter函数)3.2 通过点语法获取属性都会执行
getter函数,在这个函数中我们会把调用此属性的依赖通过watcher收集到一个集合中,也就是订阅者 ;3.3 修改属性时,会触发这里定义的
setter函数,在次函数中会去通知集合中的依赖更新,做到数据变更驱动视图变更,也叫做发布者。
vue2缺点:
- 深度监听需要一次性递归 ---> 模板不需要的数据也会进行递归,浪费性能
- 无法监听对象新增与删除的属性 ---> 因为递归已经完成,无法重新获取
- vue2中对数组有特殊处理:直接通过下标修改数组元素的值,视图是不更新的,不是object.defineProperty做不到,而是vue2没有这样做,因为数组下标是会变化的,这样做没有意义
4 vue3.中的响应式与2.x的核心思想一致,只不过数据的劫持使用
Proxy(一次性的添加getter和setter函数)而不是Object.defineProperty,Proxy相比Object.defineProperty在处理数组和新增属性的响应式处理上更加方便。
02,diff算法
比较新旧虚拟dom,给旧的真实dom打补丁,得到新的真实dom
同级比较(4个指针),深度优先(递归)
比较key 或 tag节点,如果相同,比较子节点,直到没有子节点为止
4次比对,当前的结点去旧的列表中找,找到,就复用,找不到,就新建
它的作用是比较不同,比较新旧虚拟dom
特点是同级比较(四个指针),深度优先(递归)
先说深度优先,在有key的情况下先比较key,没有key就比较tag结点,如果相同,就利用递归比较他们的子节点,直到没有子节点为止.
再说同级比较,他有四个指针,分别指向新旧dom的头和尾,会进行四次比对,用当前结点去旧的列表中找相同,找到了,就复用,找不到,就新建
最后得到一个新的dom
03,render函数
04,key
先说一下vue的vue实现流程:
vue将用户编写的模板编译成渲染函数render,渲染函数根据当前data里的数据生成虚拟dom,虚拟dom通过diff算法将新的虚拟dom与旧的虚拟dom进行比对,实现最小化的局部更新真实dom
在这个过程中,key是每个虚拟dom唯一id,key属于diff算法的优化策略,比较新旧dom时,会优先比较key,从而更快更准确的找到虚拟dom的节点
- 高效更新虚拟dom
- diff算法的优化策略
- 比较新旧dom时,优先比较key
05,vnode
06,单个组件生命周期&父子组件
- 创建前:beforeCreate,创建之后created。
- 挂载前:beforeMount,挂载之后mounted。
- 更新前:beforeUpdate, 更新后:updated。
- 销毁前:beforeDestroy, 销毁后:destroyed。
详解:juejin.cn/post/702853…
07,keep-alive
08,nextTick
立即能够获取更新后的dom元素,本质是一种优化策略。
vue在更新Dom时是异步执行的,当数据发生变化时,vue会开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新,而$nextTick就应运而生了,我们可以使用它,来立即获取更新后的DOM元素。
原理:
- 把回调函数放入callbacks(异步操作队列)等待执行
- 将执行函数放到微任务或者宏任务中
- 事件循环到了微任务或者宏任务,执行函数依次执行callbacks中的回调
应用:
- created中获取DOM的操作需要使用它
09,$set
10,插槽
11,组件通讯
1,常用父子传值
props
$emit / v-on
slot插槽
2,不常用父子传值
$parent / $children
ref / $refs
3,语法糖传值
v-model
.sync
4,跨级通信
eventBus
Vuex
provide / inject
$attrs / $listeners
5,访问根组件
$root
12,v-if和v-for的区别
这个问题只存在vue2中,在vue3已经被解决了:
-
v-if不能和v-for一起使用的原因是v-for的优先级比v-if高,一起使用会造成性能浪费
-
解决方案有两种,把v-if放在v-for的外层或者把需要v-for的属性先从计算属性中过滤一次
-
v-if和v-for的优先级问题在vue3中不需要考虑,vue3更新了v-if和v-for的优先级,使v-if的优先级高于v-for
13,v-if和v-show的区别
首先,他们的作用都是控制元素是否可见,使用方法也是一样
但是他们的控制手段不同,v-show是通过添加样式display-none实现隐藏,而v-if是直接通过控制元素的创建和销毁实现隐藏显示实际应用:v-if 登录模式的切换 v-show tab栏切换
14,computed和watch的区别
计算属性computed
- 计算属性是一个函数,返回值(return)就是计算属性得到的结果;
- 一个计算属性对应一个或多个data中的属性;
- 第一次使用计算属性的时候,会把计算结果缓存;
- 后续再次使用这个计算属性的时候,如果该计算属性用到的数据没有变化,就直接读取缓存中的结果,不会重新计算;如果该计算属性用到的数据发生了变化,重新计算结果,存到缓存中; 实际应用:
- 不支持异步,当computed内有异步操作时无效,无法监听数据的变化。
监听属性watch
- 监听器与data中的属性同名,当属性的值发生改变的时候,监听器被触发执行;
- 一个监听器只对应一个data中的属性;
- 不支持缓存,数据发生改变,直接触发响应的操作,不会创建变量保存结果;
- watch支持异步;
- watch中有两个参数deep和immediate:deep深度监听,用来发现对象内部值的变化,因为watch只会监听数据的值是否改变,不会监听地址的变化,如果需要监听引用类型的数据变化,需要深度监听;immediate组件加载立即触发回调函数执行。
15,自定义指令
16,组件封装
组件封装是前端工作人员的日常工作,组件的类型有以下三种:
(1)工具类型的,与具体的项目无关,例如轮播图组件、element-ui......
(2)项目业务相关的组件,与具体项目紧密相关,可能会在项目中使用多次。或者时公司同一系列的项目中使用,比如图片上传组件,凡是涉及到上传或者修改图片的都可以使用
(3)项目复杂页面的子组件,不会使用多次,比如一些需要弹框表单的组件,因为页面信息比较多,通过组件拆分,比较好管理和维护
在实际开发中:
之前做过后管系统,使用了element-ui,所以没有机会封装工具类型的组件,但是因为很多地方都需要上传或修改头像,于是封装了一个图片上传的组件。
在有些员工信息的地方,需要弹框加表格来收集用户信息,于是封装了许多子组件与父组件搭配使用
17,vue.use
18,vuex
vuex是一个全局状态管理库,它解决了非关系组件之间数据传递和共享的问题,一般在使用的时候它里面有常见的六个配置项,分别是state,mutations,actions,getters,modules
它的触发流程是这样的:例如说点击按钮发请求,希望把请求的数据储存到state中,我们可以在actions中发请求,把请求到的数据通过mutations储存到state中,因为state中的数据是响应式的,所以一旦state中的数据发生变化,页面当中,用到state中数据的也会发生变化
如果组件之间关系清晰,数据不需要重复使用,我们可以直接通过父传子,祖辈传后辈等方式进行数据传递
19,pinia
20,v-model和.sync
在vue2中
1其本质是语法糖。
v-model即可以作用于表单元素,又可作用于自定义组件,无论是哪一种情况(vue2, vue3),它都是一个语法糖,最终会生成一个属性和一个事件。原理: 在input框里面通过v-bind动态绑定一个value,然后在input框里面通过@input方法去动态获取input输入的值,然后重新给变量赋值就可以了。
在vue3中:
modelValue属性和onUpdate:modelValue事件。
- 给子组件传递一个属性:modelValue,
- 监听子组件上的事件update:modelValue,在回调函数中,将从子组件回传的值保存
实际应用场景:收集用户输入的数据。以及父子组件传值中都有用到。
21,常见修饰符
22,路由守卫
23,组件中的data为什么要定义成一个函数而不是一个对象
每个组件都是 Vue 的实例。组件共享 data 属性,当 data 的值是同一个引用类型的值时,改变其中一 个会影响其他 ,而如果data是一个函数是,他得到的是一个返回后的新函数,改变其中一个时他不会影响到其他。
24,vue3新特性
性能更高了
体积更小了
对ts的支持更好了
组合式API
四,【TypeScript部分面试题】
五,【业务部分面试题】
01,图片懒加载
是一种常见的性能优化的手段
核心思路是:
图片不在可视区的时候不设置src属性,只有当它在可视区内的时候才设置
如何判断:
1.经过窗口位置的计算
2.用浏览器api:intersectionObserver 判断是否在可视区中
在实际开发中,我们可以通过intersection 实现,或者用vueuse中的api
02,数据懒加载
03,功能组件
04,路由权限
05,按钮权限
2,第一次加载页面会触发哪几个钩子函数
总:当页面第一次加载时会触发beforeCreate,created,beforeMount,mounted这四个钩子函数
分:created可以获取到“data”里面的变量,mounted可以获取到DOM
在实际开发中:
在项目中,当用户登录时都会看到一个基础信息页面,所以这种情况都是在created中调用,之前做过一个图表项目,它必须要获取DOM后才会执行,这时就需要在mounted中使用了
3,js的数据类型
值类型(基本类型) :字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)
引用数据类型:对象(Object)、数组(Array)、函数(Function)