https://github.com/william-xue/iwe7-design/tree/mvvc_like.
<!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>
My name is {{firstName + ' ' + lastName}}, I am {{age + ss}} years old.
</p>
<button id="button" onclick="la()">点击更新,验证监听是成功的</button>
</div>
<script type="text/javascript">
const observe = (data) => {
if (!data || typeof data !== 'object') {
return;
}
// 取出所有属性遍历,监听所有属性
for (let key in data) {
defineReactive(data, key, data[key]);
}
};
//利用Object.defineProperty监听数据变化
//一个defineReactive对应对象一个属性
const defineReactive = (data, key, val) => {
//一个dep管理一个对象的属性
//对象属性跟节点是一对多的关系
var dep = new Dep();
Object.defineProperty(data, key, {
get: function () {
if (target) {
//收集依赖
//为什么在这里收集?不在别处?
//数据与dom是一对多的关系。你数据遍历,跟踪完了。拿到dom上渲染。(暂且搁置虚拟dom,虚拟dom也就是加一层。只要渲染dom最好也免不了这步)
//那我怎么知道dom上你密密麻麻写那么多指令对应的说哪个数据。跟踪了js数据结构,怎么跟踪dom结构。并且保持一直上下文。?
//解决方法是:也就是在跟踪数据的时候,判断target....存在与否。target又是个什么鬼东西?
//见下文定义watch类。他是个什么鬼?他的构造器里,三句话。 target = this; this.node = node; this.data = data;这三句
//将此时此刻的this,装进去了data和node。
dep.addNode(target)
}
return val
},
set: function (newVal) {
val = newVal;
//触发变更
dep.notify();
}
});
}
//以上是循环遍历.实现observe相对简单。下边是实现观察者。watch是连接observer和compile的中间人,订阅发布模式。异常重要。
//Dep是对象属性对节点的管理器.Dep类的作用是,一个容器,一个装满了所有监听的数据节点。或者是绑定了v-***属性的容器。add是收集依赖,notify是接到observe通知后进行更新。
function Dep() {
this.nodes = []
}
Dep.prototype = {
addNode: function (node) {
this.nodes.push(node)
},
notify: function () {
//异步更新节点.为什么是settimeout?更新dom?这个要说的话 也是半天。先空着。简单的说 js 是一层,拿到数据放到dom又是一层。浏览器的处理机制,eventloop
setTimeout(() => {
this.nodes.map((node) => {
node.update()
})
})
}
}
//观察者 观察者观察者 观察者观察者 观察者观察者 观察者观察者 观察者观察者 观察者观察者 观察者
function Watcher(data, node) {
//target的作用是只在new Watcher的时候建立关系
//不然每次get都将节点压入dep
target = this;
console.dir(target);
//保存节点
this.node = node;
this.data = data;
//保存text节点的值
this.nodeValue = node.nodeValue;
//将对象的每一个属性都访问一遍,建立对象属性跟节点的关系
//异常重要。删掉无法运行。
console.log('..........get........')
for (let key in data) {
data[key]
}
//以上这句话看着像废话???
//是get.属性访问。是observe的get.目的是收集监听。dep.addNode(target).没有这句话,就没有了桥梁,整个项目就是0⃣️。
//下文还有一个赋值语句。看着也像是废话。他是set.没有他,整个项目也是0.
// for (let key in data) { data[key] = data[key] return }
//console.log('..........555.........')
//target = null;
}
// 模板解析模板解析模板解析模板解析模板解析
Watcher.prototype = {
//模版解析.init
execute: function (exp) {
return new Function(...Object.keys(this.data), `return ${exp}`)(...Object.values(this.data))
},
// new Function.(参数一,参数二,参数三四五六,最后一个参数是执行的方法体。) new Function(...Object.keys(this.data), `return ${exp}`)这步仅仅是实例化一个函数,
// 后,传入(...Object.values(this.data))作为参数,为调用。
// `return ${exp}` es6语法。标签字符串。一种新的调用函数的方式.http://es6.ruanyifeng.com/#docs/string#%E6%A8%A1%E6%9D%BF%E5%AD%97%E7%AC%A6%E4%B8%B2
//这也是为什么{{里边可以进行任何js表达式的原因}}
//更新view.此处与依赖收集的notify中的update()相关联。
update: function () {
//匹配到{{}}
const newValue = this.nodeValue.replace(/{{(.*?)}}/g, (exp) => {
//去除{{,}}
exp = exp.replace(/{{|}}/g, '')
return this.execute(exp)
//整个vue的模版语法各种正则,大致思路一致。在作用域里找key对应的value。
})
this.node.nodeValue = newValue;
}
}
//监听所有text节点
const watchTextNode = (el, data) => {
[...el.childNodes].forEach(child => {
// 递归直至都是text.也就是说只监听text。别的类型那么多,这里只是个最简单的也说明大体。
if (!(child instanceof Text)) {
watchTextNode(child, data)
}
//这里监听了所有的text节点.从根节点往下,有几个节点就有几个watch.而每一个watch 实例,参看定义,有node,有data,有init方法,有update方法,。他们都定义这里。那他们又在哪里调用尼?
//转到dep实例里。每个watch都push进里dep数组里。在什么时间点呢?在init数据,对所有数据进行observer的时候。
//为什么在彼时彼地进行调用?数据跟踪在进行的过程中,要确定好,我的这个数据是在view层的哪个dom节点使用。与更新。进而,在此时此地的上下文中进行调用,来更新js数据和dom.
else new Watcher(data, child)
})
}
const bindViewToData = (root, data) => {
//监听数据
observe(data);
//数据变化则更新对应的节点
watchTextNode(root, data);
console.log('..............s e t .....')
//先触发数据变化,更新view。赋值操作很重要。要不然不会触发行为
for (let key in data) {
data[key] = data[key]
return
}
}
let appData = {
firstName: 'Lucy',
lastName: 'Green',
age: 12222,
ss: 22
}
function la(){
appData.firstName='大头儿子';
setInterval(() => {
appData.age = +(new Date())
}, 500)
}
bindViewToData(document.getElementById('app'), appData)
</script>
</body>
</html>
复制代码
总共这么点代码。参照vue的实现方式,试图对vue的运行原理进行剖析。 三个类和两句html.每句都有注释。