学习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)的三要素:
- 响应式:Vue如何监听到data属性的变化?
- 模板引擎:Vue的模板如何被解析?指令如何处理?
- 渲染: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的整个实现流程其实是对以上内容的联系总结,这个问题在下一个博客中总结。