前端面试:vue专场,原理讲解

549 阅读5分钟

游泳健身了解一下:github 和小伙伴一起搞的日常总结

mvvm?Vue响应式原理

mvvm ? (数据驱动视图)
首先我们看下都是什么意思
m(model):逻辑层
v(view):视图层
vm(viewmodel):逻辑视图层(用于和逻辑层和视图层的链接)
mvvm 是mvp升级后的产物,主要区别就是我们不需要再关注当前触发更新视图。

vue响应式原理?
原理就是mvvm,我们的数据驱动视图,主要由Object.defineproperty来进行一个劫持。
原理如下:

        let objectMvvm = {
            name: '张三',
            age: '18'
        }
        let reactiveProperty = function(obj,item,val) {
            observer(val)
            Object.defineProperty(obj,item,{
                get(){
                    return val
                },
                set(newVal){
                    if(newVal!==val){
                        val = newVal
                        console.log('我来更新视图')
                    }
                }
            })
        }
        let observer = function(obj) {
            if(typeof obj!=='object'||obj===null)return;
            for(let item in obj){
                reactiveProperty(obj,item,obj[item])
            }
        }
        observer(objectMvvm)

        objectMvvm.name = '李四'

我们可以看到当前的视图已经被更新了。主要是通过 defineProperty 的一个对象劫持
// vue3.0 中已经改成 proxy 来劫持(原因:1.对新增下标key 的不支持(比如数组),2.必须一次性递归导致性能开销很大)
我们这样回答,大佬肯定是不会满意的,
具体呢,observer 对对象进行劫持,compile 对当前需要对对象进行依赖收集,(Dep 和 watch)(发布订阅),
当observer 监听到当前对象对修改情况就是调用Dep下面的触发的方法(只对之前收集的依赖进行视图更新)
最后会贴上 vue 原理的一个简版的原理代码

上面只是到了update之前的环节,

update:
第一次进行 render 函数生成 VDom(虚拟dom),然后执行我们的patch($el,VDom)函数,得到最终VDom 然后页面渲染
对象值变更执行update(oldVDom,newVDom) 然后,进行diff算法对比,得到最终VDom 然后页面渲染

computed和watch的区别

watch(属性监听):对当前对prop 或者 data 里面对值进行监听,可以进行异步操作,性能开销比较大

computed(属性计算):会缓存之前对值,vue computed 会对当前对值进行添加,然后进行definproperty进行数据劫持
,如果当前对值是一个对象那么就会调用当前对应的getter方法 computed 并不能异步操作,通常我们需要缓存
,性能开销低的使用当前的computed

vue3.0为什么要用proxy

1.defineproperty不能对当前对象属性新增删除进行监听
2.defineproperty需要一次性对当前所有需要的值进行递归监听导致性能开销很大
3.proxy可以对当前对对象进行代理,可以对新增属性进行监听(proxy也不是全能的,只能对当前的层级进行监听,下一个层级就不行了)

nextTick原理

由于vue是异步渲染的(时时渲染性能开销太大)
当我们浏览器支持promise那么就使用promise进行异步渲染(或者MessageChannel(管道)),
否则我们则使用setTimeout来进行异步渲染,
主要就是宏任务和微任务之前轮转来进行的异步操作
当渲染成功我们就会调用nextTick方法

vue事件机制手写 on,on, emit, $off

vue事件总线还是通过发布订阅来实现的
class event {
    constructor(){
        this.teskList = {}
    }
    $on(tesk,fn){
        this.teskList[tesk] = []
        this.teskList[tesk].push(fn)
    }
    $emit(tesk,...arr){
        if(this.teskList[tesk]){
            this.teskList[tesk].forEach(fn=>{
                fn(...arr)
            })
        }
    }
    $off(tesk){
        delete this.teskList[tesk]
    }
}
let Event = new event()

Event.$on('xxx',function(x){
    Event.$off('xxx')
    // 没有了
    Event.$emit('xxx',1)
})
Event.$emit('xxx',1)

keep-alive原理

create 的时候初始化当前 cache 缓存对象
在render 获取当前对slot 下面组件name 或者是tag 
函数的时候对当前进行判断 
    不需要缓存:如不在include || 在exclude 里那么就直接返回VDOM 因为不需要存储
    需要缓存:如果当前 cache[name|tag] 里面存有当前的缓存就直接获取当前缓存 赋值给 componentInstance 
        否则就把当前的VDOM赋值给当前的 cache[name|tag]
    对当前VDOM的data的keepalive进行标记
    (componentInstance 在初始化的时候如果有值会对当前的VDOM进行覆盖)

watch 对当前的 include 和 exclude 进行监听,然后对当前的 slot 进行替换
    

$set 原理

