index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="./vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="app">
<h3>姓名是:{{ name }}</h3>
<h3>年龄是:{{ info.age }}</h3>
<input v-model="name" type="text"></div>
<script>
const vm = new Vue({
el: "#app",
data: {
name: "Ada",
info: {
age: "18",
gender: "女",
},
},
});
// console.log(vm.$data);
</script>
</body>
</html>
vue.js:
class Vue{
constructor(options){
this.$data = options.data
//调用数据劫持的方法
Observe(this.$data)
//属性代理
Object.keys(this.$data).forEach(key=>{
Object.defineProperty(this,key,{
enumerable:true,
configurable:true,
get(){
return this.$data[key]
},
set(newVal){
this.$data[key] = newVal
}
})
})
//调用模板编译的函数
Compile(options.el,this)
}
}
//定义一个数据劫持的方法
function Observe(obj){
//递归的终止条件
if(!obj||typeof obj!=='object') return
const dep = new Dep()
Object.keys(obj).forEach(key=>{
let value = obj[key]
//将value这个子节点进行递归
Observe(value)
//给当前key对应的属性添加setter和getter
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){
Dep.target && dep.addSub(Dep.target)
console.log(`有人获取了${key}的值`)
return value
},
set(newVal){
value = newVal
Observe(value)
//通知每一个订阅者更新自己的文本
dep.notify()
}
})
})
}
//对html结构进行模板编译的方法
function Compile(el,vm){
//获取el对应的dom元素
vm.$el = document.querySelector(el)
// 创建文档碎片提高DOM操作性能
//在内存中操作DOM元素再渲染到页面上,避免重绘重排,提高DOM渲染的性能
const fragment = document.createDocumentFragment()
while(childNode = vm.$el.firstChild){
fragment.appendChild(childNode)
}
//进行模板编译
replace(fragment)
vm.$el.appendChild(fragment)
//负责对DOM模板进行编译的方法
function replace(node){
//定义匹配插值表达式的正则
const resMustache = /\{\{\s*(\S+)\s*\}\}/
//证明当前的 node 节点是一个文本子节点,需要进行正则的替换
if(node.nodeType === 3){
//文本子节点也是一个DOM对象,如果要获取文本子节点字符串的内容,需要调用textContent属性获取
const text = node.textContent
//进行字符串的正则匹配与提取
const execResult = resMustache.exec(text)
if(execResult){
const value = execResult[1].split('.').reduce((newObj,k)=> newObj[k],vm)
node.textContent = text.replace(resMustache, value)
//创建watcher类的实例
new Watcher(vm,execResult[1],(newVal)=>{
node.textContent = text.replace(resMustache, newVal)
console.log(node.textContent)
})
}
//终止递归的条件
return
}
//判断当前的属性是否为input输入框
if(node.nodeType === 1 && node.tagName.toUpperCase() === 'INPUT'){
//得到当前元素所有的属性节点
const attrs = Array.from(node.attributes)
const findResult = attrs.find((x) => x.name === 'v-model')
// console.dir(findResult)
if(findResult){
//获取当前v-model属性的值
const expStr = findResult.value
const value = expStr.split('.').reduce((newVal,key)=>newVal[key],vm)
console.log(value)
node.value = value
//创建Watcher的实例
new Watcher(vm, expStr, (newVal)=>{
node.value = newVal
})
//监听文本框的input输入事件,拿到文本框最新的值,把最新的值更新到vm上
node.addEventListener('input',(e)=>{
const keyArr = expStr.split('.')
const obj = keyArr.slice(0,keyArr.length - 1).reduce((newVal,k)=>newVal[k],vm)
obj[keyArr[keyArr.length - 1]] = e.target.value
})
}
}
//证明不是文本节点,可能是一个DOM元素,需要进行递归处理
node.childNodes.forEach(child=>replace(child))
}
// replace(childNode)
}
//依赖收集的类/收集watcher订阅者的类
class Dep{
constructor(){
//所有的watcher都要存到这个数组中
this.sub = []
}
//向sub数组中添加watcher的方法
addSub(watcher){
this.sub.push(watcher)
}
//负责通知每个watcher的的方法
notify(){
this.sub.forEach(watcher => watcher.update())
}
}
class Watcher{
//cb回调函数中,记录着当前Watcher如何更新自己的文本内容
//仅仅知道如何更新自己还是不行,必须拿到最新的数据
//需要在new Watcher期间,把vm也传进来,因为vm中保存着最新的数据
//除此之外,还需要知道在众多的数据中,哪个数据才是当前自己需要的数据
// 因此,必须在new Watcher期间,指定watcher对应数据的名字
constructor(vm,key,cb){
this.vm = vm,
this.key = key,
this.cb = cb
//将创建的Watcher实例存到Dep实例的subs数组中
Dep.target = this
//这里会调用上面数据劫持的getter,将this传进去
key.split('.').reduce((newObj,k) => newObj[k],vm)
Dep.target = null
}
//watcher的实例需要有update函数,从而让发布者通知我们进行更新
update(){
const value = this.key.split('.').reduce((newObj,k) => newObj[k],this.vm)
this.cb(value)
console.log(this.key)
// this.cb
}
}