vue基本知识MVVM
MVVM 是vue的一大亮点,是学习vue必不可少的知识点,面试官们也钟情于此,手写简单的vueMVVM,笔记思路记录一下。
MVVM 设计模式,是由 MVC(最早来源于后端)、MVP 等设计模式进化而来,M - 数据模型(Model),VM - 视图模型(ViewModel),V - 视图层(View)。
在 MVC 模式中,除了 Model 和 View 层以外,其他所有的逻辑都在 Controller 中,Controller 负责显示页面、响应用户操作、网络请求及与 Model 的交互,随着业务的增加和产品的迭代,Controller 中的处理逻辑越来越多、越来越复杂,难以维护。为了更好的管理代码,为了更方便的扩展业务,必须要为 Controller “瘦身”,需要更清晰的将用户界面(UI)开发从应用程序的业务逻辑与行为中分离,MVVM 为此而生。
基于下图思路:
Observer:数据劫持,数据状态化抽象化,使之可响应;
Watcher:观察者,监测数据等的变化;
Dep:发布订阅,存储观察者;
一步步来~~~ 先写个简单的HTML
大体如下,自己补全啊,引入我们自己写的vueMVVM.js
// 简单HTML
<div id="app">
<input type="text" v-model="school.name">
<div>{{school.name}}</div>
<div>{{school.age}}</div>
{{getNewName}}
<ul>
<li>1</li>
<li>1</li>
</ul>
</div>
<!-- <script src="https://cdn.bootcss.com/vue/2.6.10/vue.common.dev.js"></script> -->
<script src="./vueMVVM.js"></script>
<script>
let vm = new Vue({
el:"#app",
data:{
school:{
name:"beida",
age:100
}
},
methods: {
},
computed: {
getNewName(){
return this.school.name + "666";
}
}
})
</script>
Compile编译模板
将DOM树遍历,节点化为文档碎片处理,元素节点和文本碎片分开处理
// 编译模板
class Compiler {
constructor(el, vm) {
this.el = this.isElementNode(el) ? el : document.querySelector(el)
this.vm = vm;
// 文档碎片,所有节点
let fragment = this.node2fragment(this.el);
// 在内存中编译所有节点
this.compile(fragment);
// 把编译好的节点重新渲染到页面
this.el.appendChild(fragment)
}
// 判断是否是指令
isDirective(attrName) {
return attrName.startsWith("v-")
}
// 编译元素节点
compileElement(node) {
let attributes = node.attributes;
[...attributes].forEach(attr => {
let {name,value: expr} = attr;
if (this.isDirective(name)) {
let [, directive] = name.split("-");
CompilerUtil[directive](node, expr, this.vm);
}
})
}
// 编译文本节点
compileText(node) {
let content = node.textContent;
let reg = /\{\{(.+?)\}\}/;
reg.test(content)
if (reg.test(content)) {
CompilerUtil['text'](node, content, this.vm)
}
}
// 编译
compile(node) {
let childNodes = node.childNodes;
// 遍历判断
[...childNodes].forEach(child => {
if (this.isElementNode(child)) {
// 是元素节点
this.compileElement(child)
// 元素节点中嵌套其他的元素节点或文本节点,回调
this.compile(child)
} else {
// 是文本节点
this.compileText(child)
}
})
}
// 文档碎片
node2fragment(node) {
let fragment = document.createDocumentFragment();
let firstChild;
while (firstChild = node.firstChild) {
fragment.appendChild(firstChild)
}
return fragment
}
isElementNode(node) {
return node.nodeType === 1;
}
}
Observer数据劫持
数据劫持,就是给对象添加get、set方法,增加响应式
// 实现数据响应式
class Observer {
constructor(data) {
this.observer(data)
}
observer(data) {
if (data && typeof data == 'object') {
for (let key in data) {
this.defindReactive(data, key, data[key])
}
}
}
defindReactive(obj, key, value) {
this.observer(value);
let dep = new Dep();
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.subs.push(Dep.target)
return value
},
set: (newVal) => {
if (newVal != value) {
this.observer(newVal)
value = newVal
dep.notify();
}
}
})
}
}
Watcher 观察者
存储了旧的值,当数据状态发生改变时,回调cb方法
// 观察者
class Watcher {
constructor(vm, expr, cb) {
this.vm = vm;
this.expr = expr;
this.cb = cb;
this.oldValue = this.get();
}
// 获取状态
get() {
Dep.target = this;
let value = CompilerUtil.getVal(this.vm, this.expr);
Dep.target = null;
return value;
}
// 状态改变时调用
update() {
let newVal = CompilerUtil.getVal(this.vm, this.expr);
if (newVal !== this.oldValue) {
this.cb(newVal);
}
}
}
Dep发布订阅
// 存储观察者
class Dep {
constructor() {
this.subs = [];
}
// 添加watcher
<!-- 虽然没有什么用,数据劫持的时候就做了 -->
addSub(watcher) {
this.subs.push(watcher)
}
// 通知容器中的所有观察者
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
验证MVVM
还需要补充一点代码
CompilerUtil包括处理指令等方法的对象 和必要的Vue类
// 包含处理指令方法的对象
CompilerUtil = {
getVal(vm, expr) {
return expr.split(".").reduce((data, current) => {
return data[current]
}, vm.$data)
},
setVal(vm, expr, value) {
expr.split(".").reduce((data, current, index, arr) => {
if (index == arr.length - 1) {
return data[current] = value
}
return data[current]
}, vm.$data)
},
model(node, expr, vm) {
let fn = this.updater["modelUpdater"]
new Watcher(vm, expr, (newVal) => {
fn(node, newVal)
})
node.addEventListener("input", (e) => {
let value = e.target.value
console.log(value)
this.setVal(vm, expr, value);
})
let value = this.getVal(vm, expr)
fn(node, value);
},
html() {
},
getContentValue(vm, expr) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getVal(vm, args[1])
})
},
text(node, expr, vm) {
let fn = this.updater["textUpdater"]
let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
new Watcher(vm, args[1], () => {
fn(node, this.getContentValue(vm, expr))
})
return this.getVal(vm, args[1])
})
fn(node, content);
},
updater: {
modelUpdater(node, value) {
node.value = value
},
htmlUpdater() {},
textUpdater(node, value) {
node.textContent = value
}
}
}
// Vue类
class Vue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
let computed = options.computed;
if (this.$el) {
new Observer(this.$data)
for (let key in computed) {
Object.defineProperty(this.$data, key, {
get: () => {
return computed[key].call(this)
}
})
}
this.proxyVm(this.$data)
new Compiler(this.$el, this)
}
}
// 代理data
proxyVm(data) {
for (let key in data) {
Object.defineProperty(this, key, {
get() {
return data[key]
}
})
}
}
}