江湖救急!vue双向绑定实现时出现只能获取第一个文本类型结点的问题

在fragment_compile获取node.nodeType === 3也就是text文本类型的节点时只能获取第一个span,而且后面也无法获取node.nodeType === 1的input结点,这是为什么呢?如下图只能打印出name
image.png

<!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>
阅读 951
1 个回答

vm.$el.appendChild(fragment)这句把DocumentFragment里的所有Node都放到vm.$el里了。后面的循环再找,肯定就没有了呀。多巩固一下基础知识吧。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题