<!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)
}
} }

手写MVVM框架 之vue双向数据绑定原理剖析的更多相关文章

  1. Vue双向数据绑定原理分析(转)

    add by zhj: 目前组里使用的是前端技术是jQuery + Bootstrap,后端使用的Django,Flask等,模板是在后端渲染的.前后端没有分离,这种做法有几个缺点 1. 模板一般是由 ...

  2. vue双向数据绑定原理探究(附demo)

    昨天被导师叫去研究了一下vue的双向数据绑定原理...本来以为原理的东西都非常高深,没想到vue的双向绑定真的很好理解啊...自己动手写了一个. 传送门 双向绑定的思想 双向数据绑定的思想就是数据层与 ...

  3. Vue双向数据绑定原理深度解析

    首先,什么是双向数据绑定?Vue是三大MVVM框架之一,数据绑定简单来说,就是当数据发生变化时,相应的视图会进行更新,当视图更新时,数据也会跟着变化. 在分析其原理和代码的时候,大家首先了解如下几个j ...

  4. 前端MVVM框架avalon揭秘 - 双向绑定原理

    avalon大家可能不熟悉,但是Knockout估计或多或少听过用过,那么说说KO的几个概念 监控属性(Observables)和依赖跟踪(Dependency tracking) 声明式绑定(Dec ...

  5. Vue双向数据绑定原理解析

    基本原理 Vue.采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,数据变动时发布消息给订阅者,触发相应函数的回调 ...

  6. Vue 双向数据绑定原理分析 以及 Object.defineproperty语法

    第三方精简版实现 https://github.com/luobotang/simply-vue Object.defineProperty 学习,打开控制台分别输入以下内容调试结果 userInfo ...

  7. vue 双向数据绑定原理

    博客地址: https://ainyi.com/8 采用defineProperty的两个方法get.set 示例 <!-- 表单 --> <input type="tex ...

  8. Vue双向数据绑定原理

    https://www.cnblogs.com/kidney/p/6052935.html?utm_source=gold_browser_extension

  9. angular和vue双向数据绑定

    angular和vue双向数据绑定的原理(重点是vue的双向绑定) 我在整理javascript高级程序设计的笔记的时候看到面向对象设计那章,讲到对象属性分为数据属性和访问器属性,我们平时用的js对象 ...

随机推荐

  1. C语言变量声明问题——变量定义一定要放在所有执行语句/语句块的最前面吗?

    报错信息:error C2065: 'salary' : undeclared identifier #include <stdio.h> void main(){ printf(&quo ...

  2. Intel® Media SDK Media Samples Linux 学习笔记(转)

    最近折腾intel media sdk,主要硬件平台是在HD4600的核显上进行测试,intel media sdk是intel提供的一种基于核显的硬件编解码的解决方案,之前已经有使用ffmpeg进行 ...

  3. OpenCV——PS滤镜算法之 Ellipsoid (凹陷)

    // define head function #ifndef PS_ALGORITHM_H_INCLUDED #define PS_ALGORITHM_H_INCLUDED #include < ...

  4. CodeForces979D:Kuro and GCD and XOR and SUM(Trie树&指针&Xor)

    Kuro is currently playing an educational game about numbers. The game focuses on the greatest common ...

  5. POJ3020 二分图匹配——最小路径覆盖

    Description The Global Aerial Research Centre has been allotted the task of building the fifth gener ...

  6. 嵌入式Linux学习方法——给那些彷徨者(上)

    要想学好嵌入式Linux,首先要解决两个重要问题: 1. 学什么? 2. 怎么学? 首先解决第一个问题. 嵌入式Linux的系统架构包括软件和硬件两个部分,如下图: 再来看看一个成熟的嵌入式产品的开发 ...

  7. 廖雪峰python3练习题二

    字符串和编码 题目: 答案: #!/usr/bin/env python3 #-*- coding:utf-8 -*- s1 = 72 s2 = 85 print('小明的成绩提高了%.1f%%个百分 ...

  8. ML一些零散记录

    朴素贝叶斯的假定条件:变量独立同分布 一般情况下,越复杂的系统,过拟合的可能性就越高,一般模型相对简单的话泛化能力会更好一点,增加隐层数可以降低网络误差(也有文献认为不一定能有效降低),提高精度,但也 ...

  9. 877C

    构造 想了好长时间... 答案是n+n/2 我们这么想,先把偶数位置炸一遍,所有坦克都在奇数位置,然后再把奇数炸一遍,坦克都到偶数去了,然后再炸一次偶数就都炸掉了... 好巧妙啊 奇偶讨论很重要 #i ...

  10. printf(“%06d\n”,x);

    %06d : %是格式化输入接受参数的标记 0格式化命令:结果将用零来填充 6:填充位数 d:代表十进制 数据 printf(“%06d\n”,x); console: 000001 000002 0 ...