this.$set(this.arr,1(key),1val))
$set为Array的时候,判断当前长度是否存在,如果不存在则覆盖key
然后则调用splice来进行一个数据操作 this.arr.splice(key,1,val),vue对当前的Array的部分的api进行了重写所以可以直接页面的重写渲染

this.$set(this.obj,1(key),1val))
$setobject的时候且当前的在当前的target的key存在,且不是Object的参数内
则可以直接进行赋值 this.obj[key] = val

如果当前还没有满足,则会判断当时是否有__ob__属性
    有__ob__则当前是响应数据 则要对当前对数据手动添加监听defineReactive(ob.value, key, val),手动调 ob.dep.notify()
    无__ob__则返回当前 this.obj[key] = val 及可 当前并不需要依赖收集

vue 如何实现异步渲染的

当我们的数据被改变的时候,会调用当前依赖收集的Dep.update方法 调用 queueWatcher 然后把当前的 watcher 存入
观察者队列 queue (不会重复推入,相同id 是不会被推入),然后执行 flushSchedulerQueue 方法来执行 当前
watcher.run() 来渲染页面 compile ,执行完毕后调用当前的 nextTick 的 callback 函数(有Promise就执行Promise
没有就调用setTimeout)(微任务和宏任务)

vue 路由 hash 和 history

hash 
	主要可以通过 /# 后面的值进行一个hash 的判断
    <a> a标签进行一个跳转
    window.addEventListener('hashchange') 来进行一个监听
   
history
	不常用,通过我们使用spa页面才会来进行使用
    history 通过 history.popState 进行接收
    history.pushState 来进行触发
    history.replace or history.go 也可以。主要是当前缓存的路径进行后退前进

依赖收集

只有在template模版里面被使用过的data里面的变量才会被依赖收集
	通过cpmile (解析)里面的new watcher 进行一个依赖收集,在 observe getter 
    里通过 Dep.target 是否存在来进行一个判断是否需要进行依赖收集 target 指的就是当前 data里面的变量

Array 为什么要进行重写

defineproperty 的一个bug,无法对新增加属性对一个监听,所以Array 的 pop,push,shift,splice 等方法
对长度进行一个增加不能进行一个有效的监听
我们对当前Array进行一个修改,对当前的数据进行一个_proto_ 指针的重定向实现的一个监听
下面:
	 let ArrayProType = Object.create(Array.prototype);
        ['pop','splice','shift','unshift','push'].forEach(item=>{
            ArrayProType[item] = function(){
                // 更新视图
                updateView()
                Array.prototype[item].call(this,...arguments)
            }
        })

        // observe
        if (Array.isArray(obj)){
            obj._protp__ = ArrayProType
        }

虚拟Dom

<div class="a" id="app"><span>123</span>456</div>

// vDom 就是类似是这样的 通过render 函数编译出来

{
	tag: 'div',
    className: 'a',
    attrs: {
    	id: 'app'
    },
    chidren: [
    	{
        	tag: 'span',
            text: '123'
        },
        {
        	text: '456'
        }
    ]
}

diff 简述diff过程

diff 算法
	1.只对同层级进行对比
    2.当tag不对直接进行替换
    3.当tag和key一样,默认当前是同一个

现在vue 2.x 用的是两端对比,两端进行一个对比直到不一样,然后剩下的进行一个循环对比,

patch

patch 有两种 patch(Ele,newVDom) or patch(oldVdom,newVDom)

patch(Ele,newVDom) 
	第一次会进行一个新的空VDom的创建,然后就会进行saveVdom
    
patch(oldVdom,newVDom)
	1.newVDom 是否存在children
    	oldVDom children 是 进行 updateChildren 进行一个 diff 对比
        oldVDom text 是 oldVDom 的text 赋值空 newVDom 进行一个 saveVdom的操作
        newVDom 空 oldVDom children 空 则 oldVDom 进行一个 removeVdom的操作
        newVDom 空 oldVDom text 空 则 text 赋值空
    2.newVDom 是text
    	oldVDom 是 children 则 oldVDom 进行一个 removeVdom的操作 newVDom 进行一个 赋值的操作
        oldVDom 是 text oldVDom 的text 赋值空 newVDom 进行一个 赋值的操作

模版编译过程 渲染过程(新增修改过程)

我们通常会vue-loader 预先编译好当前的render函数,执行render函数生成 Vdom然后进行patch函数
当我们修改当前变量,会执行当前的observe的setter方法然后调用render,在生成Vdom-> patch

Vue常见的性能优化

1.v-show 和 v-if 的使用优化
2.v-for 和 v-if 的使用优化
3.vue-loader的预编译
4.v-for添加key
5.computed会缓存的特性
6.$on 添加的方法需要进行销毁,window 的也需要销毁,定时器
7.keep-alive 的使用
8.data的层级不要太深

最后

求靠谱内推(北京地区)可以留言我 +。=