【Vue】MVVM 模板解析 渲染

学习vue过程中的一些收获,以问题解答的形式总结:
问题一:使用jQuery和框架的区别
问题二:对MVVM的理解
问题三:如何实现MVVM
问题四:Vue中如何实现响应式
问题五:Vue中如何解析模板
问题六:Vue的整个实现流程

★使用jQuery和框架的区别
这个问题通过用jQuery和vue实现todolist demo来对比说明。
jQuery实现todolist :

<body>
<div>
    <input type="text" id="txt-title"/>
    <button id="btn-submit">submit</button>
</div>
<div>
    <ul id="ul-list"></ul>
</div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    var $txtTitle = $("#txt-title");
    var $btnSubmit = $("#btn-submit");
    var $ulList = $("#ul-list");
    $btnSubmit.click(function () {
        var title = $txtTitle.val();
        var $li = $('<li>' + title + '</li>');
        $ulList.append($li);
        $txtTitle.val("");
    })
</script>
</body>

vue实现todolist:

<template>
  <div id="app">
    <div>
      <input type="text" v-model="title">
      <button v-on:click="add">submit</button>
    </div>
    <ul>
      <li v-for="item in list">{{item}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'app',
  data () {
    return {
      title:"",
      list:[]
    }
  },
  methods:{
    add:function(){
      this.list.push(this.title);
      this.title="";
    }
  }
}
</script>

由以上demo的实现方式对比可以看出两者的区别:
1.vue数据和视图分离,以数据驱动视图
2.jQuery数据和视图并没有分离,数据没有驱动视图的修改
解释说明:
不难发现,在jQuery中,视图如下:
在这里插入图片描述
数据如下:
在这里插入图片描述
ul标签只是容器,并没有列表的视图,列表视图在数据部分,所以jQuery数据和视图没有分离。从数据层可以看出,添加列表修改视图的时候并不是通过数据驱动视图修改的,而是通过用最底层的API append直接操作视图实现的。
而在vue中,数据部分如下:
在这里插入图片描述
视图部分如下:
在这里插入图片描述
可以看出vue的数据和视图是分开的,vue通过给list数组push元素实现页面的渲染,即数据驱动视图显示的。
总结:
1.vue数据和视图的分离,以数据驱动视图,只关心数据变化,DOM操作被封装。解耦,不会牵一发而动全身,符合开放封闭原则
2.jQuery数据和视图没有分离,不是通过数据驱动视图的方式渲染的,而是直接进行DOM操作。
★对MVVM的理解
说到MVVM,必须提到MVC
在这里插入图片描述

  • M-Model 数据
  • V-View 视图、界面
  • C-Controller 控制器、逻辑处理
    一种情况:
    在这里插入图片描述
    举这种情况的例子:用户在界面View上点击按钮,触发按钮事件,Controller进行逻辑处理,修改Model数据,Model将修改的数据 同步到View层。
    还有另一种情况:
    在这里插入图片描述
    这种是用户直接在Controller修改Model数据,Model将修改了的数据同步到View界面。
    有时候这两种情况不能严格区分,这就是MVC。
    回顾上面用Vue写的todolist的demo,下面来看MVVM都对应哪块代码:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    下面看看M V VM三者之间的关系:
    在这里插入图片描述
    View通过事件绑定操作Model
    Model通过数据绑定操作View

ViewModel是将后端的MVC应用到前端,真正结合前端场景应用的创建,是一种微创新。ViewModel是连接View和Model的桥。

★如何实现MVVM
MVVM是Vue的框架,Vue是对MVVM框架的实现,所以说,MVVM的实现也就是Vue的实现。
首先,我们得知道Vue(MVVM)的三要素:

  1. 响应式:Vue如何监听到data属性的变化?
  2. 模板引擎:Vue的模板如何被解析?指令如何处理?
  3. 渲染:Vue的模板如何被渲染成html?它的渲染过程是怎样的?以及data属性变化的时候如何重新渲染。

掌握Vue的三要素及各要素之间的联系,即可实现MVVM结构,也就可以知道vue的整个实现流程。
来个总分总结构分析一下:
先说响应式,首先什么是响应式呢?
vue中的响应式就是修改data属性后,vue立刻就能监听到,立刻进行页面渲染。
在响应式这块需要知道:
1.vue如何监听data属性的变化
2.实现响应式的核心函数Object.defineProperty
3.data属性为啥被代理到vm(new的vue实例)上。

我们在获取和设置属性值的时候,直接就获取到了或者是设置成功了,那怎么样才能监听到呢?(只有监听到data属性变化了再渲染页面)这就需要Object.defineProperty函数了。
下面我们模拟实现vue如何监听到data属性的变化,以及data属性代理到vm上。

