前端面试题(一)
1.Vue的生命周期
vue的生命周期分为8个阶段:
beforeCrete( 创建前 ):在实例初始化之后,数据观测和事件配置之前被调用,此时无法访问methods, data, computed等上的方法和数据。
created( 创建后 ):创建实例之后执行,此时可以访问到methods, data, computed等上的方法和数据。
beforeMount:模版已经编译完成,但是尚未挂在到真实DOM上
mounted阶段:vue实例挂载到真实DOM上,此时可以操作Dom
beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前,适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器
updated:虚拟DOM重新渲染和打补丁之后调用,组成新的DOM已经更新
beforeDestroy:实例销毁前调用,实例还可以用,this能获取到实例,常用于销毁定时器,解绑事件
destroyed:实例销毁后调用,调用后所有事件监听器会被移除,所有的子实例都会被销毁
2.vue中 key 值的作用
使用key来给每个节点做一个唯一标识,主要是为了高效的更新虚拟DOM。
3.$route和$router的区别
$router是路由器的意思,通常用来进行路由跳转,例如:
this.$router.push()、replace()/back()/go()
$route是记录当前路由信息的一个对象,例如:
$route.path/params/query/meta
4.vue修饰符
vue修饰符一般用来控制事件的冒泡和默认行为
.stop:阻止事件冒泡
.prevent:阻止默认事件
.once:只触发一次
5.axios是什么?怎么使用
(1)安装与引入:通过npm i axios -S将axios安卓到本地,然后通过import axios from 'axios'引入到项目中
(2)默认配置和封装:创建实例,设置baseurl,添加请求拦截器,为每个请求的请求头添加token,添加 loading 的UI效果。添加响应拦截器,当请求回来后关闭 loading 的UI效果,并根据状态码判断token是否过期(401 未登录 403 登录过期),以此决定后一步的操作
(3)配置代理解决跨域问题:打开config/index.js文件,在proxyTable节点中进行设置
proxyTable: {
'/api': { // 匹配以/api开头的所有路径
target: 'http://localhost:4000', // 代理的后端api基础路径
changeOrigin: true, // 支持跨域
pathRewrite: { // 重写路径,去掉路径中开头的'/api'
'^/api': ''
// '^/api': 'api'
}
}
}
6.vue数据存储
vue存储有三种:cookie/localstorage/vuex
cookie一般由服务器生成,可以设置过期时间,大小限制为4K,每次同源请求都会携带在 header 中,参与到服务端的通信。前端通过document.cookie对cookie进行操作
localstorage除非被清理,否则一直存在,通过专门的方法setItem()/getItem()可以很容易的对数据进行操作,大小限制在5M,不参与服务端通信
vuex通过将$store挂载到Vue原型链中,使得所有的vue实例都能访问到它,便于各个组件间的通信。但是他存储在内存中,页面刷新或者关闭则数据失效
7.vue的双向绑定原理
先看原理图
(1)对data对象中所有层次的属性进行遍历,通过Object.defineProperty()实现数据劫持,同时为每个属性创建一个Dep,用来存放订阅者
(2)对模版进行编译,如果模版中的某个节点中存在数据绑定的指令,例如v-model,v-html,v-bind,双括号语法,除了将模版中变量替换成数据,还会为此创建一个watcher,然后添加订阅,将这个watcher添加到对应的dep中。如果是v-model指令,还会为此表单元素添加事件监听,当表单元素的值发生改变时,则触发其回调,将最新的值同步到data中
(3)当data中的数据发生改变时,会触发该属性的set方法,在set方法内通知订阅者数组dep,订阅者数组循环调用各订阅者的update方法更新视图。
8.闭包的概念?优缺点?
闭包的概念:闭包就是能读取其他函数内部变量的函数。
优点:能够缓存函数内部的变量,避免全局变量污染
缺点:增加内存使用量
实现方式:
在函数A内部定义一个函数B,在函数B内部对函数A的变量C进行操作,然后将函数B return出去,将会形成一个闭包。
因为函数B在A的内部,所以它能够对函数A内的变量进行操作,函数B返回之后,函数A内部和函数外部搭建起一座桥梁,使得函数A的内存无法被释放,里面的变量得以缓存下来。
9.节流和防抖
节流:当一个事件被频繁触发时(滚动条滚动,鼠标移动事件),为了节省性能,我们希望某个固定的时间段内只触发一次
具体做法就是:设定一个时间间隔,当事件发生时,对比当前时间和上次回调执行时间作对比,如果时间差超过了规定的时间间隔,则触发回调,并更新回调执行时间
代码:
<script>
function throttle(cb,delay){
//记录上次回调执行时的时间戳
var prev = 0
//定义内部函数并返回,形成闭包,缓存prev
return function(){
//获取函数调用时的上下文
var context = this
//获取参数(事件对象)
var event = arguments[0]
//对比时间差 超过时间间隔则执行回调
if(Date.now() - prev >= delay){
//执行回调(绑定this,传递参数)
cb && cb.apply(context,event)
//更新prev
prev = Date.now()
}
}
}
function handle(e){
console.log(e)
}
document.addEventListener('scroll', throttle(handle,200))
</script>
防抖:当事件触发时,将回调延迟执行,如果在延迟时间内再次触发了事件,则重置回调执行的延迟时间。
效果:如果一个事件被频繁触发,只有最后一次触发事件后才会执行回调
实现方式:通过设置延时定时器来推迟回调执行,如果延迟期间再次触发回调,则清除延时定时器,并开启新的延时定时器
<script>
function debounce(fn, wait) {
//默认情况下 没有延时定时器
var timeout = null
//定义内部函数并返回,形成闭包,缓存 timeout
return function() {
//获取函数调用时的上下文
var context = this
//获取参数(事件对象)
var event = arguments[0]
//如果已经存在延时定时器 则尝试清除
if(timeout !== null) {
clearTimeout(timeout)
}
//开启新的延时计时器
timeout = setTimeout(fn.bind(context,event), wait)
}
}
function handle(e){
console.log(e)
}
document.addEventListener('scroll', debounce(handle,200))
</script>
10.数据类型?深拷贝和浅拷贝?
(1)数据类型分为:
字符串类型,数字类型,布尔类型,undefined,null,数组,对象
(2)如何区分数据类型:
使用typeof,根据输出的值类判断数据类型,字符串类型返回sting,数字类型返回number,布尔类型返回boolean,undefined返回undefined,null,数组,对象这三个则返回object,需要进行进一步的区分
要区分null容易,直接判断值是否为null即可
区分数组有多种方法:
Array.isArray(val)
val instanceof Array
<script>
console.log(typeof 'hello world') //string
console.log(typeof 100) //number
console.log(typeof true) //boolean
console.log(typeof undefined) //undefined
console.log(typeof null) //object
console.log(typeof []) //object
console.log(typeof {}) //object
function dataType(val){
var type = (typeof val).toLocaleLowerCase()
//如果不为object,则直接返回
if(type === 'object'){
//判断是否为null
if(val === null){
return null
}else if(Array.isArray(val)){
//val instanceof Array
return 'array'
}else{
return 'object'
}
}else{
//如果不为object,则直接返回
return type
}
}
console.log(dataType(null)) //null
console.log(dataType([])) //array
console.log(dataType({})) //object
</script>
(3)浅拷贝:直接使用 '=' 赋值就是浅拷贝,想数组/对象使用浅拷贝赋值的话,因为他们共享同一个数据,所以会相互影响
(4)深拷贝就是给数组/对象赋值时让他们拥有自己独立的数据,互不影响。具体的方法有3种:
方法 | 代码 | 说明 |
---|---|---|
JSON转换 | JSON.parse(JSON.stringify(obj)) | 将对象(数组)转换为字符串在转换成对象(数组) |
E6的解构赋值 | {...obj} | 创建新的对象,使用解构赋值将键值对填入(注意:二级嵌套时此方法无效) |
遍历 | for() | 遍历数组或对象,依次添加属性 |
<script>
var obj = {name:'zhangsan',age:20}
//浅拷贝
var obj2 = obj
//深拷贝1
var obj3 = JSON.parse(JSON.stringify(obj))
//深拷贝2
var obj4 = {...obj}
//深拷贝3
var obj5 = {}
for(key in obj){
obj5[key] = obj[key]
}
//修改obj
obj.age = 25
console.log(obj2.age) //25 浅拷贝受到影响
console.log(obj3.age) //20
console.log(obj4.age) //20
console.log(obj5.age) //20
</script>