将整个前端页面分成View,Controller,Modal,视图上发生变化,通过Controller(控件)将响应传入到Model(数据源),由数据源改变View上面的数据。
可以看到MVVM分别指View,Model,View-Model,View通过View-Model的DOM Listeners将事件绑定到Model上,而Model则通过Data Bindings来管理View中的数据,View-Model从中起到一个连接桥的作用。
小白一枚,一直使用的是React,想要多了解一些其它的框架,正好最近Vue越来越火热,Github上的Star数已经超过了React。而其背后蕴含的MVVM框架思想也一直跟React的组件化开发思想并驾齐驱,在这里也是本着兼收并蓄的思想,多了解一种开发模式。因此通过一些学习资料,写一些自己对MVVM开发思想的理解。
废话不多说,咱们进入正题。
MVVM框架理解
说起这个MVVM模型,就不得不说MVC框架。
将整个前端页面分成View,Controller,Modal,视图上发生变化,通过Controller(控件)将响应传入到Model(数据源),由数据源改变View上面的数据。
整个过程看起来是行云流水,业务逻辑放在Model当中,页面渲染逻辑放在View当中,但在实际运用上却存在一个问题:那就是MVC框架允许View和Model直接进行通信!!
换句话说,View和Model之间随着业务量的不断庞大,会出现蜘蛛网一样难以处理的依赖关系,完全背离了开发所应该遵循的“开放封闭原则”。
面对这个问题,MVVM框架就出现了,它与MVC框架的主要区别有两点:
1、实现数据与视图的分离
2、通过数据来驱动视图,开发者只需要关心数据变化,DOM操作被封装了。
可以看到MVVM分别指View,Model,View-Model,View通过View-Model的DOM Listeners将事件绑定到Model上,而Model则通过Data Bindings来管理View中的数据,View-Model从中起到一个连接桥的作用。
MVVM的实现原理:
MVVM的实现主要是三个核心点:
- 响应式:vue如何监听data的属性变化
- 模板解析:vue的模板是如何被解析的
- 渲染:vue模板是如何被渲染成HTML的
响应式:
var obj =
name: ‘zhangsan‘,
age: 25
当我们访问或修改obj的属性的时候,比如:
console.log(obj.name) //访问
obj.age = 22 //修改
但是这样的操作vue本身是没有办法感知到的,那么应该如何让vue知道我们进行了访问或是修改的操作呢?
那就要使用Object.defineProperty
var vm =
var data =
name: ‘zhangsan‘,
age: 20
var key, value
for (key in data)
(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)
通过Object.defineProperty将data里的每一个属性的访问与修改都变成了一个函数,在函数get和set中我们即可监听到data的属性发生了改变。
模板解析:
首先模板是什么?
模板本质上是一串字符串,它看起来和html的格式很相像,实际上有很大的区别,因为模板本身还带有逻辑运算,比如v-if,v-for等等,但它最后还是要转换为html来显示。
<div id="app">
<div>
<input v-model="title">
<button v-on:click="add">submit</button>
</div>
<div>
<ul>
<li v-for="item in list">item</li>
</ul>
</div>
</div>
模板在vue中必须转换为JS代码,原因在于:在前端环境下,只有JS才是一个图灵完备语言,才能实现逻辑运算,以及渲染为html页面。
这里就引出了vue中一个特别重要的函数——render
render函数中的核心就是with函数。
with函数将某个对象添加到作用域链的顶部,如果在 statement中有某个未使用命名空间的变量,跟作用域链中的某个属性同名,则这个变量将指向这个属性值。
var obj =
name: ‘zhangsan‘,
age: 20,
getAddress: function ()
alert(‘beijing‘)
function fn1()
with(obj)
alert(age)
alert(name)
getAddress()
fn1()
with将obj这个对象放在了自己函数的作用域链的顶部,当执行下列函数时,就会自动到obj这个对象去寻找同名的属性。
而在render函数中,with的用法是这样:
<div id="app">
<div>
<input v-model="title">
<button v-on:click="add">submit</button>
</div>
<div>
<ul>
<li v-for="item in list">item</li>
</ul>
</div>
</div>
with将obj这个对象放在了自己函数的作用域链的顶部,当执行下列函数时,就会自动到obj这个对象去寻找同名的属性。
而在render函数中,with的用法是这样:
<div id="app">
<div>
<input v-model="title">
<button v-on:click="add">submit</button>
</div>
<div>
<ul>
<li v-for="item in list">item</li>
</ul>
</div>
</div>
// 对应的js文件
var data =
title: ‘‘,
list: []
// 初始化 Vue 实例
var vm = new Vue(
el: ‘#app‘,
data: data,
methods:
add: function ()
this.list.push(this.title)
this.title = ‘‘
)
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))]))
)
]
)
]
)
如何将模板渲染为html
模板渲染为html分为两种情况,第一种是初次渲染的时候,第二种是渲染之后数据发生改变的时候,它们都需要调用updateComponent,其形式如下:
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())
首先读取当前的虚拟DOM——vm._vnode,判断其是否为空,若为空,则为初次渲染,将虚拟DOM全部渲染到所对应的容器当中(vm.$el),若不为空,则是数据发生了修改,通过响应式我们可以监听到这一情况,使用diff算法完成新旧对比并修改。