在fragment_compile获取node.nodeType === 3也就是text文本类型的节点时只能获取第一个span,而且后面也无法获取node.nodeType === 1的input结点,这是为什么呢?如下图只能打印出name
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id='app'>
<span>姓名:{{name}}</span>
<input type="text" v-model='name'/>
<span>爱好:{{more.hobby}}</span>
<input type="text" v-model='more.hobby' />
</div>
</body>
<script>
class Vue{
constructor(obj_instance){
this.$data=obj_instance.data
//1.数据劫持
Observer(this.$data)
Compile(obj_instance.el,this)
}
}
function Observer(data_instance){
//递归出口
if(!data_instance || typeof data_instance !=='object')
return
const dependency=new Dependency()
Object.keys(data_instance).forEach((key)=>{
//使用Object.defineProperty后属性就被修改,如果直接在get里调用值为undefined,所以先保存value
let value=data_instance[key]
//递归的对属性嵌套的对象添加监听
Observer(value)
Object.defineProperty(data_instance,key,{
enumerable:true,
configurable:true,
get(){
console.log(`访问了属性:${key} ---> 值:${value}`)
//8.发布者收集订阅者
Dependency.temp &&dependency.addSub(Dependency.temp)
return value
},
set(newValue){
console.log(`修改了属性:${key} ---> 值:${newValue}`)
value=newValue
//如果对象属性被修改为新的对象,也要给新的对象属性们进行监听
Observer(newValue)
//9.属性修改赶紧告诉发布者,让发布者通知订阅者执行update操作
dependency.notify()
}
})
})
}
/*
*3.创建缓冲区将所有数据更新后再渲染页面
*/
function Compile(element,vm){
vm.$el=document.querySelector(element)
// console.log(vm.$el.childNodes)
//创建文档碎片
const fragment=document.createDocumentFragment()
let child
while((child=vm.$el.firstChild)){
// 将所有子节点摘下来放入文档碎片中,页面会变成空白因为createDocumentFragment是剪切过去的
fragment.appendChild(child)
}
// 4.替换文档碎片的值
fragment_compile(fragment)
/**
* 替换文档碎片内容
*/
function fragment_compile(node){
console.log(node)
const pattern=/\{\{\s*(\S+)\s*\}\}/
// 如果结点是文本,修改其中插值表达式的内容
if(node.nodeType === 3){
// console.log(node)
//保存模板字符串中的属性名称
const xxx=node.nodeValue
const result_regex=pattern.exec(node.nodeValue)
// console.log(node.nodeValue)
// console.log(result_regex)
if(result_regex){
//more.like搞成['more','like']
const arr=result_regex[1].split('.')
// console.log(arr)
//叠加为vm.$data[more][like]
const value=arr.reduce((total,cur)=>total[cur],vm.$data)
//实现插值表达式显示data内容,value还是初始没变的旧值
node.nodeValue=xxx.replace(pattern,value)
//7.创建订阅者,如果改变了值以后如何更新
new Watcher(vm,result_regex[1],newValue=>{
node.nodeValue=xxx.replace(pattern,newValue)
})
}
return
}
if(node.nodeType===1 && node.nodeName ==='INPUT'){
console.log(node)
const attr=node.attributes;
attr.forEach(i=>{
console.log(i)
if(i.nodeName === 'v-model'){
const value=i.nodeValue.split('.').reduce((total,cur)=>total[cur],vm.$data)
// Input赋值
node.value=value
//创建订阅者
new Watcher(vm,i.node,newValue=>{
node.value=newValue
})
node.addEventListener('input',e=>{
const arr1=i.nodeValue.split('.')
const arr2=arr1.split(0,arr1.length-1)
const final=arr2.reduce((total,cur)=>total[cur],vm.$data)
final[arr1][arr1.length-1]=e.target.value
})
}
})
}
//递归修改结点的子节点
node.childNodes.forEach(child=>{
fragment_compile(child)
})
vm.$el.appendChild(fragment)
}
}
/**
* 5.实现响应式更新值 创建发布者:收集和响应订阅者,让订阅者更新
*/
class Dependency{
constructor(){
this.subscribers=[]
}
addSub(sub){
this.subscribers.push(sub)
}
notify(){
this.subscribers.forEach(sub=>sub.update())
}
}
/**
* 6.创建订阅者类
* key属性名
* callback更新执行的回调函数
*/
class Watcher {
constructor(vm,key,callback){
this.vm=vm
this.key=key
this.callback=callback
// 临时属性
Dependency.temp=this
//访问属性能触发getter,例如more[like],为啥要触发是想要在get里执行dependy里添加订阅者,进行第8步
key.split('.').reduce((total,cur)=>total[cur],vm.$data)
// 因为getter会多次触发而一个属性只用订阅一次所以触发getter后让temp等于空,这样不会再add了
Dependency.temp=null
}
update(){
// console.log(this),this是触发的那个watcher
const value=this.key.split('.').reduce((total,cur)=>total[cur],this.vm.$data)
this.callback(value)
}
}
const vm=new Vue(
{
el:'#app',
data:{
name:'tom',
more:{
hobby:'唱歌'
}
}
}
)
</script>
</html>
vm.$el.appendChild(fragment)
这句把DocumentFragment
里的所有Node
都放到vm.$el
里了。后面的循环再找,肯定就没有了呀。多巩固一下基础知识吧。