面试题
- 浏览器内核的理解
浏览器主要分为两个部分:渲染引擎和 JS 引擎
渲染引擎:主要负责获取页面内容和排版渲染页面
JS 引擎:解析和执行 JS 来实现页面的动态效果,以及交互内容
- 防抖和节流
防抖:n秒后在执行该事件,若在n秒内被重复触发,则重新计时
节流:n秒内只运行一次,若在n秒内重复触发,只有一次生效
节流和防抖
防抖:就是在连续的操作中,无论进行了多长时间,只有某一次的操作后,在指定的时间内没有再操作,这一次才被判定为有效。(事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。)
防抖的应用场景:搜索框输入关键字的过程中,实时请求服务器匹配搜索结果,如果不进行处理,输入框内容一直发生变化,导致一直发送请求。如果进行防抖处理,结果就是,当我们输入内容完成后,一定时间内(比如500ms),没有再输入内容,这时再发送请求。
节流:的意思是,在频繁的操作中,在规定时间内,只触发一次。比如我们设定500ms,在这个时间内,无论点击按钮多少次,它都只会触发一次。
- 描述sessionStorage与localStorage、cookie的区别
sessionStorage与localStorage的数据都是在本地存储,不会把数据发给服务器。localStorage是关闭浏览器,数据还存在不会丢失。而sessionStorage是离开浏览器后,数据会自动删除。
共同点:都是存储在浏览器本地(客户端)
localStorage.getItem("key")
localStorage.setItem('key',value)
localStorage.remove('key')
区别:
sessionstorage和localStorage(5M)的存储比cookie(4k)大的多。session可以存任意数据类型,cookie只存ASCII。
cookie在设置的过期时间之前一直有效;localStorage永久存储,浏览器关闭后数据不丢失; 而sessionStorage是关闭浏览器窗口后,数据会自动删除。
安全性:cookie在浏览器和服务器之间传递、而localStorage和sessionStorage仅在本地存储。
作用域:localStorage、cookie在所有同源窗口都是共享的,sessionStorage不在不同浏览器窗口共享,即使是同一个页面。
应用:要大量存储不需要变动的数据可以用localStorage、只需要在当前会话期间存储数据可以使用seessionStorage、如果在请求之间存储数据则可以使用cookie
- MVVM
M==>mode(数据模型),V==>view(代表UI组件,负责将数据转换为UI展现出来),
View和Model是通过ViewModel进行交互的,Model和ViewModel之间的交互是相互的,view数据的变化会同步到Model上,而Model的变化也会同步到View上。
- MVC
mvc:model(模型层) view(视图层) controller(控制层) controller负责编写接口并且调用逻辑方法,调用方法后返回数据给视图层(view),view视图层获取到数据后可以进行对数据做动态渲染。
- js数据类型
number string boolean null undefined symbol:ES6新增的数据类型,表示独一无二的值。
复杂类型有Object ,NAN
- 组件通信
1)父组件向子组件传递数据--使用props
功能:让组件接收外部传过来的数据 ;
传递数据: 直接在组件标签内写属性
接收数据:
defineProps(['user','index','currentIndex'])
第一种方式:(只接收)
props: ['name']
第二种接收方式:(限定类型)
props:{
name:String
}
第三种接收方式:(限定类型+限制必要性+指定默认值)
props:{
name: {
type:String, //类型
required:true, //必要性
default:'老王' // 默认值
}
}
备注:props是只读的,Vue底层会监视你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改那么复制props的内容到data中一份,然后去修改data中的数据
父组件:
<template>
<div>
<Student name= '张三' sex='男' :age='18'></Student>
</div>
</template>
<script>
import Student from './components/Student.vue'
export default {
name: 'app',
components:{Student}
}
</script>
子组件:
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<h2>学生年龄:{{myAge+1}}</h2>
<button @click= 'updateAge'>尝试修改收到的年龄</button>
</div>
</template>
<script>
export default {
name: 'Student',
data() {
return {
msg:'我是一个尚硅谷的学生',
myAge:this.age
}
},
methods: {
updateAge() {
this.myAge++
}
},
// 简单声明接收
props: ['name', 'age', 'sex']
// 接收的同时对数据类型进行限制
// props: {
// name:String,
// age:Number,
// sex:String
// }
//接收的同时:对数据类型进行限制+默认值的指定+必要值的限定
// props:{
// name: {
// type:String, // name的类型是字符串
// required:true // name是必要的
// },
// age: {
// type:Number,
// default:99 // 默认值
// },
// sex: {
// type:String,
// required:true
// }
// }
}
</script>
2)子组件向父组件传递数据--可以通过自定义事件
父组件:
<template>
<div class="app">
<h1>{{msg}},学生姓名是:{{studentName}}</h1>
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据-->
<Student @atguigu="getStudentName"/>
</div>
</template>
<script>
import Student from './components/Student'
export default {
name:'App',
components:{Student},
data() {
return {
msg:'你好啊!',
studentName:' '
}
},
methods: {
getSchoolName(name){
console.log('App收到了学校名:',name)
this.studentName = name
}
},
}
</script>
子组件:
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="sendSchoolName">把学校名给App</button>
</div>
</template>
<script>
export default {
name:'School',
data() {
return {
name:'小剑',
address:'北京',
}
},
methods: {
sendSchoolName(){
this.$emit('atguigu', this.name)
}
},
}
</script>
3)ref 链:
父组件要给子组件传值,在子组件上定义一个 ref 属性,这样通过父组件的 $refs 属性
就可以获取子组件的值了,也可以进行父子,兄弟之间的传值($parent / $children与 ref类似)
4)兄弟组件通信--使用全局事件总线($bus)
安装全局事件总线:
new Vue({
.....
beforeCreate() {
Vue.prototype.$bus = this//安装全局事件总线,$bus就是当前应用的vm
},
....
})
示例代码如下:
// 在main.js中安装全局事件总线
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
//创建vm
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
},
})
接收数据的组件:
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
methods:{
demo(data) {.....}
},
mounted() {
this.$bus.$on('xxx', this.demo)// 或者是写箭头函数的回调
}
示例代码如下:
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<h2>收到的学生的名字是:{{studentName}}</h2>
</div>
</template>
<script>
export default {
name:'School',
data() {
return {
name:'小剑',
address:'北京',
studentName:''
}
},
methods:{
// getName(name) {
// this.studentName = name
// },
},
mounted() {
// 写法一,把回调函数写在methods函数当中
// this.$bus.$on('hello', this,getName)
// 写法二,直接写回调函数
this.$bus.$on('hello', (name)=> {
this.studentName = name
})
},
beforeDestroy() {
this.$bus.$off('hello')
},
}
</script>
提供数据 :this.$bus.$emit('xxx', 数据)
示例代码如下:
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
}
},
methods: {
sendStudentName(){
this.$bus.$emit('hello',this.name)
}
},
}
</script>
5)任意组件通信--使用消息订阅与发布
使用步骤
1).安装pubsub:npm i pubsub-js
2).引入:import pubsub from 'pubsub-js'
3).接收数据:A组件想要接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
4).提供数据:pubsub.publish('xxx',数据)
接收数据的组件示例代码如下:
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<h2>收到学生的名字:{{studentName}}</h2>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name:'School',
data() {
return {
name:'小剑',
address:'北京',
studentName:''
}
},
mounted() {
this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
console.log(this, msgName, data)
this.studentName = data
})
},
beforeDestroy() {
pubsub.unsubscribe(this.pubId)
},
}
</script>
提供数据的代码如下:
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
}
},
methods: {
sendStudentName(){
pubsub.publish('hello',this.name)
}
},
}
</script>
6)provide inject组件通信
7)vuex
- 盒子水平垂直居中方法(高频)
- 通过定位,给父盒子相对定位,子盒子绝对定位,top、left为50%,再margin-left : -(子盒子宽的一半)px; margin-top: -(子盒子高的一半)px;
- 不依赖通过计算子盒子的宽高进行定位,可以用transform: translate 移动自身的一半就行了。
- 通过flex布局,设置垂直水平都居中。
没有给定宽度和高度的情况下:
1、利用绝对定位+transform 子绝父相
先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过translate来调整元素的中心点到页面的中心
//父元素
position:relative ; //父元素相对定位
//需要居中的元素设置
position:absolute; //子元素绝对定位
top:50%; //走父亲宽高的一半
right:50%;
transform:translate(-50%,-50%);
2、给父使用flex布局,通过给父元素设置:align-items:center(垂直布局)和justify-content:center(水平布局)设置容器的垂直和水平方向上为居中对齐,然后它的子元素也可以实现垂直和水平的居中
//给父元素设置
display:flex;
justify-content:center;
align-items:center;
3、通过父元素display:table-cell 。子盒子必须设置为display:inline-block
//父元素
display:table-cell;
vertical-align:middle;
text-align:center;
//需要居中的子元素
display:inline-block;
给定宽度和高度的情况下:
1、 绝对定位+margin负,子绝父相
先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过margin负值来调整元素的中心点到页面的中心。该方法适用于盒子宽高已知的情况
//父元素
position:relative ; //父元素相对定位
//需要居中的元素设置
position:absolute; //子元素绝对定位
width:200px;
height:200px;
top:50%;
left:50%;
margin-top:-100px; //负自己高一半
margin-left:-100px; //负自己宽一半
2、绝对定位+margin:auto法 子绝父相 需要
设置四个方向的值都为0,并将margin设置为auto,由于宽高固定,因此对应方向实现平分,可以实现水平和垂直方向上的居中。该方法适用于子盒子有宽高的情况
//父元素
position:relative ; //父元素相对定位
//需要居中的元素设置
position:absolute; //子元素绝对定位
margin:auto;
left:0;
right:0;
top:0;
bottom:0
- vue2生命周期
总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。
创建前/后: 在beforeCreate阶段,vue实例的挂载元素el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,el为undefined,还未初始化。
载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
更新前/后:当data变化时,会触发beforeUpdate和updated方法
销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在
- vue2和vue3生命周期的对比
基本上就是在 Vue2 生命周期钩子函数名基础上加了 on
beforeDestory 和 destoryed 更名为 onBeforeUnmount 和 onUnmounted;
然后删了两个钩子函数 beforeCreate 和 created变成了setup函数
- vue2双向绑定
在模板中使用v-model指令将input元素value值与vue实例中的message属性进行绑定,双向绑定通过数据劫持和发布订阅者模式实现,当数据发生变化的时候会触发setter getter方法通知订阅该数据的watcher对象进行更新,更新完成后通知视图进行重新渲染,实现双向绑定的效果
Vue中的双向数据绑定是如何实现的?
答案:Vue中的双向数据绑定是通过v-model指令实现的。v-model可以在表单元素上创建双向数据绑定。当用户输入改变表单元素的值时,数据模型会自动更新;反之,当数据模型的值改变时,表单元素也会自动更新。
- 对单向数据流的理解
单项数据流是指在应用程序中流动方向只有一个方向,比如父级组件到子级组件传递数据,但是子组件不能直接修改父组件的数据,只能像父组件发送事件来请求修改数据,避免了数据的混乱和数据变化冲突
- vue2和vue3的区别
性能比vue2加快了很多,使用了proxy来代替Object.defineProperty,提高了响应式系统的性能和稳定性,vue2使用的是选项API,vue3使用的是组合式API,vue3支持tree-shaking,可以更好的优化打包体积,更好的支持TS
- vue3.2和3.0的区别
vue3.2 中 只需要在 script 标签上加上 setup 属性,组件在编译的过程中代码运行的上下是 setup() 函数中不在需要一直return页面中使用数据或者方法
px与em的区别
px 与em都是长度单位,
区别是:px值是固定的,指定多少是多少。不能适应浏览器缩放时产生的变化,
em的值不固定,会继承父元素的字体大小,是一个相对单位
rem是css3中新增的相对单位,相对于html根元素字体大小计算,可以通过修改根元素字体大小调整所有字体大小
- vuex是什么?怎么使用?使用场景有哪些?
vuex 是一个专门为 vue 构建的状态管理工具,主要是为了解决 多组间之间状态共享问题。强调的是集中式管理,(组件与组件之间的关系变成了组件与仓库之间的关系)。
vuex 的核心包括:state(存放状态)、mutations(同步的更改状态)、actions(发送异步请求,拿到数据)、getters(根据之前的状态派发新的状态)、modules(模块划分)。
state 发布一条新的数据,在 getters 里面根据状态派发新的状态,actions 发送异步请求获取数据,然后在 mutations 里面同步的更改数据。
应用场合:购物车的数据共享、登入注册
<router-link>
- $router和$route的区别是什么?
$route (路由:是一个规则)对象包含当前路由信息,可以访问当前路由的路径、参数、查询参数等信息,是只读的,不能修改。
$router(路由器:VueRouter的实例),有push,replace,back,forword方法,用于动态地跳转到不同的路由。
$router(路由器:VueRouter的实例)
- 虚拟dom中key的作用?
key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】的差异比较,比较规则如下:
旧虚拟DOM中找到了与新虚拟DOM相同的Key:若虚拟DOM中的内容没有发生变化,直接使用之前的真实DOM,若虚拟DOM中内容发生了变化,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
旧虚拟DOM中未找到与新虚拟DOM相同的key:则创建新的虚拟DOM,随后渲染到页面
- 原型
每个函数都有 prototype 属性,该属性指向原型对象;使用原型对象的好处是所有对象实例共享它所包含的属性和方法。
- 原型链
每一个实例对象上有一个proto属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有proto属性,这样一层一层往上找的过程就形成了原型链。
- 搭建项目
首先先安装node和npm,在安装vue脚手架,然后创建vue项目(npm create vite),使用npm run dev 运行项目,在src目录中进行编码,路由配置,状态管理等
- v-if,v-show,v-for
共同点:都是用来控制页面元素的显示和隐藏
不同点:v-if显示隐藏是将DOM元素添加和删除,v-show显示隐藏是为该元素的css设置displa y:none,DOM元素依旧存在
v-for主要用来循环渲染列表的数据,生成对应的列表元素
- ajax工作原理
Ajax是一种异步获取数据的技术,他的原理是通过XmlHttpRequest对象来向服务器发送异步请求,类似一个中间层,负责请求数据,而不影响浏览器其他事件执行,等到数据回来之后在通知浏览器,浏览器在进行处理
- es6的新特性用到哪些?
- 声明变量的的关键字:let;声明常量的const。
- 解构赋值
- 模板字符串
- 箭头函数
- 扩展运算符
- promise 用来封装异步操作并且可以获取成功或失败的结果。
- BOM浏览器对象
BOM是浏览器提供的一个JS对象,用于与浏览器窗口进行交互
window对象:表示浏览器中打开的窗口或标签页,包含了很多常用的方法和属性,例如alert()、confirm()、prompt()等。
location对象:表示当前文档的URL,可以用来获取或设置当前页面的URL。
navigator对象:表示浏览器的信息,可以用来获取浏览器的名称、版本号、是否支持某个特性等。
screen对象:表示用户屏幕的信息,可以用来获取屏幕的宽度、高度、像素密度等。
history对象:表示浏览器的访问历史记录,可以用来在历史记录中前进forword()或back()后退。
document对象:表示当前文档,是DOM(文档对象模型)的顶层对象,可以用来操作文档中的元素和内容。
事件对象:当事件的响应函数被触发时,浏览器每次都会将一个事件对象作为参数传递进响应函数。事件对象中封装了当前事件的相关信息,比如鼠标坐标、键盘的哪个键被按下,鼠标滚轮滚动的方向如何。
什么是冒泡,如何解决冒泡?什么
是事件代理(事件委托)?
指父元素和子元素有相同的事件,当触发子元素事件时,会向上冒泡,同时也会触发父元素事件。
阻止冒泡:通过 event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行。
不需要给每一子节点都绑定事件,而是将子节点的事件绑定到他们共同的结构父级接节点上,这样在点击每一个子节点的时候,会利用事件传播冒泡执行父节点上绑定的事件。
事件代理?应用场景?
事件代理又成为事件委托,就是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的任务。事件代理的原理是DOM元素的事件冒泡,使用事件代理的好处是:可以提高性能
应用场景:可以大量节省内存占用,减少事件注册,比如在table表格上上代理所有某一行td的click的点击事件就非常方便
可以实现当新增子对象时无需再次对其绑定
- vue中插槽在什么情况下用?
父组件需要向子组件传递内容,但不确定传递的内容是什么,这时候可以使用默认插槽。子组件向父组件传递多个内容,且这些内容的位置不确定,可以使用具名插槽,父组件需要根据条件来动态传递内容,可以使用作用域插槽
插槽:
1.作用:让父组件可以向子组件指定位置插入html结构,也是组件通信的一种方式,适用于父组件===》子组件。
2.分类:默认插槽、具名插槽、作用域插槽
一、默认插槽
父组件中:
<Category>
<div>html结构</div>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot>插槽默认内容...</slot>
</div>
</template>
二、具名插槽
父组件中:
<Category>
<template slot="center">
<div>html结构</div>
</template>
<template v-slot:footer>
<div>html结构</div>
</template>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">插槽默认内容...</slot>
<slot name="footer">插槽默认内容...</slot>
</div>
</template>
三、作用域插槽
1、理解:数据在组件的自身,但是根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
2、具体编码:
父组件中:
<Category>
<template scope="scopeData">
<ul>
<li v-for="item in scopeData.games" :key="item">{{ item }}</li>
</ul>
</template>
</Category>
<Category>
<template slot-scope="scopeData">
<h4 v-for="Item in scopeData.games" :key="Item">{{ Item }}</h4>
</template>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot :games="games">插槽默认内容...</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
// 数据在子组件自身
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
}
}
</script>
默认插槽
具名插槽
作用域插槽(子传父)
- computed和watch的区别
computed是计算属性,他是根据依赖的数据动态计算出一个新的值,只有在依赖数据发生变化的时候才会重新计算。
watch是监听指定的数据变化,在数据变化的时候执行指定的回调函数,它可以监听一个复杂的数据结构或者是需要执行异步操作的情况
怎么持久化存储
可以使用浏览器的localStorage或者sessionStore将状态存储到本地,页面刷新或者关闭的时候再从本地读取状态恢复,但是只能存储字符串类型的,需要将状态序列化为字符串后在存储。
vue中的data为什么是一个函数?起到什么作用?
在Vue组件中,data选项必须是一个函数,而不能直接是一个对象。这是因为Vue组件可以同时存在多个实例,如果直接使用对象形式的data选项,那么所有的实例将会共享同一个data对象,这样会造成数据互相干扰。
因此,将data选项设置为函数可以让每个实例都拥有自己独立的data对象。当组件被创建多次时,每个实例都会调用该函数并返回一个新的data对象,从而保证了数据的隔离性。
箭头函数和普通函数的区别
- this指向不同 :箭头函数指向定义函数时候的作用域,没有自己的this,普通函数的this指向调用该函数的对象
- arguments对象不同:普通函数中有arguments对象,可以获取到函数的参数,箭头函数没有arguments对象,需要使用rest参数获取传入的参数
- 构造函数不同:普通函数可以作为构造函数使用并且能通过new创建实例对象,箭头函数不能创建实例对象
- 语法不同:箭头函数比普通函数更简洁,可以省略大括号、function和return
async和awiat的使用
async await 是ES7的新增,await用于等待一个异步方法执行完成。async用于声明一个函数,async函数返回的是一个promise对象,可以用.then方法添加回调函数,在函数执行的中,一旦遇到await就会先返回,等到这个异步操作完成之后,它再执行函数体内后面的这个语句 。
什么是闭包?优缺点是什么?
- 闭包是在一个函数内部在定一个函数,然后内部函数访问外部函数的一个变量就会形成闭包,闭包的话会形成一个私有空间,然后避免全局变量的一个污染,然后会持久化存储数据到内存中,但是闭包也有弊端,它会导致内存泄漏
- 优点: 1. 一个是可以读取外层函数内部的变量。2. 让这些变量始终保存在内存中,即闭包可以使它的诞生环境一直存在(延伸了变量的作用范围。)
- 缺点: 容易造成内存泄漏。
- 满足以下条件才是闭包:1有函数嵌套2内部函数引用外部作用域的变量3返回值是一个函数4创建一个对象函数,让其长期驻留
- 为什么需要使用闭包?因为全局变量容易污染环境,而局部变量又无法长期驻留内存,于是我们需要一种机制,既能长期保存变量又不会污染全局,这就是闭包。
- 什么时候使用闭包?:当我们需要重复使用一个对象,又想保护这个对象不被其他代码污染的时候,就可以使用闭包。
- 如何销毁闭包的内存空间?将外部接收函数的变量重新赋值为null即可
function outer() {
let x = 10;
function inner() {
console.log(x);
}
return inner;
}
let closure = outer();
closure(); // 输出 10
重绘、重排(回流)
重绘:修改了页面元素的字体颜色、背景颜色、边框颜色,但是并不影响页面布局,因为元素的尺寸大小并没有改变,其他元素也并没有受到位置上的影响,还有设置opcity透明度,visibity:hidden;属性也会导致重绘。总结:当页面元素样式改变而不影响布局时,浏览器重新对元素进行更新的过程叫做重绘。
重排:是指修改了元素的尺寸大小,例如改变了元素的宽高、边框的粗细,或者是给某元素设置display:none;属性,这样也会导致回流。总结:当页面元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程叫做重排也叫做回流。所以回流一定会导致重绘。
哪些情况会发生重排?
页面初始渲染
添加/删除可见DOM元素
改变元素位置
改变元素尺寸(宽、高、内外边距、边框等)
改变元素内容(文本或图片等)
改变窗口尺寸
如何减少重排和重绘(提高性能)?
由于回流和重绘会带来很大的性能开销,所以在开发中我们要尽量避免或减少回流和重绘的次数来提高性能
避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
对具有复杂动画的元素使用绝对定位,使其脱离文档流,否则会引起父元素及后续元素频繁回流。
要避免频繁的去操作DOM,可以通过创建documentFragment,完成所有所有DOM操作后,最后再把它添加到文档中。
避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
什么时候使用$nextTick,实现原理是什么
当我们需要在DOM更新之后执行一些操作的时候可以使用$nextTick方法,他的原理是利用JS的事件循环机制,在DOM更新之后执行回调函数,当vue需要更新DOM的时候,会先将DOM更新任务放入一个队列,然后通过JS事件循环机制,等待所有同步任务完成之后在执行所有异步任务,异步任务执行完成后会执行 $nextTick方法中的回调函数,避免出现DOM没有更新的情况。
深拷贝和浅拷贝的区别
- 深拷贝会复制对象或数组的所有内容,包括在对象或数组内部的所有属性和子属性,深拷贝后的对象或数组不会影响原对象或数组;
- 浅拷贝只复制对象或数组的引用,不会复制对象或数组的内容,如果修改浅拷贝后的对象或数组的时候,原对象或数组也会受到影响
-
get和post的区别
- 参数传递方式不同:get请求通过URL传递参数,POST请求通过请求体传递参数
- 数据量大小限制不同:get请求发送的数据通常不能超过1024字节,post请求没有数据量大小的限制
- 缓存机制不同:get请求可以被浏览器缓存,post请 求不会被浏览器缓存,每次请求都是最新的数据
- 使用场景不同:get适用于获取数据,比如搜索、获取文章列表,post适用于提交数据,比如登录、注册、发表评论
请求拦截器和响应拦截器
请求拦截器:发送请求前会先经过请求拦截器,在每次发送请求的时候判断是否存在token,如果存在token,则统一在http请求的header上都加上token,后台会根据你携带的token判断登录情况一般来说登录完成之后,token会存在localstore或者是存在cookie本地,然后用户在进入页面的时候,首先会从本地存储中读取token,如果token存在说明用户已经登录过了,则更新token的状态,如果没有token,就说明没有登录过然后在响应拦截器的在第二个函数响应失败的处理 、统一处理消息提示、如果token 过期则清空token跳转到登录页面
浏览器的同源策略(跨域问题)有了解过吗?
- 如果一个请求url的 协议、域名、端口三者之间任意一个与当前页面url不同就会产生跨域的现象。
- 同源策略,是一种网页的安全协议。同源:同协议,同域名,同端口。
http;// www. aaa.com:8080/index/vue.js ,协议子域名 主域名 端口号资源。
- 同源策略是浏览器的核心,如果没有这个策略就会遭受网络攻击。
- 主要指的就是协议+域名+端口号三者一致,若其中一个不一样则不是同源,会产生跨域。
- 三个允许跨域加载资源的标签: img、 link、 script 跨域是可以发送请求,后端也会正常返回结果,只不过这个结果被浏览器拦截了!
什么是递归,递归有哪些优缺点?
递归:如果函数在内部可以调用其本身,那么整函数就是递归函数,简单理解:函数内部自己调用自己,这个函数就是递归函数,
优点:机构清晰,可读性强
缺点:效率低,调用站可能溢出,其实每一次函数调用会在内存栈中分配空间,而每个进程的栈的内容粮食有限的。当调用的层次太多时,就会超出栈的容量,从而导致栈溢出
= =和 = = =的区
他们都是js的比较运算符,==比较两个值是否相等,会进行类型转换相等返回true不相等返回false;= = =比较两个值是否严格相等不会进行类型转换,相等返回true,不相等返回false
说说你对flex布局的了解,使用场景?常用属性?
Flex布局是一种灵活的CSS布局模型,用于在容器内部对子元素进行排列和定位。它提供了更简洁、直观的方式来创建响应式的网页布局。
使用场景:响应式布局;等高布局;垂直居中
常用属性:display:flex
flex-direction:指定主轴方向
justify-content:用于定义主轴上子元素的对齐方式;
align-items:用于定义交叉轴上子元素的对齐方式;
flex-wrap:指定子元素在容器内是否换行;
align-self:用于单独设置某个子元素在交叉轴上的对齐方式
同步和异步的区别
同步操作是指当一个操作开始执行后,必须等待它执行完成后才能执行下一个操作。也就是说,同步操作是按照顺序依次执行的,每个操作都要等待上一个操作完成后才能执行。同步操作的优点是操作的执行顺序可控,但是如果某个操作执行时间过长,会导致整个程序停顿,用户体验差。
异步操作是指当一个操作开始执行后,不需要等待它执行完成,可以继续执行后续的操作。也就是说,异步操作不是按照顺序依次执行的,某些操作可以在其它操作完成之前就开始执行。异步操作的优点是不会阻塞程序的执行,提高了程序的执行效率,但是操作的执行顺序是不可控的
页面渲染的过程?(浏览器是怎么渲染页面的?)
就是将HTML代码解析为一颗DOM Tree。按照从上到下,从左到右的顺序,将树上的节点压入文档流然后布局。
解析CSS文件,生成CSSOM(css Object Model)。顺序为:浏览器默认样式->自定义样式->页面内的样式。
将DOM和CSSOM结合起来就形成了Render树。这个渲染树和DOM树的不同之处在于,它是受样式影响的。它不包括那些不可见的节点。
当渲染树生成之后,浏览器就会根据渲染树在屏幕上渲染和展示。
谈一谈你对虚拟DOM和diff算法的理解?
虚拟DOM,也就是我们常说的虚拟节点,用于比对虚拟DOM和真实DOM的差异,从而进行局部渲染达到优化性能的目的,起初在使用JS/jQuery的时候,我们不可避免的会大量的操作DOM元素,导致一直引发回流或者重绘,降低了渲染页面的性能,虚拟DOM就是由此而生,虚拟DOM的出现大大的降低了因为频繁操作DOM引起的性能问题 。
diff算法可以算作是一种对比手法,对比的对象是新旧虚拟DOM,diff算法可以找到新旧虚拟DOM 之间的差异,但diff算法并不只是对比虚拟DOM,还会根据对比后结果更新真实DOM,由于diff算法对比的是虚拟Dom,而虚拟Dom是呈树状的,所以我们可以发现,diff算法中充满了递归。
vue中混用mixin的原理
mixin是一种代码复用的机制,可以在组件中共享相同的代码逻辑,他的原理是利用了vue中的选项合并机制。当一个组件使用了一个混入对象时,vue会将混入对象中的选项合并到组件的选项中,按照顺序依次执行
在Vue中使用插件的步骤
在使用插件前,先安装插件,可以使用npm/yarn来安装,也可以手动安装,
在安装好插件之后,
就需要在main.js文件中引入该插件,并挂载到vue实例上,在所有组件中都可以使用,因为他是一个全局引入;
也可以使用局部引入,在我们一个具体所需要的文件夹下引入这个插件;
还可以使用对象的形式:以对象形式创建插件,该对象至少包含了一个方法,可以用来安装我们所需要的插件
类形式的方式引入:定义一个构造函数来创建插件,并在原型上扩展插件的功能,也需要暴露一个静态文件
如何理解响应式,实现响应式的方法有哪些?有什么区别?
响应式布局是同一页面在不同的屏幕上有不同的布局,即只需要一套代码使页面适应不同的屏幕
响应式的4种方法:
媒体查询:使用@media可以根据不同的屏幕定义不同的样式
百分比:百分比是相对于包含块的计量单位,通过对属性设置百分比来适应不同的屏幕
vw/vh:vw/vh是视口单位,即根据浏览器的窗口大小进行适配
rem: rem是指相对于根元素的字体大小的单位,rem只是一个相对单位
vue 中的 keep-alive
keep-alive 是 vue 中的内置组件,能够在组件切换过程中将状态保留在内存中,防止重复的渲染 DOM;
keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们;
设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated 和 deactivated )
数组常用方法
- push():向数组末尾添加一个或多个元素,并返回数组的新长度。
- pop():从数组末尾删除一个元素,并返回该元素的值。
- unshift():向数组开头添加一个或多个元素,并返回数组的新长度。
- shift():从数组开头删除一个元素,并返回该元素的值。
- slice():返回指定位置的元素,不会改变原数组。
- splice():从指定位置删除或添加元素,可以改变原数组。
- concat():合并两个或多个数组,不会改变原数组。
- join():将数组中的所有元素转化为一个字符串,并返回该字符串。
- indexOf():查找指定元素在数组中的位置,返回其下标,如果不存在则返回-1。
- forEach():对数组的每个元素执行一次提供的函数。和for相似
- map():创建一个新数组,其结果是该数组中的每个元素调用提供的函数后返回的结果。
- filter():创建一个新数组,其中包含所有通过所提供函数的测试的元素。
- reduce():通过指定的函数将数组的每个元素累加为单个值。
- reverse():颠倒数组中元素的顺序。
- sort():对数组中的元素进行排序。
-
对象的常用方法
- Object.keys():返回一个由对象的属性名组成的数组。
- Object.values():返回一个由对象的属性值组成的数组。
- Object.entries():返回一个由对象的每个属性键值对数组组成的数组。
- Object.assign():将一个或多个源对象的属性复制到目标对象中,并返回目标对象。
- Object.freeze():冻结对象,防止对其进行修改。
- Object.seal():封闭对象,防止添加或删除属性,但允许修改属性值。
- Object.create():创建一个新对象,并将其原型设置为指定的对象。
- Object.hasOwnProperty():判断一个对象是否有指定的属性,不会检查原型链。
- Object.is():比较两个值是否相等,与===相比具有更精确的行为。
- Object.toString():返回对象的字符串表示形式。
- Object.defineProperty():定义对象的新属性,或修改现有属性的特性。
- Object.defineProperties():定义多个新属性,或修改现有属性的特性。
- Object.getOwnPropertyDescriptor():返回指定对象属性的属性描述符。
- Object.getPrototypeOf():返回指定对象的原型对象。
- Object.setPrototypeOf():设置一个对象的原型到另一个对象或null。
事件冒泡(Event Bubbling)和事件捕获(Event Capturing)
答案:事件冒泡是指事件从最具体的元素开始向父元素逐级触发,直到触发到根元素。事件捕获是指事件从根元素开始,逐级向最具体的元素触发。可以使用addEventListener方法的第三个参数来控制是使用事件冒泡还是事件捕获。
异步编程
答案:异步编程是一种处理可能耗时的操作而不阻塞主线程的编程方式。常见的处理异步操作的方法有回调函数、Promise、async/await和事件监听等
this关键字的作用和使用场景。
答案:this关键字在JS中表示当前执行上下文的对象。它的具体取值根据函数的调用方式而定。在全局作用域中,this指向全局对象(浏览器环境中为window对象)。在函数中,this的指向取决于函数的调用方式,可以通过call、apply、bind等方法来显式地指定this的值。
事件委托(Event Delegation)
答案:事件委托是指将事件处理程序绑定到父元素上,而不是直接绑定到每个子元素上。当事件触发时,事件会冒泡到父元素,然后通过判断事件的目标来执行相应的处理逻辑。这样可以减少事件处理程序的数量,提高性能。
JS中的严格模式(Strict Mode)
答案:严格模式是一种JavaScript的执行模式,它提供了更严格的语法和错误检查。在严格模式下,一些不安全或不推荐的语法会被禁用,同时会引入一些新的特性,如变量必须先声明才能使用、禁止使用this指向全局对象等
柯里化(Currying)
答案:柯里化是一种将接受多个参数的函数转换为接受一个参数并返回一个新函数的过程。示例:
function add(a) {
return function(b) {
return a + b;
}
}
var add5 = add(5);
console.log(add5(3)); // 输出:8
TS和JS之间的关系
答案:TypeScript是JavaScript的超集,它添加了静态类型和其他一些特性。TypeScript代码可以编译成JavaScript代码,因此可以在任何支持JavaScript的环境中运行。
TypeScript中的枚举是什么?如何定义和使用枚举?
答案:枚举是一种用于定义命名常量集合的语法。
Vue是什么?它有哪些特点?
答案:Vue.js是一个用于构建用户界面的JavaScript框架。它具有以下特点:
响应式数据绑定:通过使用Vue的数据绑定语法,可以实现数据的自动更新。 组件化开发:Vue允许将页面划分为独立的组件,提高了代码的可维护性和复用性。 虚拟DOM:Vue使用虚拟DOM来跟踪页面上的变化,并高效地更新实际的DOM。 指令系统:Vue提供了丰富的内置指令,用于处理常见的DOM操作和逻辑控制。 生态系统:Vue拥有庞大的生态系统,包括插件、工具和第三方库,可以满足各种开发需求。
Vue中的路由是如何实现的?
答案:Vue中的路由是通过Vue Router实现的。Vue Router是Vue.js官方提供的路由管理器,它允许开发者在Vue应用中实现单页面应用(SPA)。Vue Router通过配置路由映射关系,将URL路径与组件进行关联,并提供导航功能,使用户可以在不刷新页面的情况下切换视图。
Vue中的keep-alive是什么?它有什么作用?
答案:是Vue中的一个内置组件,用于缓存动态组件。当组件包裹在中时,组件的状态将被保留,包括它的实例、状态和DOM结构。这样可以避免在组件切换时重复创建和销毁组件,提高性能和用户体验。请解释Vue.js中的依赖注入(Dependency Injection)是什么?它在Vue中的应用场景是什么?
答案:依赖注入是一种设计模式,用于将依赖关系从一个组件传递到另一个组件。在Vue中,依赖注入通过provide和inject选项实现。父组件通过provide提供数据,然后子组件通过inject注入这些数据。它在跨多个层级的组件通信中非常有用。
Vue中的路由导航守卫有哪些?它们的执行顺序是怎样的?
答案:Vue.js中的路由导航守卫包括全局前置守卫、全局解析守卫、全局后置守卫、路由独享守卫和组件内守卫。它们的执行顺序如下:
全局前置守卫(beforeEach) 路由独享守卫(beforeEnter) 解析守卫(beforeResolve) 组件内守卫(beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave) 全局后置守卫(afterEach)
Vue3中的Teleport是什么?请给出一个Teleport的示例
Teleport是Vue.js 3中引入的一种机制,用于将组件的内容渲染到DOM树中的任意位置。示例:
<template>
<div>
<button @click="showModal = true">打开模态框</button>
<teleport to="body">
<modal v-if="showModal" @close="showModal = false">模态框内容</modal>
</teleport>
</div>
</template>
Vue.js 3中的Suspense是什么?它的作用是什么?
答案:Suspense是Vue.js 3中引入的一种机制,用于处理异步组件的加载状态。它可以在异步组件加载完成之前显示一个占位符,并在加载完成后渲染异步组件的内容。这样可以更好地处理异步组件的加载过程,提供更好的用户体验。
异步引入页面抖动不太好用<suspense>
Vue3中的Fragment是什么?它的作用是什么?
答案:Fragment是Vue.js 3中引入的一种机制,用于在组件中返回多个根节点。
在Vue.js 2中,组件的模板只能有一个 Vue.js 3中的Composition API中的ref和reactive有什么区别?什么时候使用哪个? 答案:ref用于创建一个响应式的基本数据类型,而reactive用于创建一个响应式的对象。当需要创建一个简单的响应式数据时,可以使用ref,当需要创建一个包含多个属性的响应式对象时,可以使用reactive。
map和filter的区别与联系
1. 功能不同:
• map 方法用于对数组中的 每个元素执行相同的操作,并返回一个新的数组,新数组的元素是原数组元素经过操作后的结果。
• filter 方法用于根据 某个条件过滤数组中的元素,并返回一个新的数组,新数组包含符合条件的元素。
2返回值不同:
• map 方法返回一个与原数组长度相同的新数组,其中每个元素都是通过操作得到的结果。
• filter 方法返回一个新的数组,其中仅包含符合条件的元素。
3. 使用方式相似:
两者都是数组的方法,通过访问数组对象调用,例如:arr.map() 和 arr.filter()。
• 两者都接受一个回调函数作为参数,该回调函数用于定义操作或条件。