<!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. cf 620C Pearls in a Row(贪心)

    d.有一串数字,要把这些数字分成若干连续的段,每段必须至少包含2个相同的数字,怎么分才能分的段数最多? 比如 是1 2 1 3 1 2 1 那么 答案是 21 34 7 即最多分在2段,第一段是1~3 ...

  2. hdu-5675 ztr loves math(数学)

    题目链接: ztr loves math  Time Limit: 2000/1000 MS (Java/Others)  Memory Limit: 65536/65536 K (Java/Othe ...

  3. maven(一)创建一个maven的web项目

    一.创建项目 1.Eclipse中用Maven创建项目 上图中Next 2.继续Next 3.选maven-archetype-webapp后,next 4.填写相应的信息,Packaged是默认创建 ...

  4. Top的VIRT是什么

    Top命令监控某个进程的资源占有情况  下面是各种内存: VIRT:virtual memory usage 1.进程“需要的”虚拟内存大小,包括进程使用的库.代码.数据等     2.假如进程申请1 ...

  5. ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 12. Views 下

    ASP.NET Core MVC 13. 安装前端库 Partial VIew 就是部分View,他没有自己的数据,数据来自图中白色的那块,它的数据需要传进去,第一个参数是View的名称,第二个参数就 ...

  6. easyui 动态添加标签页,总结

    步骤 1:创建 Tabs <div style="margin-bottom:10px"> <a href="#" class="e ...

  7. 【WEB基础】HTML & CSS 基础入门(3)段落及文本

    写在前面:CSS选择器 网页要显示很多内容,想要为每个内容设置不同的样式,我们就得首先选中要设置样式的内容,CSS选择器就是指明该样式是针对HTML里哪一个元素的.简单的例子,网页上有几段文字,我们想 ...

  8. 更新gitignore

    更新: 2017/04/26  修正windows版本下的命令   git rm -r --cached .       (Windows 下的版本) 更新: 2017/06/06  mac下的命令也 ...

  9. hdoj5328【尺取】

    现在在队内赛(灰常艾斯比的队内赛),还是来写篇题解开心一下,23333. 题意: 就是问你找出一个最长的等比数列或者等差数列 思路: 一个等差的尺取,一个等比的尺取.2333,就这么过了,具体自己写吧 ...

  10. MYSQL性能调优与架构设计之select count(*)的思考

    select count(*)的思考 原文:MYSQL性能调优与架构设计   举例: 这里我们就拿一个看上去很简单的功能来分析一下. 需求:一个论坛帖子总量的统计 附加要求:实时更新 在很多人看来,这 ...