Vue面试题整理

vue修饰符&适用场景

vue修饰符大致分为5类表单修饰符事件修饰符鼠标修饰符键盘修饰符v-bind修饰符

路由守卫

全局路由守卫:beforeEach(全局前置守卫),afterEach(全局后置守卫)

作用范围是:所有路由

beforeEach(全局前置守卫)调用:

1、组件初始化时调用;

2、每次路由切换之前调用,A切换至B,B还没有呈现时调用

//全局前置守卫
router.beforeEach((to,from,next) =>{
  //第一个参数to,包含的内容是切换后的路由对象,也就是跳转后的路由对象 =》路由B
  //第二个参数from,包含的内容的是切换前的路由对象,也就是跳转前的路由对象 =〉路由A
  //第三个参数next(),是否往下执行,执行的话,如果不写的话路由就不会跳转,操作将会终止
})

afterEach(全局后置守卫)调用:

1、组件初始化时调用;

2、每次路由切换之调用,A切换至B,B呈现时调用

应用(设置系统内tab页签) 

router.afterEach((to,from) =>{
  //第一个参数to,包含的内容是切换后的路由对象,也就是跳转后的路由对象 =》路由B
  //第二个参数from,包含的内容的是切换前的路由对象,也就是跳转前的路由对象 =》路由A
}

独享守卫:beforeEnter

进入组件时,被调用

应用:单个组件权限控制


// 该文件专门用于穿件整个应用的路由器
import VueRouter from 'vue-router'
 
// 创建一个路由器
const router = new VueRouter({   routes:[
    {   
        name:'home',
        path:'/home',
        meta:{
            // 是否授权
            isAuth:true,
            title:'首页',
            permissionKey:'home',
        },
        component: () => import("@/pages/home"), // 组件路径
        beforeEnter:(to,from,next)=>{
              const {meta}  = to
            //  判断是否需要鉴定一下权限
            if(meta.isAuth){
            if(localStorage.getItem('permissionList' ).includes(meta.permissionKey)){
                   next()
               }else{
                   alert(`无权限查看${meta.title}`)
               }
           }else{
               next()
            }
        }

    }
]
})

组件内守卫:beforeRouteEnter,beforeRouteLeave

beforeRouteEnter:进入组件内被调用,

beforeRouteLeave离开组件时,应用:取消组件内一些操作

<template>
  <div>
    测试组件内守卫
  </div>
</template>
<script>
export default {
  data() {
    return {
        name:'这是一个组件'
    }
  },
  beforeRouteEnter(to, from, next) {
    next((vm) => {
      console.log("thisInBeforeRouteEnterNext", this); // undefined
      console.log("vm", vm);
      console.log("dataDataInBeforeRouteEnter  ", vm.name);
      console.log(vm.getName());
    });
  },
  beforeRouteLeave(to, from, next) {
    // 离开时
   
  },
  mounted(){
  },
  methods:{
    getName(){
      return `组件名字${this.name}`
    }
  },
}
</script>
<style scoped>

</style>
应用:菜单(路由)权限

设置系统内tab页签


// 该文件专门用于穿件整个应用的路由器
import VueRouter from 'vue-router'
 import store from '@/store';

// 创建一个路由器
const router = new VueRouter({   routes:[
    {   
        name:'home',
        path:'/home',
        meta:{
            // 是否授权
            isAuth:true,
            permissionKey:'home',
            title:'首页'
        },
        component: () => import("@/pages/home"), // 组件路径
    }
]
})

router.beforeEach((to,from,next)=>{
    const {meta}  = to
    //  判断是否需要鉴定一下权限
    if(meta.isAuth){
        if(localStorage.getItem('permissionList' ).includes(meta.permissionKey)){
            next()
        }else{
            alert(`无权限查看${meta.title}`)
        }
    }else{
        next()
    }
 
}) 
router.afterEach((to) => {
  store.commit('system/setPageTabs',to); // 把打开页面的放到页面标签数据中
})
export default router

vue2.0的数据双向绑定v-model

MVVM设计思想

  • M        数据模型 model         对应data
  • V         视图 view           对应模版
  • VM      视图模型            vue的实例对象

如何实现响应式:发布订阅模式+双向数据绑定=简单的响应式

原理:采用 数据劫持  结合 发布者-订阅者模式的方式 通过使用 Object.defineProperty()方法,劫持各个属性的 setter,getter, 在数据变动发布消息给订阅者,触发相应的监听回调来渲染视图

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模拟vue2.0 v-model</title>
</head>
<body>
    <h2>双向数据绑定</h2>
    <input type="text" id="username">
</br>
    显示值:<span id="nameInfo"></span>
    <script>
        let obj = {}
        Object.defineProperty(obj,'username',{
        // 劫持 username 属性
            get:function(){
                console.log('取值');
            },
            set:function(val){
                console.log('设置值');
                document.getElementById('nameInfo').innerText = val
            }
        })

        document.getElementById('username').addEventListener('keyup',function(){
            console.log(event);
            obj.username = event.target.value
        })
    
    </script>
    
</body>
</html>
//订阅.js


// 1.订阅器模型

let Dep = {
    clientlist:{},
    // 添加订阅
    listen:function(key,fn){
        // if(!this.clientlist[key]){
        //     this.clientlist=[]
        // }
        // this.clientlist[key].push(fn)


        // 以上代码的简写
        (this.clientlist[key]||(this.clientlist[key]=[])).push(fn)
    },
    // 推送
    trigger:function(){
        let key = Array.prototype.shift.call(arguments), // 处理方法参数
        fns = this.clientlist[key]
        if(!fns || fns.length===0){
            // 一个都没有订阅
            return false;
        }
        console.log('arguments',arguments);
        for(let i =0,fn; fn = fns[i++];){
            fn.apply(this,arguments)
        }

    }
}
let dataHi = function({data,tag,datakey,selector}){
    console.log(data,tag,datakey,selector);
    let value = '',
    el = document.querySelector(selector)
    Object.defineProperty(data,datakey,{
            get:function(){
               return value
            },
            set:function(val){
                value = val
                // 数据变化了!!!发布消息
                Dep.trigger(tag,value)
            }
    })
    // 订阅
    Dep.listen(tag,function(text){
        // 订阅回调
        el.innerText = text
    })
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue2.0数据双向绑定demo</title>
</head>
<body>
    <h3>订阅1</h3>
    <span class="one-box"></span>
    <h3>订阅2</h3>
    <span class="two-box"></span>
    <script src='./订阅.js'>  </script>
    <script>
        let dataObj = {}
        dataHi({
            data:dataObj,
            tag:'vue-1',
            datakey:'one',
            selector:'.one-box'
        }) 
        dataHi({
            data:dataObj,
            tag:'vue-2',
            datakey:'two',
            selector:'.two-box'
        }) 
        dataObj.one='订阅1的内容'
        dataObj.two='订阅2的内容'

    </script>
</body>
</html>

Vue3.0响应式--Proxy

基本数据类型,依然是get和set实现

复杂数据类型,使用了proxy。proxy也有get和set,但是更聪明。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue3.0响应式--proxy</title>
</head>
<body>
    <script>
        let obj = {
            name:'gyh',
            age:18,
        }
        let p = new Proxy(obj,{
            // target 数据源
            get(target,propName){
                console.log(`读取p的${propName}属性`);
                return target[propName]
            },
            // 修改/新增
            set(target,propName,value){
                console.log(`修改p的${propName}属性为:${value}`);
                target[propName] = value
            },
            deleteProperty(target,propName){
                console.log(`删除p的${propName}属性`);
                return delete target[propName]
            },
        })
    </script>
</body>
</html>

Vue2.0和Vue3.0Diff算法区别

Diff算法,把新旧虚拟DOM进行对比,找出变化的放转为DOM的操作

1、事件缓存,比如有点击事件的按钮,onclick会读缓存

2、Vue3添加了静态标记:Vue2 是全量 Diff,Vue3 是静态标记 + 非全量 Diff

        静态标记就是判断当前节点是否需要动态更新,比如:div1就是静态的,会加静态标记,在patch过程中,会跳过静态节点对比

<div id="app">
    <div id='div1'>沐华</div>
    <p id='p1'>{{ age }}</p>
</div>

3、静态提升:静态节点会保存起来,每次更新age,只创建动态内容,复用静态内容

4、使用最长递增子序列优化了对比流程

在 Vue2 里 updateChildren 会进行

  • 头和头比
  • 尾和尾比
  • 头和尾比
  • 尾和头比
  • 都没有命中的对比

在 Vue3 里 patchKeyedChildren 为

  • 头和头比
  • 尾和尾比
  • 基于最长递增子序列进行移动/添加/删除


    比如:

    老的 children:[ a, b, c, d, e, f, g ]
    新的 children:[ a, b, f, c, d, e, h, g ]
    1、先进行头和头比,发现不同就结束循环,得到 [ a, b ]
    2、再进行尾和尾比,发现不同就结束循环,得到 [ g ]
    3、再保存没有比较过的节点 [ f, c, d, e, h ],并通过 newIndexToOldIndexMap 拿到在数组里对应的下标,生成数组 [ 5, 2, 3, 4, -1 ],-1 是老数组里没有的就说明是新增
    4、然后再拿取出数组里的最长递增子序列,也就是 [ 2, 3, 4 ] 对应的节点 [ c, d, e ]
    5、然后只需要把其他剩余的节点,基于 [ c, d, e ] 的位置进行移动/新增/删除就可以了

    使用最长递增子序列可以最大程度的减少 DOM 的移动,达到最少的 DOM 操作,

vue按钮级别的权限,如何控制? 

  • 常用的v-if
  • 封装组件
  • 自定义指令
app.directive("auth", {
    mounted: (el, binding) => {
        const value = binding.value
        if (!value) return
        if (!hasPermission(value)) {
            // 挂载的时候没有权限把元素删除
            removeEl(el)
        }
    },
    updated(el, binding) {
        // 按钮权限码没有变化,不做处理
        if (binding.value === binding.oldValue) return
        // 判断用户本次和上次权限状态是否一样,一样也不用做处理
        let oldHasPermission = hasPermission(binding.oldValue)
        let newHasPermission = hasPermission(binding.value)
        if (oldHasPermission === newHasPermission) return
        // 如果变成有权限,那么把元素添加回来
        if (newHasPermission) {
            addEl(el)
        } else {
        // 如果变成没有权限,则把元素删除
            removeEl(el)
        }
    },
})

const hasPermission = (value) => {
    return [1, 2, 3].includes(value)
}

const removeEl = (el) => {
    // 在绑定元素上存储父级元素
    el._parentNode = el.parentNode
    // 在绑定元素上存储一个注释节点
    el._placeholderNode = document.createComment("auth")
    // 使用注释节点来占位
    el.parentNode?.replaceChild(el._placeholderNode, el)
}

const addEl = (el) => {
    // 替换掉给自己占位的注释节点
    el._parentNode?.replaceChild(el, el._placeholderNode)
}

Vue生命周期

  • 首先创建一个vue实例
  • Init Events Lifecycle:在beforeCreate之前,对vue的生命周期钩子函数和vue的一些内置事件(如事件修饰符所代表的事件,.once, .enter等),此时数据代理还没有开始
  • beforeCreate:此时data和methods都没有初始化,所以无法通过vue实例来访问data的数据和methods中的方法
  • Init injections reactivity:在created之前,初始化组件实例的依赖注入(init injections),就是对你在main.js中use的路由和vuex等一些第三方库,以便能够确保在组件创建和使用过程中正确地注入这些依赖项。
  • created:该阶段data和methods已经初始化了,可以通过vue实例来访问data的数据和methods中的方法,但整个页面没有挂载到页面上,还无法获取到el的值
  • el选项的判断:如果有el选项(options),则继续执行;如果没有,则等待vm.$mounted(el)这个函数被调用,直到调用了这个函数才继续执行。
  • template选项的判断:如果有template这个选项,则直接用这个template;如果没有,则去找el这个节点上的整个外部HTNL结构,包含

这个节点。

比如el:'#app'

<div id="app">

    <h1>Hello</h1>

    <h2>World</h2>

</div>

在判断阶段,此时开始解析模板,在内存中生成虚拟DOM,浏览器页面还不能显示解析好内容

  • beforeMounted:此时
    1. 页面呈现的是未经Vue编译的DOM结构。
    2. 所有对DOM的操作,最终都不奏效。
  • create vm.$el and replace "el" with it:创建vm.$el,然后将内存中的虚拟DOM转为真实DOM插入页面
  • mounted:此时,
    1. 页面中呈现的是经过Vue编译的DOM。
    2. 对DOM的操作均有效(尽可能避免)。至此初始化过程结束,一般在此进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件、等初始化操作。
  • beforeUpdate:此时:数据是新的,但页面是旧的,即:页面尚未和数据保持同步。
  • Virtual DOM re-render and patch:根据新数据复集成新的虚拟 DOM,随后与引旧的虚拟DOM进行比较,最终完成页面更新,即:完成了Model View的更新,也就是进行了diff算法。
  • updated: 此时:数据是新的,页面也是新的,即:页面和数据保持同步。
  • beforeDestroy:此时:vm中所有的:data、methods、指令等等,都做处于可用状态,马上要执行销毁过程,一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作
  • destroyed:当执行到destroyed函数时,组件已经被完全销吸,此时所有的数据、方法、指令、过滤器都不可用

 computedwatch有什么区别?

相同点:

  • 本质上都是一个watcher实例,它们都通过响应式系统与数据,页面建立通信,只是行为不同
  • 计算属性和监听属性对于新值和旧值一样的赋值操作,都不会做任何变化,不过这一点的实现是在响应式系统完成的。
  • 它们都是以Vue的依赖追踪机制为基础的

不同点:

  • 计算属性具有“懒计算”功能,只有依赖的值变化了,才允许重新计算,成为"缓存",感觉不够准确。
  • 在数据更新时,计算属性的dirty状态会立即改变,而监听属性与组件重新渲染,至少会在下一个"Tick"执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高高i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值