前端面试题总结
css、html部分
1、h5新特征有哪些?
-
语义化标签,如nav,footer,header,section等
音频、视频API
使用方法
视频使用:
<video src="./video/mv.mp4"></video>
音频使用:
<audio controls> <source src="horse.ogg" type="audio/ogg"> <source src="horse.mp3" type="audio/mpeg"> </audio>
- control 属性供添加播放、暂停和音量控件
- autoplay控制多媒体是否自动播放
- volume获取设置媒体音量
- paused媒体对象是否暂停
- playbackRate属性获取设置媒体播放速度
canvas绘图
moveTo(x,y)定义线条开始的坐标
lineTo(x,y)定义线条结束的坐标
var ctx=c.getContext("2d"); ctx.moveTo(0,0); ctx.lineTo(200,100); ctx.stroke();
定义开始和结束的坐标,最后通过stroke()来绘制线条
地理位置
表单控件
placehoder 属性,简短的提示在用户输入值前会显示在输入域上。即我们常见的输入框默认提示,在用户输入后消失。
required 属性,是一个 boolean 属性。要求填写的输入域不能为空
pattern 属性,描述了一个正则表达式用于验证 元素的值。
min 和 max 属性,设置元素最小值与最大值。
step 属性,为输入域规定合法的数字间隔。
height 和 width 属性,用于 image 类型的 标签的图像高度和宽度。
autofocus 属性,是一个 boolean 属性。规定在页面加载时,域自动地获得焦点。
multiple 属性 ,是一个 boolean 属性。规定 元素中可选择多个值。
拖放API
2.cookie、sessionStorage和localStorage的区别
生命周期
cookie:可以设置失效时间,没有设置的话,默认关闭浏览器就是失效
localStorage:除非手动清除,否则永久保存
sessionStorage:仅在当前页面会话生效,关闭页面或者浏览器后就被清除
存放数据的大小
cookie:存放4kb左右
localStorage和sessionStorage:可以保存在5M左右
http请求
cookie:每次都会携带在http头中,保存过多会带来性能问题
localStorage和sessionStorage:仅存放在客户端(浏览器)中,不参与和服务器的通信
js部分
1.js基本数据类型
可以分为基本数据类型和引用数据类型.
基本数据类型存放在栈内存中,值与值之间相互独立存在,即修改一个变量不会影响哒其他的变量,
引用存放在在堆内存中
基本数据类型有: 字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined) symbol (es6新增)
引用数据类型有: 对象(Object)、数组(Array)、函数(Function)
2.判断数据类型的方法及区别
-
typeof
判断简单的数据结构,无法判断复杂的数据类型,比如object和array都会返回object
instanceof
仅用于对象,不适用简单类型的数据结构,返回布尔值.也可以判断是否是构造函数
constructor
他本身是prototype对象的一个属性,默认指向prototype所在的构造函数,返回的是布尔值
jQuery.type也可以判断数据类型
使用方法jQuery.type(str) //string 他能准确的返回要判断的数据类型
通过Object下的toString.call()方法来判断
所有typeof返回值为"object"的对象,都包含一个内部属性[[Class]],我们可以把他看作一个内部的分类,而非传统意义上面向对象的类,这个属性无法直接访问,一般通过Object.prototype.toString(…)来查看。并且对于基本数据类类型null,undefined这样没有原生构造函数,内部的[[Class]]属性值仍然是Null和Undefined
Object.prototype.toString.call(); console.log(toString.call(123)); //[object Number]
3.数组常用API
-
常用增删API
- push() 在数组尾部添加,会修改原数组,返回的是新数组
- pop() 在数组尾部删除,会修改原数组,返回的是被删除的元素
- unshift() 在数组头部添加,会修改原数组,返回的是数组长度
- shift() 在数组头部删除,会修改原数组,返回的是被删除的元素
-
数组裁切方法的API
slice(start,end)
包头不包尾,他不会修改原数组,返回的是裁切出来的数据
当只有一个参数的时候,直接从改参数的索引值开始,一直裁切到最后.如果参数是负数,则从尾部倒着数
splice(start,length,[可选参数列表])
start是开始下标,length是裁切的长度
只有一个参数就是从参数索引开始切到最后一个,如果两个参数就是从参数索引开始,切length个元素
如果[传递的参数]里面有参数,会把参数里面的内容放在裁切的位置.如果length为0的话,则把参数插在索引下标之前
-
- 其他常用API
-
concat([多个参数]) 合并数组
不会修改原数组,返回的是新数组
合并数组的其他方法
-
- for循环然后push进原数组
-
apply()
a.push.apply(a,b)把a当做参数传入a当中
-
- 扩展运算符 var c = […a,…b]
-
-
- join(‘链接符’) 把数组链接成字符串
-
- indexOf(要查找元素,[指定的下标]) 在数组中查找指定的元素,有就返回下标,没有就返回-1.如果不写第二个参数,就是查找整个数组
-
- reverse()反转数组
-
- sort()排序 不传参数就按unicode编码排序
-
- map()里面接受一个回调函数
4.数组去重
-
双循环去重
使用for(或者while)循环,比较笨拙时间复杂度是O(n*2).数组长度大会非常消耗内存
function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } let res = [arr[0]] for (let i = 1; i < arr.length; i++) { let flag = true for (let j = 0; j < res.length; j++) { if (arr[i] === res[j]) { flag = false; break } } if (flag) { res.push(arr[i]) } } return res }
-
indexOf()去重
新建一个空的结果数组,for循环原数组,判断结果数组是否存在当前元素,不存在就push进结果数组
//方法一 function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } let res = [] for (let i = 0; i < arr.length; i++) { if (res.indexOf(arr[i]) === -1) { res.push(arr[i]) } } return res } //方法二 :利用indexOf检测元素在数组中第一次出现的位置是否和元素现在的位置相等,如果不等则说明该元素是重复元素 function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } return Array.prototype.filter.call(arr, function(item, index){ return arr.indexOf(item) === index; }); }
-
set与结构赋值去重
ES6中新增了数据类型Set,Set的一个最大的特点就是数据不重复。Set函数可以接受一个数组(或类数组对象)作为参数来初始化,利用该特性也能做到给数组去重.这种方法是比较便利快速的
function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } return [...new Set(arr)] }
-
sort排序后去重,复杂度之比set低
这种方法首先调用了数组的排序方法sort(),然后根据排序后的结果进行遍历及相邻元素比对,如果相等则跳过改元素,直到遍历结束
function fn(arr){ let newArr = [] arr.sort((a,b)=>{ return a-b }) arr.forEach((val,index)=>{ if(val != arr[index+1]){ newArr.push(val) } }) return newArr }
-
Map()去重
创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果。{}空对象无法去重。
function arrayNonRepeatfy(arr) { let map = new Map(); let array = new Array(); // 数组用于返回结果 for (let i = 0; i < arr.length; i++) { if (map.has(arr[i])) { // 如果有该key值 map.set(arr[i], true); } else { map.set(arr[i], false); // 如果没有该key值 array.push(arr[i]); } } return array; }
5.原型和原型链
-
- 原型:js
- 中每一个函数都有一个半prototype属性,这个属性指向函数的原型对象,每一个有原型对象派生的子对象,都有相同的属性.子对象就叫做构造函数,从实例原型中获取相同的属性
-
- 构造函数:每个原型都有一个constructor属性,指向该关联的构造函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wYYVBH7p-1664029718045)(E:\Desktop\每日一结(1)]\前端面试题总结.md)
-
原型链:
构造函数,原型,原型链之间的关系:
每个班构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针.顺着_ proto _ 属性,一步步往上找,形成了像链条一样的结构,这个结构,就是原型链.
构造函数是使用了new关键字的函数,用来创建对象,所有函数都是Function()的实例
原型对象是用来存放实例对象的公有属性和公有方法的一个公共对象,所有原型对象都是Object()的实例
原型链又叫隐式原型链,是由__proto__属性串联起来,原型链的尽头是Object.prototype
6.es6新特性
-
- let和const
var,let和const三者之间的区别
- var 声明的变量属于函数作用域,即全局作用域,存在变量提升的情况
- let 声明的变量属于块级作用域,不存在变量提升的情况,变量不能重复声明
- const 声明的变量属于块级作用域,也不存在变量提升的情况,声明的变量不能修改,也就是说,只能声明常量
-
解构赋值
结构赋值是对赋值运算符的扩展。
-
箭头函数
箭头函数与普通函数最大的区别就在于气内部this永远指向其父级的AO对象
1、如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
2、如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回
3、如果返回对象,需要加括号将对象包裹
4、注意点:
函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替
-
promise
是异步编程的一种解决方案,对象的状态不受外界影响,只有异步操作的结果,有三个状态:pending(进行中),fulfilled(成功),rejected(失败)。
promise对象是一个构造函数,用来生成promise实例:
const promise = new Promise(function(resolve, reject) {}); //resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功” //reject函数的作用是,将Promise对象的状态从“未完成”变为“失败
实例存在有then()方法(状态发生改变时候的回调函数),catch(),catch是用于发生错误的回调
-
proxy
他是用来定义基本操作的自定义行为
修改的是程序默认行为,就形同于在编程语言层面上做修改,属于元编程
(meta programming)
。Proxy
亦是如此,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
Proxy
为 构造函数,用来生成Proxy
实例
var proxy = new Proxy(target, handler)
//target表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))
//handler通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为Set
提供了新的数据结构 Set 集合. 它类似于数组,但是成员的值都是唯一的, 集合实现了 iterator 接口, 所以可以使用[扩展运算符]和[for…of]进行遍历, 集合的属性和方法:
- Map
ES6 提供了 Map
数据结构
. 它类似于对象,也是键值对的集合, 但是"键"的范围不限于字符串,各种类型的值(包括对象)都可以当做键. Map 也实现了 iterator 接口, 所以可以使用[扩展运算符]和[for…of进行遍历].
7.事件循环
原因:JavaScript是单线程,所有任务需要排队,前一个任务结束,才会执行后一个任务。
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务:在主线程上排队执行的任务.只有上一个任务执行完毕才能执行下一个任务
异步任务:不进入主线程,进入"任务队列中",只有当"任务队列"通知主线程,某个异步任务可以执行的,该任务才会进入主线程执行.异步任务里面又分为宏任务和微任务,
微任务:Promise.then、catch、finally、async/await。
宏任务:整体代码 Script、UI 渲染、setTimeout、setInterval、Dom事件、ajax事件。
同步和异步任务分别进入不同的执行环境, 先执行同步任务,把异步任务放入循环队列当中挂起,等待同步任务执行完,再执行队列中的异步任务。异步任务**先执行微观任务,再执行宏观任务.**即同步任务>>异步微任务>>异步宏任务的执行顺序(主线程上先宏任务再微任务)
由于主线程不断重复获得任务,执行任务,在获取,在执行.所以这种机制叫做事件循环
8.Promise有哪些使用场景?
在页面打开时,要同时执行多个ajax请求,可以使用promise处理多异步任务并发执行
有些ajax请求之间存在依赖关系,需要顺序执行,造成结构嵌套,可以使用promise解决异步任务多层嵌套的问题, 实现链式调用
在项目中封装网络请求时,使用peomise封装ajax请求并返回peomise对象
9.箭头函数和普通函数的区别
箭头函数内部this跟函数所在上下文this保持一致
没有arguments参数集合,可以用rest替代
不能使用call、bind、apply来改变this的指向
10.Ajax中get和post两种请求方式的区别
(1)运行速度:get请求简单,运行速度也更快(存在缓存);
(2)缓存:get存在缓存(优:提升速度,缺:不能及时获取最新数据)post没有缓存;
(3)数据储量:get有数据量的限制,post则没有限制
(4)数据安全:发送包含未知字符的用户输入时,post比get 更稳定也更可靠;
(5)传参方式:get参数拼接在url后,post放在send里面并且需要设置请求头xmr.setRequestHeader(“content-type”,“application/x-www-form-urlencoded”)
11. webWorkers是什么?
WebWorker是运行在后台的javascript,独立于其他脚本,不会影响页面的性能。在javascript单线程执行的基础上,开启一个子线程,进行程序处理,而不影响主线程的执行,当子线程执行完毕之后,再回到主线程上,在这个过程中,并不影响主线程的执行过程。
WebWorker为Web应用程序提供了一种能在后台中运行的方法。通过WebWorker可以生成多个线程同时运行,并保证页面对用户的及时响应,完全不会影响用户的正常操作。
11.为什么要使用WebWorker
解决一些页面的卡顿问题。
解决某些函数执行时间过长,体验不流畅,卡顿。
11. webSocket是什么?适用于哪些网站?
webSocket是一种双工通信技术,可以实现服务器主动向客户端发送数据。
一般适用于需要实时通信的网站, 比如人工客服服务和在线页游等
12.Css文件中@import,@font-face,@keyframes,@media这4个关键字的作用是什么?
@import,导入另一个css文件,@font-face,导入一个字体文件,@keyframes声明一个关键帧动画,@media声明一个媒体查询条件。
vue部分
1.数据双向绑定
通过数据劫持结合发布—订阅模式,通过Object.defineProperty()为各个属性定义get、set方法,在数据发生改变时给订阅者发布消息,触发相应的事件回调。
2.vue生命周期
概念:从创建,初始化数据,编译模板,挂载dom,渲染,更新渲染,卸载等一系列过程,成为vue实例的生命周期
vue2.0
- beforeCreate:创建前.此时,vue刚刚创建,还未进行数据加载和事件配置有this但是拿不到值
- created:创建完成.但是视图没渲染,可以操作数据 ,可以在这里发送请求,请求下来可以直接赋值给data的某个值。
- beforeMount:
- mounted:挂载完成。视图渲染完毕,可以在这里操作dom和bom
- beforeUpdate:数据更新前。可以在这里添加判断阻止更新。发生在虚拟dom重新渲染和打补丁之前,不会触发附加的重渲染过程
- updated:更新后,用于数据更改和补丁完成之后,可以在这里获取到最新的值
- beforeDestroy
- destroyed
**keep-alive **
include 只有名称匹配的组件会被缓存。
exclude 任何名称匹配的组件都不会被缓存。
max 最多可以缓存多少组件实例。
-
- activated 显示周期
- deactivated 隐藏周期
vue3.0
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmount
onUnmounted
3. vuex页面刷新数据丢失问题
为什么会丢失
刷新页面vuex的数据会丢失属于正常现象,因为JS的数据都是保存在浏览器的堆栈内存里面的,刷新浏览器页面,以前堆栈申请的内存被释放,这就是浏览器的运行机制,那么堆栈里的数据自然就清空了。
解决问题
方式一: 用sessionStorage
将接口返回的数据保存在vuex的store里,也将这些信息也保存在sessionStorage里。注意的是vuex中的变量是响应式的,而sessionStorage不是,当你改变vuex中的状态,组件会检测到改变,而sessionStorage就不会了,页面要重新刷新才可以看到改变,所以应让vuex中的状态从sessionStorage中得到,这样组件就可以响应式的变化。
方式二: 用插件
4. computed和watch的区别
computed:{} 计算属性 多个地方影响一个数据的时候使用
- 支持缓存,只有依赖数据发生改变,才会重新进行计算
- 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
- computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值 。调用时候直接写属性名,把结果缓存起来,下次从缓存里面读取结果,当数据源发生变化的时候,会重新计算,节省性能,提高效率。
- 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
watch:{} 监听属性,监听dom,可以分为深度监听和普通监听 一个数据影响多个地方的时候用
- 不支持缓存,数据变,直接会触发相应的操作;
- watch支持异步;
- 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
- 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数:
immediate:组件加载立即触发回调函数执行,deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。
5. 路由传参
5. vue路由
一个路由就是一组映射关系(key-value)。key为路径,vulae可能是function或者component
vue-router是vue的一个插件,专门用来实现SPA应用。SPA也就是单页Web应用,特点是:整个应
用只有一个完整的页面,点击页面中的导航链接不会刷新页面,只会做页面的局部更新,数据需要
通过ajax请求获取。
1.路由跳转
-
router-link
不带参数
<router-link :to="{name:'home'}"> <router-link :to="{path:'/home'}"> //name,path都行, 建议用name // 注意:router-link中链接如果是'/'开始就是从根路由开始,如果开始不带'/',则从当前路由开始。
带参数
<router-link :to="{name:'home', params: {id:1}}"> // params传参数 (类似post) // 路由配置 path: "/home/:id" 或者 path: "/home:id" // 不配置path ,第一次可请求,刷新页面id会消失 // 配置path,刷新页面id会保留 // html 取参 $route.params.id // script 取参 this.$route.params.id <router-link :to="{name:'home', query: {id:1}}"> // query传参数 (类似get,url后面会显示参数) // 路由可不配置 // html 取参 $route.query.id // script 取参 this.$route.query.id
-
this.$router.push() (函数里面调用)
不带参数
this.$router.push('/home') this.$router.push({name:'home'}) this.$router.push({path:'/home'})
query传参
this.$router.push({name:'home',query: {id:'1'}}) this.$router.push({path:'/home',query: {id:'1'}}) // html 取参 $route.query.id // script 取参 this.$route.query.id
params传参
this.$router.push({name:'home',params: {id:'1'}}) // 只能用 name // 路由配置 path: "/home/:id" 或者 path: "/home:id" , // 不配置path ,第一次可请求,刷新页面id会消失 // 配置path,刷新页面id会保留 // html 取参 $route.params.id // script 取参 this.$route.params.id
- query和params的区别
-
- 使用params传参只能用name引入路由。因为params只能用name来引入路由
-
- 使用query传参必须使用path引入路由
-
- params是路由的一部分,必须要在路由后面添加参数名。query是拼接在url后面的参数。有没有不影响
-
- query相当于get请求,页面跳转的时候,可以在地址栏上看到请求的参数。二params相当于post请求,参数不会在地址栏上面显示
-
-
this.$router.replace()
用法跟上面this.$router.push()一样
-
this.$router.go(n)
向前或者向后跳转n个页面,n可以是正数,也可以是负数
-
-
缓存路由组件
1、作用:让不展示的路由组件保持挂载,不被销毁。
2、具体代码:
<keep-alive include="News"> <router-view></router-view> </keep-alive>
-
路由守卫
1、作用:对路由进行权限控制;
2、分类:全局守卫、独享守卫、组件内守卫;
3、全局守卫:
全局前置守卫,初始化时执行,每次路由切换前执行
全局后置路由守卫,初始化时执行,每次路由切换之后被调用
4、独享路由守卫:某一个路由所独享的;
6. vue中$nextTick作用和原理
nextTick 就是设置一个回调,用于异步执行。(当数据更新了,在dom中渲染后,自动执行该函数,)
就是把你设置的回调放在 setTimeout 中执行,这样就算异步了,等待当时同步代码执行完毕再执行。
this.$nextTick( => {
this.$refs.saveTagInput.$refs.input.focus();
});
当页面上的元素被重新渲染之后,才会执行指定回调函数中的代码。
vue.nextTick(callback)使用原理
原因是,Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
当你设置 vm.someData = ‘new value’,DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
7. 路由懒加载
整个网页默认是刚打开就去加载所有的页面,路由懒加载就是只加载你点击的那个页面
按需加载路由对应的资源,提高首屏的加载速度(首页不用设置懒加载,而是一个页面加载过后再次访问不会重复加载)
懒加载实现原理:将路由的相关组件,不在直接导入,而是改写成异步组件的写法.只有函数被调用的时候才会执行,才回去加载对应组件的内容
//传统路由配置
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '@/views/login/index.vue'
import Home from '@/views/home/home.vue'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/login', component: Login },
{ path: '/home', component: Home }
]
export default router
//路由懒加载写法
//const Login = ()=> {
// return import('@/views/login/index.vue')
//}
//有return且函数体只有一行,所以省略后为
const Login = ()=> import('@/views/login/index.vue')
const Home = ()=> import('@/views/home/home.vue')
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/login', component: Login },
{ path: '/home', component: Home }
]
//路由懒加载简化写法,省去自定义变量
const router = new VueRouter({
routes: [
{ path: '/login', component: () => import('@/views/login/index.vue') },
{ path: '/home', component: () => import('@/views/home/home.vue') }
]
export default router
8. hash模式和history模式
// 两种路由模式:
// createWebHashHistory hash模式原理
// 会带一个#号 通过 loaction.hash 跳转的
// 通过这个监听事件来监听路由变化
// window.addEventListener("hashchange",(e)=>{
// console.log(e);
// })
// createWebHistory history模式
// 是基于 h5 的 history 去实现的
// 通过 history.pushState({},"",) 这个函数来跳转的 参一:是一个对象可以存一些信息 参二:是一个title 参三:是跳转的路径
// 但是通过这个跳转之后不会被监听到 切换完路径需要手动刷新 或者调用vue内置的push方法
// 是通过这个监听事件来监听路由的变化的
// window.addEventListener("popstate",(e)=> {
// console.log(e);
// })
9. 响应式原理
vue2的响应式原理:
使用的是
Object.defineProperty()
进行数据劫持,主要是通过getter
和setter
方法, 在getter中收集依赖,在setter中触发依赖 ,就是读取数据的时候,会触发getter
函数,修改数据的时候会触发setter
函数. 但getter/setter
只能追踪一个数据是否被修改,无法追踪新增属性和删除属性.Object.defineProperty
不能监听数组的变化,还需要进行数组方法的重写
vue3的响应式原理
使用的是ES6新增的
Proxy
,Proxy
的代理是针对整个对象的,而不是对象的某个属性,因此不同于Object.defineProperty
的必须遍历对象每个属性,Proxy
只需要做一层代理就可以监听同级结构下的所有属性变化,当然对于深层结构,还是需要进行递归的。此外**Proxy
支持代理数组的变化。** 不过proxy的兼容性不好
读取数据的时候触发的是getter方法,修改数据触发的是setter方法
10vue组件之间的通信
-
父子组件传值 (props)
//先在父组件中给子组件的自定义属性绑定一个父组件的变量 <template class="father"> <child :自定义属性名="父组件的变量"></child> <template > //在子组件的props属性接受父组件传来的值 export default { name: "child", props: ["自定义属性名"], data() {} }
-
子组件向父组件传值
//先在父组件中给子组件 <template class="father"> <child @自定义事件="父的处理函数"> <template > export default { name: "father", data() {} methods:{ 父的处理函数(参数){ //参数:得到子组件触发事件($emit)时,传递过来的数据 } } })
-
兄弟组件传值
- 创建全局空Vue实例:eventBus
- 具体页面使用$emit发布事件 - 传递值
- 具体页面使用$on订阅事件 - 接收组件值
o n 先进行监听,一旦 on先进行监听,一旦 on先进行监听,一旦emit发布事件后所有组件都可以 o n 监听到事件。所以传递参数的时候一定已经先进行了监听才能得到参数。比如在父组件中 on监听到事件。所以传递参数的时候一定已经先进行了监听才能得到参数。比如在父组件中 on监听到事件。所以传递参数的时候一定已经先进行了监听才能得到参数。比如在父组件中emit事件放在mounted钩子函数中,等待子组件创建并 o n 开始监听事件后再去触发 on开始监听事件后再去触发 on开始监听事件后再去触发emit发布事件。
ref
ref 被用来给DOM元素或子组件注册引用信息。引用信息会根据父组件的 $refs 对象进行注册。如果在普通的DOM元素上使用,引用信息就是元素; 如果用在子组件上,引用信息就是组件实例。
在父组件中自定义一个表格组件,给子组件加上 ref属性
在父组件中就可以通过this.$refs.result去找到result子组件进行操作,比如把父组件的sdata直接放入子组件中
11父组件和子组件生命周期钩子函数执行顺序
加载渲染过程:
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
子组件更新过程:
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父组件更新过程:
父 beforeUpdate -> 父 updated
销毁过程:
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
12. vue2和vue3的区别
- 底层发生了变化, Vue3 之前使用了 ES5 的一个 API Object.defineProperty Vue3 中使用了 ES6 的 Proxy,都是对需要侦测的数据进行 变化侦测 ,添加 getter 和 setter ,这样就可以知道数据何时被读取和修改。
- vue3组件中可以有很多个根节点
- vue3使用的是组合式Api
- 写法上有很大的区别
- vue2的数据是在data中,vue3是setup
- 不同的生命周期钩子函数
- vue3中基本上没有this
- vue3中取消了过滤器
- vue3中的组件在引入的时候,不用在声明
13. vue和ts的关联
在vue文件中,可以在script标签中添加lang=ts, 就可以使用ts语法了
14. 三次握手4次挥手突然中断请求
报状态码400的错误
15. vue的缓存机制
16. keepalive底层原理
keep-alive是一个组件,这个组件中有三个属性,分别是include、exclude、max,在created中创建缓存列表和缓存组件的key列表,销毁的时候会做一个循环销毁清空所有的缓存和key,当mounted时会监控include 和 include属性,进行组件的缓存处理。如果发生变化会动态的添加和删除缓存。渲染的时候会去拿默认插槽,只缓存第一个组件,取出组件的名字,判断是否在缓存中,如果在就缓存,不在就直接return掉,缓存的时候,如果组件没有key,就自己通过组件的标签、key和cid拼接一个key。如果该组件缓存过,就直接拿到组件实例。如果没有缓存过就把当前的vnode缓存,和key做一个对应关系。这里面有一个算法叫LRU,如果有key就不停的取,如果超限了就采用LRU进行删除最近最久未使用的,从前面删除,LRU就是将当前使用的往数组的后面移,在最前面的就是最久未使用的。
17. 说一下常见的请求头
18. 谈一谈对vue组件化的了解
组件是独立和可复用的代码组织单元。组件系统是 Vue 核心特性之一,它使开发者使用小型、 独立和通常可复用的组件构建大型应用;
组件化开发能大幅提高应用开发效率、测试性、复用性等;
组件使用按分类有:页面组件、业务组件、通用组件;
vue 的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生成其构造函数,它们基于 VueComponent,扩展于 Vue
vue 中常见组件化技术有:属性 prop,自定义事件,插槽等,它们主要用于组件通信、扩展等;
合理的划分组件,有助于提升应用性能;
组件应该是高内聚、低耦合的;
遵循单向数据流的原则。
19.mvc,mvp和mvvm
这三者都是框架模式,它们设计的目标都是为了解决 Model 和 View 的耦合问题。
MVC 模式出现较早主要应用在后端,如 Spring MVC、ASP.NET MVC 等,在前端领域的早期也有应用,如Backbone.js。它的优点是分层清晰,缺点是数据流混乱,灵活性带来的维护性问题。
MVP 模式在是 MVC 的进化形式,Presenter 作为中间层负责 MV 通信,解决了两者耦合问题,但 P 层 过于臃肿会导致维护问题。
MVVM 模式在前端领域有广泛应用,它不仅解决 MV 耦合问题,还同时解决了维护两者映射关系的大量繁杂代码和 DOM 操作代码,在提高开发效率、可读性同时还保持了优越的性能表现。
20. 你了解哪些 Vue性能优化方法?
路由懒加载
keep-alive 缓存页面
使用 v-show 复用 DOM
v-for 遍历避免同时使用 v-if
长列表性能优化
- 如果列表是纯粹的数据展示,不会有任何改变,就不需要做响应化
- 如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容
事件的销毁
图片懒加载
第三方插件按需引入
21. vuex 中数据存储 localStorage
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在 vuex 里数据改变的时候把数据拷贝一份保存到 localStorage 里面, 刷新之 后,如果 localStorage 里有保存的数据,取出来再替换 store 里的 state 。
注意:vuex 里,保存的状态,都是数组,而 localStorage 只支持字符串,所以需要用 JSON 转换
22. vue中常用的通信方式
props (父传子)
e m i t / emit/ emit/on 事件总线 (跨层级通信)
vuex (状态管理 常用 皆可) 优点:一次存储数据,所有页面都可访问
provide/inject (高阶用法 = 推荐使用 ) 优点:使用简单 缺点:不是响应式
23. vue-router 中的导航钩子(路由守卫)有那些?
to:要去哪 from: 从哪来 next: 是否放行
- 全局的钩子函数(全局守卫)
beforeEach (to,from,next) 路由改变前调用,常用验证用户权限
- 路由配置中的导航钩子(路由独享守卫)
beforeEnter (to,from,next)
- 组件内的钩子函数(组件守卫)
- beforeRouteEnter (to,from,next)
该组件的对应路由被 comfirm 前调用。
此时实例还没被创建,所以不能获取实例(this)
- beforeRouteUpdate (to,from,next)
当前路由改变,但改组件被复用时候调用
该函数内可以访问组件实例(this)
- beforeRouteLeave (to,from,next)
当导航离开组件的对应路由时调用。
该函数内可以访问获取组件实例(this)
24. 谈谈对vue的理解
1.数据驱动(MVVM)
MVVM`表示的是 `Model-View-ViewModel
- Model:模型层,负责处理业务逻辑以及和服务器端进行交互
- View:视图层:负责将数据模型转化为UI展示出来,可以简单的理解为HTML页面
- ViewModel:视图模型层,用来连接Model和View,是Model和View之间的通信桥梁
2.组件化
1.什么是组件化
一句话来说就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue
中每一个.vue
文件都可以视为一个组件
2.组件化的优势
- 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历、时间、范围等组件作具体的实现
- 调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单
- 提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级
移动端
1.解释移动端手势穿透现象? 如何解决?
原因: click事件触发后有300ms的延迟响应, 当点击遮罩层时,tap事件触发即立即响应,执行隐藏, 而click事件触发会在0.3s之后响应, 而在0.3s之后, 遮罩层已经隐藏, 则由下层输入框响应这个click事件, 而获取焦点
解决方案:
1, 阻止btn的touchend事件的默认行为,
2, 延迟300ms执行遮罩层cover的隐藏, 那click响应时,btn还在,即相应给btn
3, 使用插件 fastclick.js 防止手势穿透, 这个插件专为解决手势穿透现象而生
fastclick取消click事件300ms延迟的原理: fastclick监听了click事件的触发, 当body上触发了click事件时, fastclick会自定义一个事件,并立即触发, 并把监听到的系统click事件阻止掉
后来加的其他知识点
1.跨域拦截以及解决方法
后端设置一个
cors
请求头, 这种是最简单的使用
jsonp
,不过不常用前端自己代理服务器,
2. 常见状态码
100:继续
200:成功
400:错误请求
401:未授权
403:服务器拒绝请求,禁止
404:服务器找不到请求的网页
405:禁止请求中指定的方法
408:请求超时
414:请求的网址URL过长,服务器无法处理
500:服务器内部错误
505:服务器不支持请求中所用的http协议版本
3. url到页面发生了什么
- 查询缓存
- DNS服务器
- TCP三次握手
- HTTP协议包
- 浏览器处理HTML文档
4.深浅拷贝
5.防抖节流
节流: 若在n秒内重复触发,只生效最开始的一次
防抖: 若在n秒内重复触发,每次都会执行最新的那次
6. 性能优化
四个层面八个点、
四个层面:网络层面、构建层面、浏览器渲染层面、服务端层面
八个点:1. 资源的合并与压缩 2. 图片的编码原理和类型选择 3. 浏览器的渲染机制 4. 懒加载预加载 5. 浏览器存储 6. 缓存机制 7. pwa 8. vue ssr
7. webpack打包
Webpack:把所有依赖打包成一个 bundle.js文件,通过代码分割成单元片段并按需加载。Webpack是以公共JS的形式来书写脚本的,但对AMD/CMD的支持也很全面,方便旧项目进行代码迁移。
把项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。babel将es6、es7、es8等语法转换成浏览器可识别的es5或es3语法。
webpack打包
9. form表单将信息提交到数据库
在from表单里面设置飙到将要提交到的目的地的名字
form表单内部使用div分区
(后端内容,自行学习)
10. 事件传播流程
事件传播的三个过程:捕获、冒泡、目标阶段
事件流:指从页面中接收事件的顺序,有冒泡流和捕获流。
事件捕获由远及近逐渐靠近事件目标,事件冒泡由事件目标逐渐向上冒泡
可以通过设置addEventListener(type,handler,useCapture)中的userCapture值来决定元素是在冒泡阶段执行事件还是捕获阶段执行,默认为false,即冒泡阶段。
11.SPA(单页面应用)首屏加载慢的问题
原因:
- 网络延时问题
- 资源文件体积是否过大
- 资源是否重复发送请求去加载了
- 加载脚本的时候,渲染内容堵塞了
解决:
- 减小入口文件积
- 静态资源本地缓存
- UI框架按需加载
- 图片资源的压缩
- 组件重复打包
Token验证的过程
- 用户通过用户密码发送请求
- 服务器端程序验证
- 服务器端程序返回一个带签名的token给客户端
- 客户端存储token,并且每次访问API都携带token到服务端
- 服务端验证token,校验成功则返回请求数据,校验失败则返回错误码
项目中使用token
- 前端使用用户名和密码请求首次登录
- 后端收到请求之后,去验证用户名和密码是否正确
- 验证成功后,服务端会根据用户id,用户名,定义好密钥和过期时间生成一个token,在把这个token发给前端
- 前端收到返回的
token
,把它存储起来,比如放在Cookie
里面或者LocalStorage
里- 前端每次路由跳转,判断
LocalStorage
有没有token,没有就跳到登录页,有则请求用户信息,改变登录状态- 前端每次向服务端请求资源的时候需要在
请求头
里面携带服务端签发的token
- 服务端收到请求,然后去验证前端请求里面带着的token,没有或者token过期,返回401,如果验证成功,就向前端返回请求的数据
- 前端得到401状态码,重定向到登录页面
vuex
1. vuex中的五个属性
**state, getters, mutations, actions, modules **
2. 每个属性的用途
state
:vuex的基本数据,用来存储变量
**在vue中使用: ** this.$store.state
geeter
:从基本数据(state)派生的数据,相当于state的计算属性,具有返回值的方法
**在vue中使用: ** this.$store.getters
mutation
:提交更新数据的方法,必须是同步的(如果需要异步使用action)。每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)
**在vue中使用: ** this.$store.commit(‘mutations内的方法名’,值)
action
:和mutation
的功能大致相同,不同之处在于 ==》1. action 提交的是 mutation,而不是直接变更状态。 2. action 可以包含任意异步操作。
**在vue中使用: ** this.$store.dispatch(‘actions内的方法名’,值)
**说明 **: 这个里面存放的都是异步的, 异步的要先提交到mutations
内, 在通过mutations
进行操作
modules
:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。简单来说就是可以把以上的 state、mutation、action、getters 整合成一个user.js,然后放到store.js里面
3. Vuex的辅助函数
mapState、mapGetters、mapActions、mapMutations
1.mapState 获取数据
将 vuex内的state的数据映射到 页面
mapState和mapGetters都是使用在computed中的
2.mapGetters vuex中的计算属性
mapGetters可以将getters中的方法映射到 computed 计算属性中 便于在页面中直接使用getters数据
3.mapMutations
把 mutations 里面的方法映射到 methods 中
4.mapActions
把 actions 方法映射到 methods 中
mapMutations 和mapActions都是函数方法 所以都是放在methods中的 ,这里就把他们放在一起使用
4. vuex的注意事项
数据流都是单向的
组件能够调用action
action用来派发mutation
只有mutation可以改变状态
store是响应式的,无论state什么时候更新,组件都将同步更新
tion`:提交更新数据的方法,必须是同步的(如果需要异步使用action)。每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)
**在vue中使用: ** this.$store.commit(‘mutations内的方法名’,值)
action
:和mutation
的功能大致相同,不同之处在于 ==》1. action 提交的是 mutation,而不是直接变更状态。 2. action 可以包含任意异步操作。
**在vue中使用: ** this.$store.dispatch(‘actions内的方法名’,值)
**说明 **: 这个里面存放的都是异步的, 异步的要先提交到mutations
内, 在通过mutations
进行操作
modules
:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。简单来说就是可以把以上的 state、mutation、action、getters 整合成一个user.js,然后放到store.js里面
3. Vuex的辅助函数
mapState、mapGetters、mapActions、mapMutations
1.mapState 获取数据
将 vuex内的state的数据映射到 页面
mapState和mapGetters都是使用在computed中的
2.mapGetters vuex中的计算属性
mapGetters可以将getters中的方法映射到 computed 计算属性中 便于在页面中直接使用getters数据
3.mapMutations
把 mutations 里面的方法映射到 methods 中
4.mapActions
把 actions 方法映射到 methods 中
mapMutations 和mapActions都是函数方法 所以都是放在methods中的 ,这里就把他们放在一起使用
4. vuex的注意事项
数据流都是单向的
组件能够调用action
action用来派发mutation
只有mutation可以改变状态
store是响应式的,无论state什么时候更新,组件都将同步更新