• vue源码解析之,插值替换


    通过一个小例子 看一下,vue做了些什么?

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="../node_modules/vue/dist/vue.js"></script>
    </head>
    <body>
        <div id="root">
            <p>{{name}}</p>
            <p>{{message}}</p>
        </div>
        <div id="a"></div>
    
        <script>
            console.log(root);
            let app = new Vue({
                el:"#root",
                data:{
                    name:"哈哈",
                    message:"是一个男人"
                }
            })
            console.log(root);
        </script>
    </body>
    </html>

     下面我们模仿一下vue去实现一下,插值替换的步骤:

    <!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="root">
            <div>
                <div>
                    <p>{{name}}--{{message}}</p>
                </div>
            </div>
            <p>{{name}}</p>
            <p>{{message}}</p>
        </div>
    
        <script>
            let rkuohao = /{{(.+?)}}/g;//匹配双括号的正则  .+?  两面加小括号 目的是 通过正则可以把它取出来
            /**
             * 步骤拆解
             * 1、拿到模板
             * 2、拿到数据data
             * 3、将数据与模板结合,得到的是html元素(DOM元素)
             * 4、放到页面当中
            */
            // 第1步
            let tmpNode = document.querySelector("#root");
            // 第2步
            let data = {
                name:"姓名",
                message:"是一个男人"
            }
            // 第3步
            // 递归的方法去找到需要替换的节点
            // 在现在的案例中 template 是dom元素,
            // 在真正的vue源码中是DOM-》字符串-》虚拟DOM(vnode)-》真正的DOM
            function compiler( template,data ){
                let childNodes = template.childNodes;//取出子元素
                console.log(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 = txt.replace(rkuohao,function(_,g1){
                            let key = g1.trim();
                            let value = data[key];
                            // 将{{ xxx }} 替换成value
                            return value;
                        })
                        // 注意 现在这个txt和DOM元素是没有关系的
                        childNodes[i].nodeValue = txt;
                    }else if(type === 1){
                        // 是元素,需要判断是都有子元素,判断是否要插值
                        compiler(childNodes[i],data);//递归
                    }
                }
            }
            console.log(tmpNode);//此时 插值还未被替换
            let generatorNode = tmpNode.cloneNode(true);//拷贝一份模板   这里是DOM元素可以通过这样的方法 拷贝
            compiler( generatorNode,data );//调用一下
            console.log(generatorNode);//此时 模板中的 插值就被替换成真正的值了
            // 我们此时是没有生成新的template,所以这里看到的,是直接在页面中就更新了数据,因为DOM是引用类型
            // 这样做模板就没有了
            // 第4步
            root.parentNode.replaceChild(generatorNode,root);//替换子元素   用新的元素去替换旧的元素
    
            // 上面的案例是一个极简的 例子 存在很大的问题
            // 1、vue使用的是虚拟DOM
            // 2、上面只考虑到了单属性 {{name}}  在真正的vue中会有很多这样的写法  {{child.name.firstName}}
            // 3、代码没有整合  vue是用构造函数的形式整合的
        </script>
    </body>
    </html>

    上面的小例子 以极简的方式 模仿vue 实现了 插值替换,代码显得有点low,我们整合一下,也模仿vue用构造函数的方式整合

    <!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="root">
            <div>
                <div>
                    <p>{{name}}--{{message}}</p>
                </div>
            </div>
            <p>{{name}}</p>
            <p>{{message}}</p>
        </div>
    
        <script>
            let rkuohao = /{{(.+?)}}/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 = txt.replace(rkuohao,function(_,g1){
                            let key = g1.trim();
                            let value = data[key];
                            // 将{{ xxx }} 替换成value
                            return value;
                        })
                        // 注意 现在这个txt和DOM元素是没有关系的
                        childNodes[i].nodeValue = txt;
                    }else if(type === 1){
                        // 是元素,需要判断是都有子元素,判断是否要插值
                        compiler(childNodes[i],data);//递归
                    }
                }
            }
            function JGVue( options ){
                // 习惯:内部的数据使用下划线开头  ,只读数据使用$开头
                this._data = options.data;
                this._el = options.el;
                // 准备工作 (准备模板)
                this.$el = this._templateDOM = document.querySelector( this._el );
                this.parentNode = this._templateDOM.parentNode;//存一下 模板的父级元素
    
                // 渲染工作 
                this._render();
            }
            // 在原型中 加上_render方法
            // 作用  将模板结合数据 得到HTML 加到页面中
            JGVue.prototype._render = function(){
                this.compiler();
            }
            /**编译  将模板与数据结合 得到真正的 DOM 元素 */
            JGVue.prototype.compiler = function(){
                let realHTMLDOM = this._templateDOM.cloneNode( true );//用模板拷贝 得到一个DOM
                compiler(realHTMLDOM, this._data);
                this.update( realHTMLDOM );
            }
            /** 将DOM元素 放到页面中 */
            JGVue.prototype.update = function( real ){
                this.parentNode.replaceChild(real,document.querySelector("#root"));//将父元素的 新的子元素替换成 旧的 子元素
    
    
            }
            // 想想怎么用
            let app = new JGVue({
                el:"#root",
                data:{
                    name:"哈哈",
                    message:"时隔男人"
                }
            })
        </script>
    </body>
    </html>

     接下来处理路径 访问对象成员   也就是在 双括号中可以出现 xxx.xxx.xxx的形式的值

    看红色部分的更改

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="../node_modules/vue/dist/vue.js"></script>
    </head>
    <body>
        <div id="root">
            <p>{{name.firstName}}{{name.lastName}}{{age}}</p>
        </div>
    
        <script>
            // 要解决一个问题  使用xxx.xxx.xxx可以访问一个对象
            // 用字符串路径来访问对象的成员(思路)
            function getValueByPath( obj,path ){
                let paths = path.split(".");
                let res = obj;
                let prop;
                while( prop = paths.shift()){//把数组中的第0项取出来
                    res = res[ prop ];
                }
                return res;
            }
            let rkuohao = /{{(.+?)}}/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 = txt.replace(rkuohao,function(_,g1){
                            let path = g1.trim();
                            let value = getValueByPath(data,path);
                            // 将{{ xxx }} 替换成value
                            return value;
                        })
                        // 注意 现在这个txt和DOM元素是没有关系的
                        childNodes[i].nodeValue = txt;
                    }else if(type === 1){
                        // 是元素,需要判断是都有子元素,判断是否要插值
                        compiler(childNodes[i],data);//递归
                    }
                }
            }
            function JGVue( options ){
                // 习惯:内部的数据使用下划线开头  ,只读数据使用$开头
                this._data = options.data;
                this._el = options.el;
                // 准备工作 (准备模板)
                this.$el = this._templateDOM = document.querySelector( this._el );
                this.parentNode = this._templateDOM.parentNode;//存一下 模板的父级元素
    
                // 渲染工作 
                this._render();
            }
            // 在原型中 加上_render方法
            // 作用  将模板结合数据 得到HTML 加到页面中
            JGVue.prototype._render = function(){
                this.compiler();
            }
            /**编译  将模板与数据结合 得到真正的 DOM 元素 */
            JGVue.prototype.compiler = function(){
                let realHTMLDOM = this._templateDOM.cloneNode( true );//用模板拷贝 得到一个DOM
                compiler(realHTMLDOM, this._data);
                this.update( realHTMLDOM );
            }
            /** 将DOM元素 放到页面中 */
            JGVue.prototype.update = function( real ){
                this.parentNode.replaceChild(real,document.querySelector("#root"));//将父元素的 新的子元素替换成 旧的 子元素
    
    
            }
            // 想想怎么用
            let app = new JGVue({
                el:"#root",
                data:{
                    name:{
                        firstName:"",
                        lastName:"三丰"
                    },
                    age:500
                }
            })
            
            
        </script>
    </body>
    </html>

     到目前为止,我们以上提到了3个问题,1:构造函数 来整理代码,2、路径处理;3、vue中使用虚拟DOM,而我们用的是真是的DOM

    现在就差第三个问题待解决了:

    为什么需要用虚拟DOM呢?

    主要为了提高性能;

    如果我们一直在页面中操作DOM,就会导致极大的内存消耗,导致页面的刷新,降低性能等,所以vue采用了虚拟DOM;

    使用虚拟DOM。所有的DOM操作都在内存中进行,只需要更新一遍,就可以;

  • 相关阅读:
    CAN器件
    团队赋能
    电子模拟开关
    名言出处
    危机管理
    隔离技术
    让CSS布局更加直观:box-sizing
    浅谈跨域以WebService对跨域的支持
    给初学者的20个CSS实用建议
    JS中eval处理JSON数据 为什么要加括号
  • 原文地址:https://www.cnblogs.com/fqh123/p/13621466.html
Copyright © 2020-2023  润新知