HTML部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/Mao.js"></script>
</head>
<body>
<div id="root">
<input type="button" M-bind:value="str" M-on:click="fn">
<input type="text" M-model="str">
<div M-html="str">{{str}}</div>
<div M-text="str">{{str}}</div>
<div>{{a}}</div>
</div>
</body>
<script>
new Mao({
el: "#root",
data: {
str: "今天是个好日子",
a: 1
},
methods: {
fn() {
this.str = "量变引起质变"
console.log(11111, this.str);
}
}
});
</script>
</html>
原生js部分 理解Vue原理
/*
* 数据:--》data-->methods
* 数据监听器:observer.对数据的属性进行监听,如果发生变化,会告诉发布者。
* 发布者:通知订阅者数据发生变了。(发布者会通知订阅者更新视图)
* 订阅者:视图元素订阅的数据信息。data下面的每一个数据,都会有多个订阅者。
* 订阅者的职责:当数据发生变化时,会将与数据相关的元素属性进行更新(更新视图)
* 订阅者与数据的关系是多对一的。
* 编译器:对HTML进行解析的,分析HTML,然后生成相对应的事件以及订阅者。compile
* watcher:数据监听器与编译器的一个桥梁。 实际是封装函数 优化代码
* */
function Mao(options) {
this.$options = options;
//获得挂载元素
if (options.el)
this.$el = document.querySelector(options.el);
//获取数据
if (options.data)
this.$data = options.data;
// $binds是一个对象。确定视图与数据之间的关系 。
// 里面的属性均是咱们的数据属性。属性的值是一个数组,数组的每一个元素即是一个订阅者
//$binds中 ---> 键-->this.$data中的键值-->相当于一个类型
//$binds中 ---> 值 是与每一个this.$data数据的变量str或a或b产生关联的元素更新数据的方法
// 当数据发生变化时-->执行方法-->更新视图
// <input type="button" M-bind:value="str" M-on:click="fn">
// <input type="text" M-model="str">
// <div M-html="str"></div>
//指令中 有str有多个元素--> 所以 $binds中 ---> 值-> 多对一 ->键 值是一个数组储存的方法
this.$binds = {};
//创建数据监视器 当数据发生变化时,告诉发布者 去通知订阅者更新视图
this.obsever();
//创建编译器 对HTML进行解析,生成相对应的事件以及订阅者
this.comfile();
}
Mao.prototype.obsever = function () {
// 声明_this的原因在56行;
let _this = this;
for (let key in this.$data) {
//初始化$binds 遍历 this.$data时,先初始化类型 方便储存方法
this.$binds[key] = [];
//储存变量,下面需要使用
let value = this.$data[key];
//拦截 当this.$data中数据改变会执行 set
//获取this.$data中数据 会执行 get 返回值
//当改变时 会执行set方法 在此改变数据的同时去通知订阅者更新视图
Object.defineProperty(this.$data, key, {
get() {
return value
},
set(v) {
value = v
//该this指向的是this.$data
// × this.$binds[key].forEach(v => v.update())
//$binds原因详解23-29行 update方法在编译器中声明创建
//this.$data中的数据发生变化时,通知所有订阅者更新视图
_this.$binds[key].forEach(v => v.update(false))
}
})
}
}
// 对HTML进行解析。分析HTML当中是否包含框架的指令或者其它标识{{}}
// 查看属性是否有框架当中的指令。(标签是否与DATA有关联,如果有关联,可以生成一个订阅者)
Mao.prototype.comfile = function () {
//获得挂载元素中的所有一级子元素
// *****该编译器只适用于一级子元素 如想任意嵌套实现指令,需用递归实现
let _this = this;
const nodes = this.$el.children;
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
//当有M-on:click指令存在时 增加一个点击事件
if (node.hasAttribute("M-on:click")) {
//获取事件名
const M_on = node.getAttribute("M-on:click")
//增加事件
//this.$options.methods[M_on]指向的是该node 需要改变this指向 指向this.$data
node.addEventListener("click", this.$options.methods[M_on].bind(this.$data))
}
//当有M-bind:value指令存在时 增加一个订阅者 当this.$data数据发生改变时 视图改变
if (node.hasAttribute("M-bind:value")) {
//获取数据
const M_bind = node.getAttribute("M-bind:value") //str
/*******************第一种写法****************/
//增加一个订阅者 $binds{str:[订阅者]}
// this.$binds[M_bind].push({
// //当数据发生变化时视图发生变化
// update() {
// node.value = _this.$data[M_bind] _this 73行 指向构造函数
// }
// })
/*******************第二种写法**********第三种写法在最后面******/
//因为 页面刷新的时候 绑定指令时 会直接显示 用这种方法比较好
const watcher = {
data: this.$data,
key: M_bind,
el: node,
prop: "value",
update() {
this.el[this.prop] = this.data[this.key]
}
}
this.$binds[M_bind].push(watcher)
watcher.update()
}
//当有M-model指令存在时 增加一个订阅者 实现双向绑定
if (node.hasAttribute("M-model")) {
const M_model = node.getAttribute("M-model")
// console.log(this.$data[M_model], this.$data)
const watcher = {
data: this.$data,
key: M_model,
el: node,
prop: "value",
update() {
this.el[this.prop] = this.data[this.key]
}
}
//增加一个订阅者
this.$binds[M_model].push(watcher);
// console.log(watcher.update());
watcher.update();
//视图改变 数据改变
node.oninput = function () {
_this.$data[M_model] = node.value
}
}
//当有M-html指令存在时 增加一个订阅者 实现双向绑定
if (node.hasAttribute("M-html")) {
const M_html = node.getAttribute("M-html");
const watcher = {
data: this.$data,
key: M_html,
el: node,
prop: "innerHTML",
update() {
/*解析双{{}}过程 此步可以省略*/
//把this.$data中的数据键值 转换成数组 方便遍历判断是否含有{{}}
let arrData = Object.keys(this.data)
let len = arrData.length
while (len--) {
//正则表达式 每次遍历 每次正则条件不同
const ele = new RegExp("{{" + arrData[len] + "}}")
// console.log(ele.test(this.el[this.prop]))
// 判断有没有{{}} 有就解析
if (ele.test(this.el[this.prop])) {
this.el[this.prop] = this.data[this.key]
}
}
//由于第一次有
this.el[this.prop] = this.data[this.key]
}
}
this.$binds[M_html].push(watcher);
watcher.update();
}
if (node.hasAttribute("M-text")) {
const M_text = node.getAttribute("M-text");
// const watcher = {
// data: this.$data,
// key: M_text,
// el: node,
// prop: "innerText",
// update() {
// //把this.$data的数据转成数组
// let arrData = Object.keys(this.data)
// let len = arrData.length
// while (len--) {
// const ele = new RegExp("{{" + arrData[len] + "}}")
// // 判断有没有{{}} 有就解析
// console.log(ele.test(this.el[this.prop]), this.el[this.prop])
// if (ele.test(this.el[this.prop])) {
// this.el[this.prop] = this.data[this.key]
// }
// }
// this.el[this.prop] = this.data[this.key]
// }
// }
//第三种写法法 详情看204-231行
const watcher = new Watcher(node, "innerText", this.$data, M_text)
this.$binds[M_text].push(watcher);
watcher.update();
}
//当没有指令时解析
for (let k in this.$data) {
const watcher = new Watcher(node, "innerText", this.$data, k)
watcher.update();
}
}
}
function Watcher(el, prop, data, key) {
this.el = el;
this.prop = prop;
this.data = data;
this.key = key;
}
//默认第一次时解析{{}} 默认为true
//因为只要第一次才有{{}} 后面解析完成是没有的
Watcher.prototype.update = function (flag = true) {
//把this.$data的数据转成数组
if (flag) {
let arrData = Object.keys(this.data)
let len = arrData.length
while (len--) {
const ele = new RegExp("{{" + arrData[len] + "}}")
//解析当前的数据
const nowData = arrData[len];
// 判断有没有{{}} 有就解析
console.log(ele.test(this.el[this.prop]), this.el[this.prop])
if (ele.test(this.el[this.prop])) {
this.el[this.prop] = this.data[nowData]
}
}
console.log(111)
} else {
this.el[this.prop] = this.data[this.key]
console.log(222)
}
}
-
总结:
-
1、实现一个数据监听器Observer,
对数据对象的所有属性进行监听,当数据发生变化时 通过Object.defineProperty方法同时所有订阅者去更新视图 -
2、实现一个HTML解析器Compile.
对每个元素进行解析,根据其相对应的属性,为其增加订阅者或绑定事件 -
3、watcher:生成订阅者–>构造函数 优化代码
双向绑定:通过数据监听器,以及HTML解析器再加上一个订阅者生成器来完成的。
在对数据监听时,可以根据数据的变化去通知相对应的数据订阅者,让你的订阅者更新视图。
当视图影响到数据时,数据会发生变化。当数据变化,再次通知订阅者。