vue原理

作者: 吾名刘斩仙 | 来源:发表于2020-03-14 19:09 被阅读0次

    Vue原理

    未经允许 禁止转载

    MVVM 数据驱动视图

    传统组件只是静态渲染,更新还要依赖于操作DOM

    vue MVVM,数据驱动视图

    react setState,数据驱动视图

    MVVM:Modev-View-ViewModel

    1. Model:可以理解为vue里的data对象

    2. View:页面,DOM

    3. ViewModel:Vue层,处理一些DOM监听、指令操作等

    MVVM就是通过vue监听、指令操作等方法修改data进而渲染页面


    Vue响应式

    vue3.0之前

    核心API:Object.defineProperty

    Object.defineProperty基本用法:

    var data = {}
    var name = 'zhangsan'
    Object.defineProperty(data,'name',{
        get:function(){
            console.log('get')
            return name
        },
        set:function(newVal){
            console.log('set')
            name = newVal
        }
    })
    console.log(data.name)  //get zhangsan
    data.name = 'liu'       //set
    console.log(data.name)  //get liu
    

    功能稍完整的Object.defineProperty:

    //定义data
    const data = {
        name: 'zhangsan',
        age: 14,
        city:{      //需要深度监听
            id: 010,
            name: '北京'
        }
    }
    //监听函数
    function observer(target) {
        //判断传入对象参数是否为object
        if (typeof target !== 'object' || target == null) {
            return target
        }
        //对传入对象进行for in遍历
        for (let key in target) {
            //target--对象;key--对象属性;target[key]--属性值
            defineProperty(target, key, target[key])
        }
    }
    
    //obj.defineProperty
    function defineProperty(target, key, value) {
        //深度监听
        observer(value)
    
        Object.defineProperty(target, key, {
            get() {
                return value
            },
            set(newVal) {
                if (newVal !== value) {
                    //深度监听
                    observer(value)
    
                    value = newVal
                    update()
                }
            }
        })
    }
    
    //update
    function update() {
        console.log('update')
    }
    observer(data)
    data.name = 'liu'
    data.age = 15
    

    Object.defineProperty的缺点:

    1. 深度监听需要递归到底,一次性计算量大。
    2. 无法监听新增/删除属性。(需要用Vue.set/Vue.delete)

    虚拟DOM和diff算法

    1. vdom是实现vue和react的重要基石

    2. diff算法是vdom中最核心、最关键的部分

    用JS模拟DOM结构

    <!-- DOM -->
    <div class='container' id='div1'>
       <p>vdom</p>
       <ul style='font-size: 14px;'>
           <li>a</li>
       </ul>
    </div>
    
    
    
    //用JS模拟DOM
    {
       tag: 'div',
       props:{
           id: 'div1',
           className: 'container'
       },
       children: [
           {
               tag: 'p',
               children: 'vdom'
           },
           {
               tag: 'ul',
               props: {
                   style: 'font-size: 14px'
               },
               children: [
                   {
                       tag: 'li',
                       children: 'a'
                   }
               ]
           }
       ]
    }
    

    vue、react的vdom参考了snabbdom

    //snabbdom:
    var container = document.getElementById('container')
    var vnode = h(...)    snabbdom通过h方法模拟出DOM结构赋值给vnode
    patch(container,vnode)  //把vnode结构赋值给container
    

    snabbdom重点:1、h函数;2、vnode数据结构;3、patch函数

    vdom总结:

    1. 用JS模拟DOM结构(vnode)

    2. 新旧vnode进行对比,得出最小的更新范围,最后更新DOM

    3. 数据驱动视图的模式下,有效控制DOM操作

    diff算法

    树diff的时间复杂度O(n的3次方)

    1. 遍历tree1
    2. 遍历tree2
    3. 排序

    1000个节点要计算1亿次

    优化时间复杂度到O(n)

    1. 只比较同一层级,不跨级比较
    2. tag不相同,则直接删除重建,不再深度比较
    3. tag和key,两者都相同,则认为是相同节点,不再深度比较

    1000个节点只需计算1000次

    snabbdom源码解读

    1. h函数

    h函数一般接受3个参数:sel(元素标签)、data(元素属性)、children(子内容)。也可单独接受其中一两个参数。

    h函数返回执行一个vnode函数,参数包括sel,data,children,text(如果children为字符串,则用text显示该字符串),undefined

    2. vnode函数

    返回一个对象,包含sel、data、children、text、elm(该DOM节点)、key

    3. patch函数

    1. 接受的第一个参数为element||vnode,第二个参数为vnode。
    2. 执行pre hook生命周期。
    3. 判断第一个参数是否为vnode,不是的话(传入的第一个参数为一个DOM)则创建一个空vnode关联到这个DOM元素。

    4. sameVnode函数

    执行sameVnode判断两个vnode的key和ele是否都相等,相等的话则执行patchVnode函数,不相同则删除销毁旧的vnode,然后用新的vnode重建。

    5. patchVnode函数

    1. 执行prepatch hook生命周期钩子。
    2. 设置新vnode的ele,把旧vnode的ele赋给新的。
    3. 判断新旧children

    6. addVnodes函数

    有旧的children,没有新的,则添加vnode

    7. removeVnodes函数

    没有旧的children,有新的,则移除vnode

    8. updateVnode函数

    对比children,如开始和开始作对比,如果相同,则执行patchVnode函数,执行后index会进行累加或者累减,直到对比完成。

    如果几种对比方式(start-start,end-end,start-end,end-start)都未命中,则用key和sel进行对比,如果都相等则执行patchVnode函数。

    从这里可以看出v-for使用key的重要性,不使用key的话无法做对比,直接销毁旧的创建新的,另外key如果是随机数或者index则也无法对比。所以key是有必要写的且不能乱写

    diff算法总结

    1. patchVnode

    2. addVnodes removeVnodes

    3. updateChildren(key的重要性,可回答v-for为什么要有key:因为vdom的diff算法会对比select节点和key是否相同,相同则继续深入对比,不相同则重建,所以key是必要的)

    vdom和diff算法总结

    vdom核心概念很重要:h、vnode、patch、diff、key等。

    vdom存在价值更重要:数据驱动视图,控制DOM操作。


    模板编译

    with语法

    const obj = {
       a: 1,
       b: 2
    }
    console.log(obg.a)
    console.log(obg.b)
    console.log(obg.c)  //undefined
    
    
    //使用with语法,打破了作用域规则,能改变{}内自由变量的查找方式
    //将自由变量当做obj的属性来查找
    with(obj){
       console.log(a)  //1
       console.log(b)  //2
       console.log(c)  //报错
    }
    

    1.模板不是html,因为包含一些指令、插值,直接放在浏览器里是不能执行的

    2.html是标记性语言,只有js才能实现判断循环

    3.因此,模板一定是转换成js代码,即模板编译

    模板编译流程

    1. 模板编译为rander函数,执行rander函数返回vnode
    2. 基于vnode再执行patch和diff
    3. 使用webpack vue-loader,会在开发环境下编译模板

    使用rander代替template

    Vue.component('component',{
        rander: function(createElement){    
            return createElment(    //vnode
                'h'+this.level,
                [
                    createElement('a',{
                        attrs:{
                            name: 'headerId',
                            href: '#'+'headerId'
                        }
                    },'this is a tag')
                ]
            )
        }
    })
    

    模板编译总结

    1. with语法
    2. 模板到rander函数,再到vnode,再到渲染和更新
    3. vue组件可以使用rander替代template

    组件 渲染/更新过程

    涉及vue原理三大知识点:

    1.响应式:监听data属性 getter setter

    2.模板编译: 模板到rander函数 再到vnode

    3.vdom: patch(elm,vnode)和patch(vnode,newVnode)

    1. 初次渲染过程

    1.解析模板为rander函数(在webpack的vue-loader、vue-cli环境下已完成)

    2.触发响应式,监听data属性getter setter

    3.执行rander函数生成vnode,执行patch(elem,vnode)

    2. 更新过程

    1.修改data,触发setter(此前在getter已经被监听)

    2.重新执行rander函数生成newVnode,执行patch(vnode,newVnode)

    3. 异步渲染

    vue是异步渲染,修改data一次性提交,能提高性能

    $nextTick相关

    相关文章

      网友评论

          本文标题:vue原理

          本文链接:https://www.haomeiwen.com/subject/qbqnshtx.html