vue原理

2020-03-14  本文已影响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相关

上一篇 下一篇

猜你喜欢

热点阅读