<script>
    var vm={};
    var data={
        name:"zhangsan",
        age:20
    }
    var key,value;
    for(key in data){
        //命中闭包,新建一个函数,保证key独立的作用域
        (function(key){
            Object.defineProperty(vm,key,{
                get:function(){
                    console.log("get",data[key]);
                    return data[key]
                },
                set:function(newVal){
                    console.log("set",newVal);
                    data[key]=newVal;
                }
            })
        })(key)
    }
    console.log(vm.name);         		//输出:get  zhangsan      zhangsan
    vm.age=21;                           //输出:set 21
</script>

以上代码实现了:监听到了data属性的变化,将data属性代理到了vm上
在模板解析这块,我们需要知道:
在vue代码中,模板是:

<template>
 <div id="app">
    <div>
      <input type="text" v-model="title">
      <button v-on:click="add">submit</button>
    </div>
    <div>
      <ul>
        <li v-for="item in list">{{item}}</li>
      </ul>
    </div>
  </div>
</template>

模板的本质其实是一串有逻辑(一些指令v-if,v-for)的字符串。与HTML结构很像,但有很大区别:vue模板是动态的,HTML模板是静态的。模板字符串最终必须转换成JS代码(render函数),原因有两点:a.模板中有逻辑,必须用JS来处理逻辑(JS是前端中唯一一个图灵完备的语言) b.要将模板渲染到页面上,必须用JS实现。

下面,看一下模板转换成的render函数(学习render函数必须会with的用法):
先看一个简单的例子:
在这里插入图片描述
模板对应解析出的render函数是:
在这里插入图片描述

下面看一下todolist demo中模板对应生成的render函数:

<div id="app">
    <div>
      <input type="text" v-model="title">
      <button v-on:click="add">submit</button>
    </div>
    <div>
      <ul>
        <li v-for="item in list">{{item}}</li>
      </ul>
    </div>
  </div>

生成的render函数是:

with(this){  // this 就是 vm
            return _c(
                'div',
                {
                    attrs:{"id":"app"}
                },
                [
                    _c(
                        'div',
                        [
                            _c(
                                'input',
                                {
                                    directives:[
                                        {
                                            name:"model",
                                            rawName:"v-model",
                                            value:(title),
                                            expression:"title"
                                        }
                                    ],
                                    domProps:{
                                        "value":(title)
                                    },
                                    on:{
                                        "input":function($event){
                                            if($event.target.composing)return;
                                            title=$event.target.value
                                        }
                                    }
                                }
                            ),
                            _v(" "),
                            _c(
                                'button',
                                {
                                    on:{
                                        "click":add
                                    }
                                },
                                [_v("submit")]
                            )
                        ]
                    ),
                    _v(" "),
                    _c('div',
                        [
                            _c(
                                'ul',
                                _l((list),function(item){return _c('li',[_v(_s(item))])})
                            )
                        ]
                    )
                ]
            )
        }
  • 模板中所有的信息都被包含在render函数中。
  • this即vm(因为render函数的with(this)指的是vm,所以要把data属性代理到vm上)
  • price即this.price,即this.price
  • _c即this._c,即vm._c
  • 模板中用到的data属性都变成了JS变量
  • 模板中的指令v-for v-model v-if都变成了JS逻辑
  • render函数返回的是vnode

render函数中的vm._c其实就是createElement函数,vm._v其实就是createTextVNode函数,vm._s其实就是toString函数,在控制台输出其实都可以看到。

大家肯定有疑问,在哪怎么样输出render函数:
答:在vue源码中搜索code.render,找到var code=generate(ast,options);
在它后面console.log(code.render);其输出的内容就是该模板对应的render函数。
大家肯定又会问道:那怎么生成render函数的呢?
答:从vue2.0开始支持预编译,编译打包后就将模板生成对应的render函数,已经工具化了。

从render函数中可以看到v-model v-on v-for等一些指令是怎么实现的:
get set就可以实现v-model,v-on通过在渲染button的时候给它绑定onclick事件add实现的,v-for是通过一个_l函数实现的,这个函数通过对数组list的遍历,生成li标签,将所有的li标签作为数组一块返回。

上面解决了模板中逻辑指令的处理的问题,接下来看模块生成html的问题(渲染)!!!

在模板渲染这块,首先看一个函数:

vm._update(vnode)
    {
        const prevVnode = vm._vnode;
        vm._vnode = vnode;
        if (!prevVnode) {
            vm.$el = vm._patch_(vm.$el, vnode);
        } else {
            vm.$el = vm._patch_(prevVnode, vnode);
        }
    }
    function updateComponent() {
        vm._update(vm._render())
    }

updateComponent()函数实现了vdom中的patch函数
updateComponent()函数在修改data属性的时候调用,即响应式的核心函数Object.defineProperty那块监听data属性值变化的时候调用。若是页面首次渲染,则执行vm.patch(container, vnode),若是data属性值发生变化,需要重新渲染界面,则执行vm.patch(vnode, newVnode);(用到了vdom中patch函数的两种用法)。

emmm 学完react会发现,vdom中的h函数,vue中的vm._c函数,还有react中的React.createElement函数是一样的

最后一个问题,vue的整个实现流程其实是对以上内容的联系总结,这个问题在下一个博客中总结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值