<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title>自定义MVVM框架,这是比较牛逼的v-text,v-model和数据绑定原理介绍</title>
</head> <body>
<div id="app">
<p v-text="message"></p>
<input type="text" v-model="message"/>
<p>{{message}}</p>
</div>
<script src="observer.js"></script>
<script src="TemepCompiler.js"></script>
<script src="watcher.js"></script>
<script src="mvvm.js"></script>
<script>
var vm = new MVVM({
el: "#app", //挂载视图
data: {
msg:'这是自己写的mvvm框架',
message:'mvvm出来吧'
} //定义数据
})
</script>
</body> </html>
//创建一个MVVM框架类
class MVVM { //类构造器(创造实例模板代码)
constructor(options) { //相当于函数的参数 {el:...,data:...}
//缓存重要属性
this.$vm = this;
this.$el = options.el;
this.$data = options.data; //判断视图是否存在
if(this.$el) {
//添加属性观察对象(实现属性挟持)
new Observer(this.$data);
//创建模板编译器,来解析视图
this.$compiler = new TemepCompiler(this.$el, this.$vm);
}
}
}
class TemepCompiler { //类构造器(创造实例模板代码)
constructor(el, vm) { //相当于函数的参数 (this.$el,this.$vm)
//缓存重要属性
this.vm = vm;
this.el = this.isElemetNode(el) ? el : document.querySelector(el);
//判断视图是否存在
if(this.el) {
//1.把模板内容放入内存(片段)
var fragment = this.node2fragment(this.el);
//2.解析模板
this.compile(fragment);
//3.把内存的结果返回页面
this.el.appendChild(fragment);
}
}
//工具方法
isElemetNode(node) {
return node.nodeType === 1 //1.元素节点 2.属性节点 3.文本节点
}
isTextNode(node) {
return node.nodeType === 3 //1.元素节点 2.属性节点 3.文本节点
}
toArray(arr) {
return [].slice.call(arr); //假数组转为数组;
}
isDerective(attrName) {
return attrName.indexOf("v-") >= 0; //判断属性名中是否有v-开头的属性
}
//核心方法(节省内存)把模板放入内存,等待解析
node2fragment(node) {
//创建内存片段
var fragment = document.createDocumentFragment(),
child;
//模板内容丢到内存
while(child = node.firstChild) {
fragment.appendChild(child);
}
//返回
return fragment;
}
compile(parentNode) {
//获取子节点
var childNodes = parentNode.childNodes, //类数组
compiler = this;
//遍历每一个节点
this.toArray(childNodes).forEach((node) => {
//判断节点类型
if(compiler.isElemetNode(node)) {
//1.属性节点(解析指令)
compiler.compileElement(node); } else {
//2.文本节点(解析指令)
var textReg = /\{\{(.+)\}\}/; //(\转义)文本表达式验证规则
var expr = node.textContent;
//var expr = node.innerText; //谷歌支持
//按照规则验证内容
if(textReg.test(expr)) {
expr = RegExp.$1 //缓存最近一次的正则里面的值;
//调用方法编译
compiler.compileText(node, expr); //如果还有子节点,继续解析;
}
}
});
}
//解析元素节点的指令的方法
compileElement(node) {
//获取当前元素节点的所有属性
var arrs = node.attributes;
self = this;
//遍历当前元素所有属性
this.toArray(arrs).forEach(attr => {
var attrName = attr.name;
//判断属性是否是指令
if(self.isDerective(attrName)) {
var type = attrName.substr(2); //v-text,v-model...
var expr = attr.value; //指令的值就是表达式
//找帮手
CompilerUntils[type](node, self.vm, expr);
}
})
}
//解析表达式的方法
compileText(node, expr) {
CompilerUntils.text(node, this.vm, expr);
} } //解析指令帮手
CompilerUntils = {
//解析text指令
text(node, vm, expr) {
//第一次观察
//1.找到更新规则对象的更新方法
var updaterFn = this.updater["textUpdater"];
//2.执行方法
updaterFn && updaterFn(node, vm.$data[expr]) //等价if(updaterFn){updaterFn(node,vm.$data[expr])} //第n+1次观察
new Watcher(vm, expr, (newVaule) => {
//触发订阅时按之前的规则对节点进行更新;v-model的也一样;
updaterFn && updaterFn(node, newVaule);
})
},
//解析model指令
model(node, vm, expr) {
//1.找到更新规则对象的更新方法
var updaterFn = this.updater["modelUpdater"];
//2.执行方法
updaterFn && updaterFn(node, vm.$data[expr]) //等价if(updaterFn){updaterFn(node,vm.$data[expr])} //第n+1次观察
new Watcher(vm, expr, (newVaule) => {
//触发订阅时按之前的规则对节点进行更新;v-model的也一样;
updaterFn && updaterFn(node, newVaule);
})
//视图到模型变化
node.addEventListener("input", (e) => {
var newValue = e.target.value;
//把值放进数据
console.log(newValue+'新值');
vm.$data[expr] = newValue;
})
},
updater: {
//文本更新方法
textUpdater(node, value) {
node.textContent;
//node.innerText; //谷歌支持
},
//输入框值更新方法
modelUpdater(node, value) {
node.value = value;
}
}
}
class Observer {
//构造函数
constructor(data) {
//提供一个解析方法,完成属性的分析,和挟持
this.observe(data);
}
observe(data) {
//判断数据的有效性 必须是对象
if(!data || typeof data !== "object") {
return;
}
var keys = Object.keys(data); //拿到所有的属性(key)转为数组
keys.forEach((key) => {
//重新定义key
this.defineReactive(data, key, data[key]); //动态属性需要中括号不能.
}) }
//针对当前对象属性的重新定义(挟持)
defineReactive(obj, key, val) {
var dep = new Dep();
//重新定义
Object.defineProperty(obj, key, {
enumerable: true, //是否可以遍历
configurable: false, //是否可以重新配置
get() { //getter取值
Dep.target && dep.addSub(Dep.target); //拿到订阅者
//返回属性
return val;
},
set(newValue) { //setter修改值
val = newValue; //新值覆盖旧值
dep.notify(); //通知,触发update操作;
} }) }
} //创建发布者
//1.管理订阅者
//2.通知
class Dep {
constructor() {
this.subs = [];
}
//添加订阅
addSub(sub) { //其实就是Watcher实例
this.subs.push(sub);
}
//集体通知
notify() {
this.subs.forEach((sub) => {
sub.update();
})
}
}
//定义一个订阅者
class Watcher {
//构造函数
//1.需要使用订阅功能的节点
//2.全局vm对象,用于获取数据
//3.需要使用订阅功能的节点
constructor(vm, expr, cb) {
//缓存重要属性
this.vm = vm;
this.expr = expr;
this.cb = cb; //缓存当前值
this.value = this.get();
} //获取当前值
get() {
//把当前订阅者添加到全局
Dep.target = this; //watcher实例
//获取当前值
var value = this.vm.$data[this.expr];
//清空全局
Dep.target = null;
//返回
return value;
}
//提供一个更新
update() {
//获取新值
var newValue = this.vm.$data[this.expr];
//获取老值
var oldValue = this.value;
//执行回调
if(newValue !== oldValue) {
this.cb(newValue); //效果一样关键需不需要改变this指向this.cb.call(this.vm,newValue)
}
} }

