如何实现一个MVVM
说一说我对于mvvm模型的理解吧
我第一次接触mvvm也是在学习vue的时候,在我看来vue和react都是数据驱动视图,但是vue属于标准的mvvm模型,react是从组件化演变而来
不多废话,直接粘图
第一次使用mvvm的时候感觉特别的神奇,我只是修改了数据就可以驱动视图的改变
- 学习mvvm模型的作用
一开始就是在学习vue的使用还有vuex等等很多,也能做一些小的网站,但就是没有办法提升自己的vue到一个更高的境界,后来就不断的往深了学习
听过网上的这么一句话
编程世界和武侠世界比较像,每一个入门的程序员,都幻想自己有朝一日,神功大成,青衣长剑,救民于水火之中,但是其实大部分的人一开始学习方式就错了,导致一直无法进入高手的行列,就是过于看中招式,武器,而忽略了内功的修炼,所以任你慕容复有百家武学,还有被我乔峰一招制敌,所以这就是内功差距
原理就是内功修炼的捷径
进入主题
- 原理
Object.defineProperty(obj,name,{get:function(),set:function()})
- 手写
mvvm主要分为两部
- kvue.js
- 获取数据,先获取options
- 把options.data的数据通过Object.key()解析
- 进入主题 Obejct.defineProprety() 进行双向绑定
- 接下来是两个类 Dep 和 Watcher (关系可以看上面的图片)
- compile.js
- 获取dom宿主节点 options.el
- 把宿主节点拿出来遍历,高效 createDocumentFragment()
- 编译过程 判断是否是文本节点,如果是文本节点就通过正则的分组获取到{{}}插值表达式中间的值
- 更新函数 初始化更新函数,调用Watcher
第一次写,怕说不明白,直接粘上代码
先创建目录结构
代码
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 插值绑定 -->
<p>{{name}}</p>
<!-- 指令解析 -->
<p k-text="name"></p>
<p>{{age}}</p>
<p>
{{doubleAge}}
</p>
<!-- 双向绑定 -->
<input type="text" k-model="name" />
<!-- 事件处理 -->
<button @click="changeName">呵呵</button>
<!-- html内容解析 -->
<div k-html="html"></div>
</div>
<script src="./compile.js"></script>
<script src="./kvue.js"></script>
<script>
const kaikeba = new KVue({
el: "#app",
data: {
name: "I am test.",
age: 12,
html: "<button>这是一个按钮</button>"
},
created() {
console.log("开始啦");
setTimeout(() => {
this.name = "我是测试";
}, 1500);
},
methods: {
changeName() {
this.name = "残梦a博客园";
this.age = 1;
}
}
});
</script>
</body>
</html>
- kvue.js
// new KVue({data:{...}})
class KVue {
constructor(options) {
this.$options = options;
// 数据响应化
this.$data = options.data;
this.observe(this.$data);
// 模拟一下watcher创建
// new Watcher();
// // 通过访问test属性触发get函数,添加依赖
// this.$data.test;
// new Watcher();
// this.$data.foo.bar;
new Compile(options.el, this);
// created执行
if (options.created) {
options.created.call(this);
}
}
observe(value) {
if (!value || typeof value !== "object") {
return;
}
// 遍历该对象
Object.keys(value).forEach(key => {
this.defineReactive(value, key, value[key]);
// 代理data中的属性到vue实例上
this.proxyData(key);
});
}
// 数据响应化
defineReactive(obj, key, val) {
this.observe(val); // 递归解决数据嵌套
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.addDep(Dep.target);
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
// console.log(`${key}属性更新了:${val}`);
dep.notify();
}
});
}
proxyData(key) {
Object.defineProperty(this, key, {
get(){
return this.$data[key]
},
set(newVal){
this.$data[key] = newVal;
}
})
}
}
// Dep:用来管理Watcher
class Dep {
constructor() {
// 这里存放若干依赖(watcher)
this.deps = [];
}
addDep(dep) {
this.deps.push(dep);
}
notify() {
this.deps.forEach(dep => dep.update());
}
}
// Watcher
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
// 将当前watcher实例指定到Dep静态属性target
Dep.target = this;
this.vm[this.key]; // 触发getter,添加依赖
Dep.target = null;
}
update() {
// console.log("属性更新了");
this.cb.call(this.vm, this.vm[this.key]);
}
}
- complie.js
// 用法 new Compile(el, vm)
class Compile {
constructor(el, vm) {
// 要遍历的宿主节点
this.$el = document.querySelector(el);
this.$vm = vm;
// 编译
if (this.$el) {
// 转换内部内容为片段Fragment
this.$fragment = this.node2Fragment(this.$el);
// 执行编译
this.compile(this.$fragment);
// 将编译完的html结果追加至$el
this.$el.appendChild(this.$fragment);
}
}
// 将宿主元素中代码片段拿出来遍历,这样做比较高效
node2Fragment(el) {
const frag = document.createDocumentFragment();
// 将el中所有子元素搬家至frag中
let child;
while ((child = el.firstChild)) {
frag.appendChild(child);
}
return frag;
}
// 编译过程
compile(el) {
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
// 类型判断
if (this.isElement(node)) {
// 元素
// console.log('编译元素'+node.nodeName);
// 查找k-,@,:
const nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(attr => {
const attrName = attr.name; //属性名
const exp = attr.value; // 属性值
if (this.isDirective(attrName)) {
// k-text
const dir = attrName.substring(2);
// 执行指令
this[dir] && this[dir](node, this.$vm, exp);
}
if (this.isEvent(attrName)) {
const dir = attrName.substring(1); // @click
this.eventHandler(node, this.$vm, exp, dir);
}
});
} else if (this.isInterpolation(node)) {
// 文本
// console.log('编译文本'+node.textContent);
this.compileText(node);
}
// 递归子节点
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node);
}
});
}
compileText(node) {
// console.log(RegExp.$1);
this.update(node, this.$vm, RegExp.$1, "text");
}
// 更新函数
update(node, vm, exp, dir) {
const updaterFn = this[dir + "Updater"];
// 初始化
updaterFn && updaterFn(node, vm[exp]);
// 依赖收集
new Watcher(vm, exp, function(value) {
updaterFn && updaterFn(node, value);
});
}
text(node, vm, exp) {
this.update(node, vm, exp, "text");
}
// 双绑
model(node, vm, exp) {
// 指定input的value属性
this.update(node, vm, exp, "model");
// 视图对模型响应
node.addEventListener("input", e => {
vm[exp] = e.target.value;
});
}
modelUpdater(node, value) {
node.value = value;
}
html(node, vm, exp) {
this.update(node, vm, exp, "html");
}
htmlUpdater(node, value) {
node.innerHTML = value;
}
textUpdater(node, value) {
node.textContent = value;
}
// 事件处理器
eventHandler(node, vm, exp, dir) {
// @click="onClick"
let fn = vm.$options.methods && vm.$options.methods[exp];
if (dir && fn) {
node.addEventListener(dir, fn.bind(vm));
}
}
isDirective(attr) {
return attr.indexOf("k-") == 0;
}
isEvent(attr) {
return attr.indexOf("@") == 0;
}
isElement(node) {
return node.nodeType === 1;
}
// 插值文本
isInterpolation(node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
}
如何实现一个MVVM的更多相关文章
- 手写一个MVVM
最近看了珠峰的架构课——实现一个MVVM. 首先,我们来了解一下什么是MVVM. MVVM是Model-View-ViewModel的简写.它本质上就是MVC 的改进版.MVVM 就是将其中的View ...
- 剖析手写Vue,你也可以手写一个MVVM框架
剖析手写Vue,你也可以手写一个MVVM框架# 邮箱:563995050@qq.com github: https://github.com/xiaoqiuxiong 作者:肖秋雄(eddy) 温馨提 ...
- 自己动手实现一个MVVM库
我们知道的,常见的数据绑定的实现方法 1.数据劫持(vue):通过Object.defineProperty() 去劫持数据每个属性对应的getter和setter2.脏值检测(angular):通过 ...
- MVVM模式的一个小例子
使用SilverLight.WPF也有很长时间了,但是知道Binding.Command的基本用法,对于原理性的东西,一直没有深究.如果让我自己建一个MVVM模式的项目,感觉还是无从下手,最近写了一个 ...
- 实现一个类 Vue 的 MVVM 框架
Vue 一个 MVVM 框架.一个响应式的组件系统,通过把页面抽象成一个个组件来增加复用性.降低复杂性 主要特色就是数据操纵视图变化,一旦数据变化自动更新所有关联组件~ 所以它的一大特性就是一个数据响 ...
- 不要听吹牛逼什么前端MVVM框架就是好,其实都是一帮没学好分层设计的搞出来的,让你彻底看清前端MVVM的本质
最近前端圈子里面,发现大家都在热炒概念,什么knockout,angularJs,都被捧成神了,鄙人不才,最近心情也不好,特地写这篇文章来找骂 写代码的码农都知道,Java社区虽然不是一个提出分层思想 ...
- 1000行代码实现MVVM (类似Angular1.x.x , Vue)
最近花了近半个多月的时间, 自己纯手工写了一个很小型的类angularjs/vue的mvvm 库. 目前已经用于公司一个项目. 项目托管在github https://github.com/leonw ...
- 转:界面之下:还原真实的 MVC、MVP、MVVM 模式
前言 做客户端开发.前端开发对MVC.MVP.MVVM这些名词不了解也应该大致听过,都是为了解决图形界面应用程序复杂性管理问题而产生的应用架构模式.网上很多文章关于这方面的讨论比较杂乱,各种MV*模式 ...
- MVVM简介
如果你对MVVM的概念还是不了解,可以参看下面链接:http://baike.baidu.com/view/3507915.htm 我们以WPF+MVVM的本地桌面程序为背景,这样一来我们可以不去操心 ...
随机推荐
- 无意间做了个 web 版的 JVM 监控端(前后端分离 React+Spring Boot)
之前写了JConsole.VisualVM 依赖的 JMX 技术,然后放出了一个用纯 JMX 实现的 web 版本的 JConsole 的截图,今天源码来了. 本来就是为了更多的了解 JMX,第一步就 ...
- 洛谷P1613 跑路
题目描述 小A的工作不仅繁琐,更有苛刻的规定,要求小A每天早上在6:00之前到达公司,否则这个月工资清零.可是小A偏偏又有赖床的坏毛病.于是为了保住自己的工资,小A买了一个十分牛B的空间跑路器,每秒钟 ...
- Mac搭建 Eclipse +Pydev+Python 环境
Mac配置Python开发环境(Eclipse +Pydev+Python) 1.首先下载MAC版的64位Eclipse. eclips下载地址打开链接,选择需要的版本下载 2.下载Python. M ...
- Linux C++轻量级开发指南
作为一名产品开发,一套顺手的集成开发环境必不可少.大多数时候,开发经理将精力投入在产品需求分析上而忽视了研发质量甚至连基本的集成开发环境都没有统一.当然,如果你们的项目仅仅需要部署在单一的环境中或没有 ...
- Centos 7.2天兔(Lepus 3.8)数据库监控系统部署
天兔(Lepus 3.8)数据库监控系统部署 转载自:https://blog.csdn.net/m0_38039437/article/details/79613260 一.安装LAMP基础环境 首 ...
- 流包装器实现WebShell免杀
说明: 本文首发自 https://www.secpulse.com/archives/73391.html ,转载请注明出处. 前言 本文是看PHP使用流包装器实现WebShell有感,权当做个笔记 ...
- Oier们的幸运数字
题目描述 JerryC对数字痴迷到了一种非正常的境界.每天JerryC都有喜欢的一些数字.第 iii 天JerryC就喜欢Ai−BiA_i-B_iAi−Bi中的数字.但是他觉得这样并不是很有趣,于 ...
- 安装Go语言及搭建Go语言开发环境
一步一步,从零搭建Go语言开发环境. 安装Go语言及搭建Go语言开发环境 下载 下载地址 Go官网下载地址:https://golang.org/dl/ Go官方镜像站(推荐):https://gol ...
- Qt 表格的使用
参考 http://doc.qt.io/qt-5/qtablewidget.html http://doc.qt.io/qt-5/qtablewidgetitem.html https://blog. ...
- 百万年薪python之路 -- MySQL数据库之 MySQL行(记录)的操作(二) -- 多表查询
MySQL行(记录)的操作(二) -- 多表查询 数据的准备 #建表 create table department( id int, name varchar(20) ); create table ...