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操作都在内存中进行,只需要更新一遍,就可以;

 

 

 

 

 

 

posted @ 2020-09-06 13:34  古墩古墩  Views(537)  Comments(0Edit  收藏  举报