仿照Vue实现模板与数据的绑定

589 阅读3分钟
  1. 拿到模板

     let tmpNode = document.querySelector('#root');
    
  2. 拿到数据

    let data = {
        name:'张三',
        age:'24'
    }
    
  3. 将数据与模板结合

    使用递归实现
    
    匹配 {{ xxx }} 的正则
    let reg = /\{\{(.+?)\}}/g; // . 匹配除换行符以外的其它任意字符, () 组的意思,g 全局匹配
    
    function compiler(template,data) {
    	// 取出子节点
    	let childNodes = template.childNodes;
    	for(let i = 0;i<childNodes.length;i++) {
             // 获取节点的类型
             let type = childNodes[i].nodeType; // 1 元素节点 3 文本节点
             if(type === 3) { // 文本节点
                let txt = childNodes[i].nodeValue;
                txt.replace(reg,(_,a)=> {
        			console.log(_,a); // {{name}} {{age}}
        			let key = a.trim(); // 去除 a 左右的空格
        			childNodes[i].nodeValue = data[key];
        		})     
        	  } else if(type === 1) { // 元素节点,继续递归,获取元素节点中的文本节点
        		compiler(childNodes[i],data);    
               }
         } 
     }
    
  4. 放到页面中

    let generate = tmpNode.cloneNode(true); 
    		/** 
    			执行这一步的原因:
    				1、直接把页面中的内容给替换了,并没有保留原模版
    				2、直接替换会影响后期数据的双向绑定
    		*/ 
            /** 
            	 cloneNode()
            	 	 1、指定节点的精确拷贝,拷贝所有的属性和值
            	 	 2、如果参数为 true 递归复制节点的子节点,false(默认) 只复制当前节点
            */
            
            compiler(generate,data);
            
            /** 
            	放到页面当中 
                	root.parentNode ==> 获取到的是body中的标签
                	replaceChild ==> 用新节点替换某个子节点,注意只是替换,并不是把原来节点中的内容全部覆盖
            */
    
            root.parentNode.replaceChild(generate,root);
    
  5. 完整的代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>仿照vue实现</title>
    </head>
    <body>
        <!-- 编写页面模板 -->
        <div>123</div>
        <div id="root">
            <div>
                <div>
                    <span>{{name}}</span>
                </div>
                <p>{{age}}</p>
            </div>
            <p>{{ name }}</p>
            <p>{{ age }}</p>
        </div>
        <script>
            // 1 拿到模板
            let tmpNode = document.querySelector('#root');
            
            let reg = /\{\{(.+?)\}}/g;
    
            // 2 拿到数据
            let data = {
                name:'张三',
                age:'24'
            }
            // 3 将数据放到模板中(递归)
            // template 指DOM元素
            function compiler(template,data) {
                // 取出子节点
                let childNodes = template.childNodes;
                for(let i = 0;i<childNodes.length;i++) {
                    // 获取节点的类型
                    let type = childNodes[i].nodeType; // 1 元素节点 3 文本节点
                    if(type === 3) { // 文本节点
                       let txt = childNodes[i].nodeValue;
                       txt.replace(reg,(_,a)=> {
                           console.log(_,a); // {{name}} {{age}}
                           let key = a.trim(); // 去除 a 左右的空格
                           childNodes[i].nodeValue = data[key];
                       })     
                    } else if(type === 1) { // 元素节点,继续递归,获取元素节点中的文本节点
                        compiler(childNodes[i],data);    
                    }
                } 
            }
            
            let generate = tmpNode.cloneNode(true);
            console.log('1',tmpNode,generate);
            compiler(generate,data);
            console.log('2',generate);
            
    
            // 4 放到页面当中   
            root.parentNode.replaceChild(generate,root);
        </script>  
    </body>
    </html>
    
  6. 上面代码存在的问题

    1. Vue使用的是虚拟DOM,上面的操作使用的是真实的DOM元素

      将在下一篇文章将,将真实DOM转换为虚拟DOM

    2. 只考虑了单属性 {{ name }}, 没有考虑多属性 {{ name.firstName.secName }}

      function getValueByPath(obj,path) { // obj ==> data , path ==> {{ data.name.firstName.txt }}
                  // data.name.firstName.txt
                  let res = obj;
                  let paths = path.split('.'); // data.name.firstName.txt 分割为数组
                  let props;
                  
                  /*
                      shift、unshift、push
                          - unshift:向数组的开头添加一个或更多个元素,并返回新的长度 arr.unshift($1,$2,$3....)
                          - push: 向数组的尾部添加一个或更多个元素
                          - shift:把数组的第一个元素从其中删除,并返回第一个元素的值
                  */
                  while(props = paths.shift()) { // 数组.shift() 得到的是数组中的第一项
                      res = res[props]
                  }
                  return res;
              }
      
    3. 代码太凌乱没有整合

  7. 关键操作方法介绍

    • 正则

      • . 表示匹配除换行符以外的其它任意字符,

      • () 组的意思

      • g 全局匹配

      • replace()

        • let reg = /\{\{(.+?)\}}/g;
          let str = 'aa{{123}}b{{456}}c{789}'        
          str.replace(reg,(_)=> { // 回调函数中的第一个参数表示:正则匹配到的字符串 2:在使用组		匹配时,组匹配到的值 3:匹配值在原字符串中的索引 4:原字符串
                  
          })
          
    • 节点类型 nodeType

      • 1 => 元素节点
      • 3 => 文本节点
    • cloneNode()

      • 指定节点的精确拷贝,拷贝所有的属性和值
      • 如果参数为 true 递归复制节点的子节点,false(默认) 只复制当前节点

以上是分析vue源码中学到的,不喜勿喷😭,感觉不错🙂,点赞👍,收藏♥