vue2常见原理分析

67 阅读4分钟

MVVM model view

mvvm与mvc最大的区别:

MVVM实现了view与model的自动同步,也就是model属性改变的时候, 我们不需要再自己手动操作dom元素去改变view的显示,而是改变属性后该属性对应的view层会自动改变。

1.响应式原理

发布订阅模式+数据劫持

简易版:
const Dep = {
    list: {}, //容器
    //添加订阅
    listen: function(key,fn) {
       if(!this.list[key]) {
           this.list[key] = []
       }
       this.list[key].push(fn);
    },
    //发布
    trigger: function() {
        let key = Array.prototype.shift.call(arguments),
            fns = this.list[key];
        if(!fns || fns.length === 0) {
            return false;
        }
        fns.forEach(fn=>{
            fn.apply(this, arguments);
        })
    }
}
//数据劫持
let initData = function({data, tag, dataKey, selector}) {
    let value = '',el = document.querySelector(selector);
    Object.defineProperty(data,dataKey,{
        get: function() {
            return value;
        },
        set: function(tag,val) {
            this.value = val;
            Dep.trigger(tag,val);
        }
    })
    Dep.listen(tag,(text)=>{
        text.innerHTML = text;
    })
}

let obj = {};
initData({
    data: obj,
    tag: 'view-1',
    dataKey: 'one',
    selector: '.box'
})
obj.one = '999';

Object.defineProperty的缺点:

  1. 深度监听需要递归到底,一次性计算量大
  2. 无法监听新增属性、删除属性(需要用Vue.set Vue.delete)
  3. 无法原生监听数组,需要特殊处理

Proxy的优点:

1.可以直接监听对象,不用递归遍历,提高了性能 2.可以监听数组

如何监听数组 vue重新定义了数组原型的方法

const arrPro = Object.create(Array.prototype);
['push','pop','shift','unshift','splice'].forEach(methname => {
    arrPro[methname] = function() {
        Array.prototype[methname].call(this, ...arguments)
    };
})

vue监听数组的变化

vue无法监听数组变化的场景:

1.通过数组索引改变数组元素的值 比如:this.list[2].checked = true;数据改变了视图没有更新

2.改变数组的长度

解决无法监听的方法:

1.this.$set(arr, index, newval);

2.通过splice(index, num, val);

3.使用临时变量作为中转,重新赋值数组

能监听数组变化的场景:

1.通过赋值的形式改变正在监听的数组;

2.通过splice/push

vue监听对象的变化:

vue无法监听到对象的场景:

1.增加/删除/修改无法被vue监听

解决方法:

1.this.$set()

2.Object.assign(),直接赋值的原理(推荐)

源码写法:

const Observe = function(data) {
    for(let key in data) {
        defineReactive(date, key);
    }
}
const defineReactive = function(obj, key) {
    const dep = new Dep();
    let val = obj[key];
    if(typeof val !== 'object') return fasle;
    Observe(val);
    Object.defineProperty(obj,key,{
        get() {
            dep.depend();
            return val;
        },
        set(newval) {
            dep.notify();
            val = newval;
        }
    })
}
const Watcher = function(vm, fn) {
    this.vm = vm;
    Dep.target = this;
    this.update = function(){
        fn();
    }
    this.value = fn;
    Dep.target = null;
}
const Dep = function() {
    this.target = null;
    this.subs = [];
    this.depend = function() {
        if(Dep.target) {
            this.addSub(Dep.target);
        }
    }
    this.addSub = function(watcher){
        this.subs.push(watcher);
    }
    this.notify = function() {
        for(let i=0;i<this.subs.length;i++) {
            this.subs[i].update();
        }
    }
}

2.diff算法

diff是一种算法,当数据发生变化是,虚拟dom就会变化,为了高效的比对虚拟dom,vue引入了diff算法,通过diff比对,将变化的地方更新在真实dom中。尽可能复用节点,减少dom的新增创建操作,提高性能。

diff的对比遵循深度优先,同层比较。首先从顶层比较,如果两者都是元素,那就判断两者是否有孩子,有则向下递归,比较过程中首先假设首尾节点可能相同,做四次对比尝试,如果没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点,这个过程中借用key可以更精确的找到相同节点,尽可能的复用节点减少dom操作从而使性能更高效

diff算法同层级比较,前前后后,前后 后前比较,为了尽可能复用减少dom操作

vue3.0 静态模板:vue2.0 diff算法后,相同节点复用之后会更新属性,即使是节点的属性是静态不变的也会更新;vue3.0模板分为静态模板和动态模板,:id='id'属于动态的,对于静态的不做更新,对于动态的才做更新。

详情

3.vue-router原理

vue的路有有两种模式mode:hash和history,hash是带有#的,默认hash模式;

hash模式:通过window.location.hash获取到当前url的hash;hash模式下通过hashchange方法可以监听url中hash的变化。

window.addEventListener("hashchange", function(){}, false)

特点:兼容性好,hash变化的时候会在浏览器history中添加一条记录,可实现浏览器的前进后退功能

history模式

它基于HTML5的history对象,通过location.pathname获取到当前url的路由地址;history模式下,通过pushStatereplaceState方法可以修改url地址,结合popstate方法监听url中路由的变化

history模式的特点是实现更加方便,可读性更强,同时因为没有了#,url也更加美观;

它的劣势也比较明显,当用户刷新或直接输入地址时会向服务器发送一个请求,所以history模式需要服务端同学进行支持,将路由都重定向到根路由

详情:juejin.cn/post/684490…

3.nextTick

一般是在数据改变了,视图没有按预期渲染,就会考虑是不是dom没有渲染完,就会考虑用nextTick,它的作用就是等dom渲染完再执行下一步操作。 nextTick主要是为了延迟回调,在下次dom更新结束之后执行延迟回调。在修改数据之后立即使用此方法,获得更新后的dom

兼容性问题处理:

if 浏览器支持Promise 就用Promise.then

else if 支持MutationObseve 就用支持MutationObseve

else if 支持setImmedate就用支持setImmedate就用

else 用setTimeout

4.keepalive原理

一个抽象组件,自身不会渲染一个DOM元素也不会出现在父组件链中,可以保存组件渲染状态。 原理:主要是用了缓存策略,将组件实例缓存到了this.cache对象里

github.com/febobo/web-…