最新文章

  1. ios设备mdm的实现过程
  2. eclipse的包的加减号展开方式
  3. 将本地的新建的web Form页面放到服务器提示错误;
  4. 20160907_Redis问题
  5. python---str
  6. [转]Handy adb commands for Android
  7. iso socket基础2
  8. Python实现PLA(感知机)
  9. mysql_connect v/s mysql_pconnect
  10. MAX16054
  11. An existing PostgreSql installation has been found... 的解决
  12. nginx自动切割访问日志
  13. wiki 3143 二叉树的前序、中序及后序遍历
  14. 》》HTML5 移动页面自适应手机屏幕四类方法
  15. Centos7网络配置-转载
  16. [sharepoint]Rest api相关知识(转)
  17. 火热的线上APP的源码分享,开箱即用
  18. Github以及推广
  19. 【转】Zabbix 3.0 从入门到精通(zabbix使用详解)
  20. 20155228 实验三 敏捷开发与XP实践

热门文章

  1. 通过Toad工具查看dmp里面的表
  2. poj 2771 Guardian of Decency(最大独立数)
  3. codeforces 691C C. Exponential notation(科学计数法)
  4. fastText(二):微博短文本下fastText的应用(一)
  5. 将svn的项目转移到另外一个仓库中
  6. Ruby 类的创建
  7. ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 05.Controller 的路由
  8. Eclipse中,open declaration;open implementation;open super implementation的区别
  9. 博客图片失效?使用npm工具一次下载/替换所有失效的外链图片
  10. Codeforces - 706B - Interesting drink - 二分 - 简单dp