vue手写源码详解二

vue手写源码详解二

本章讲解如何根据指定的数据渲染控制区域

我们可以通过获取元素来判断有没有用v-model或者{{}},同时可以用指定的数据来替换,但这样做有一个弊端如数据发生变化那么页面就需要从新渲染,这样高频的渲染显然是我们不想看到的,网页卡顿的同时给用户也造成了很差的体验

那么有什么办法可以去规避这个问题那,其实可以通过把控制区域内的元素放入内存中,在内存中把所有的数据替换好,这样一次性即可渲染到网页上

那么我们需要的处理步骤分为三步

第一步:将网页上的元素放入内存中

如何把网页内的元素放入内存中那,使用DocumentFragment(文档碎片)这个方法可以完成,感兴趣可以百度看看

class Nue {
    constructor(options){
        // 1.保存创建时候传递过来的数据
        if(this.isElement(options.el)){
            this.$el = options.el;
        }else{
            this.$el = document.querySelector(options.el);
        }
        this.$data = options.data;
        // 2.根据指定的区域和数据去编译渲染界面
        if(this.$el){
            new Compiler(this);
        }
    }
    // 判断是否是一个元素
    isElement(node){
        return node.nodeType === 1;
    }
}
class Compiler {
    constructor(vm){
        this.vm = vm;
        // 1.将网页上的元素放到内存中
        let fragment = this.node2fragment(this.vm.$el);
        console.log(fragment);
        // 2.利用指定的数据编译内存中的元素
        // 3.将编译好的内容重新渲染会网页上
    }
    node2fragment(app){
        // 1.创建一个空的文档碎片对象
        let fragment = document.createDocumentFragment();
        // 2.编译循环取到每一个元素
        let node = app.firstChild;
        //当node元素为undefined就不会走到下发循环内
        while (node){
            // 注意点: 只要将元素添加到了文档碎片对象中, 那么这个元素就会自动从网页上消失
            fragment.appendChild(node);
            //每次获取第二个依次类推,做了个循环
             //借用变量提升重新赋值上面的node
            node = app.firstChild;
        }
        // 3.返回存储了所有元素的文档碎片对象
        return fragment;
    }
}

这时利用数据已经在内存中处理完毕,那么我们怎么根据指定的数据渲染那

第二步:利用指定的数据编译内存中的元素

class Nue {
    constructor(options){
        // 1.保存创建时候传递过来的数据
        if(this.isElement(options.el)){
            this.$el = options.el;
        }else{
            this.$el = document.querySelector(options.el);
        }
        this.$data = options.data;
        // 2.根据指定的区域和数据去编译渲染界面
        if(this.$el){
            new Compiler(this);
        }
    }
    // 判断是否是一个元素
    isElement(node){
        return node.nodeType === 1;
    }
}
class Compiler {
    constructor(vm){
        this.vm = vm;
        // 1.将网页上的元素放到内存中
        let fragment = this.node2fragment(this.vm.$el);
        // 2.利用指定的数据编译内存中的元素
        this.buildTemplate(fragment);
        // 3.将编译好的内容重新渲染会网页上
    }
    node2fragment(app){
        // 1.创建一个空的文档碎片对象
        let fragment = document.createDocumentFragment();
        // 2.编译循环取到每一个元素
        let node = app.firstChild;
        while (node){
            // 注意点: 只要将元素添加到了文档碎片对象中, 那么这个元素就会自动从网页上消失
            fragment.appendChild(node);
            node = app.firstChild;
        }
        // 3.返回存储了所有元素的文档碎片对象
        return fragment;
    }
    
    
            <<----------手动分解线方便理解------->>    
    
    
    
    //处理编译每一个元素
    buildTemplate(fragment){
        // 内存中的数据使用childNodes取出是类数组,需要三点语法解析
        let nodeList = [...fragment.childNodes];
        nodeList.forEach(node=>{
            // 需要判断当前遍历到的节点是一个元素还是一个文本
            // 如果是一个元素, 我们需要判断有没有v-model属性
            // 如果是一个文本, 我们需要判断有没有{{}}的内容
            if(this.vm.isElement(node)){
                // 是一个元素
                this.buildElement(node);
                // 处理子元素(处理后代)
                this.buildTemplate(node);
            }else{
                // 不是一个元素
                this.buildText(node);
            }
        })
    }
    // 处理属性指令
    buildElement(node){
        // attributes 属性返回指定节点的属性集合
        // 因为返回的是类数组不是数组,所以用三点语法将其变为真正的数组,方便我们去遍历
        let attrs = [...node.attributes];
        console.log('attrs',attrs)
        // 遍历属性
        attrs.forEach(attr => {
            // name是key,value是值
            let {name, value} = attr;
            // 通过startsWith获取以v-开头的属性
            if(name.startsWith('v-')){
                console.log('是Vue的指令, 需要我们处理', name);
            }
        })
    }
    // 处理模板内容
    buildText(node){
        // 获取模板内的,内容
        let content = node.textContent;
        // 正则匹配{{}}花括号有特殊含义需要\去转义
        let reg = /\{\{.+?\}\}/gi;
        // 匹配内容
        if(reg.test(content)){

            console.log('是{{}}的文本, 需要我们处理', content);
        }
    }
}

第三步:将编译好的内容重新渲染回网页上

3.1替换指令中的数据

这一步将根据指令渲染到页面,此时我们为了处理复杂的逻辑,做了一个全局指令来协助完成编译

建议复制html内容和js运行理解下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>01-Vue基本模板</title>
    <!--1.下载导入Vue.js-->
<!--    <script src="js/vue.js"></script>-->
    <script src="js/nue.js"></script>
</head>
<body>
<div id="app">
    <input type="text" v-model="name">
    <input type="text" v-model="time.h">
    <input type="text" v-model="time.m">
    <input type="text" v-model="time.s">
    <div v-html="html">abc</div>
    <div v-text="text">123</div>
    <p>{{ name }}</p>
    <p>{{age}}</p>
    <ul>
        <li>6</li>
        <li>6</li>
        <li>6</li>
    </ul>
</div>
<script>
    // 2.创建一个Vue的实例对象
    // let vue = new Vue({
    let vue = new Nue({
        // 3.告诉Vue的实例对象, 将来需要控制界面上的哪个区域
        el: '#app',
        // el: document.querySelector('#app'),
        // 4.告诉Vue的实例对象, 被控制区域的数据是什么
        data: {
            name: "name内容",
            age: 33,
            time: {
                h: "我是对象内容1",
                m: "我是对象内容2",
                s: "我是对象内容3"
            },
            html: `<div>我是div</div>`,
            text: `<div>我是div</div>`
        }
    });
</script>
</body>
</html>
// 处理指令工具类
let CompilerUtil = {
    // 处理复杂数据例如对象,根据点语法截取循环
    getValue(vm, value){
        // time.h --> [time, h]
        return value.split('.').reduce((data, currentKey) => {
            // 第一次执行: data=$data, currentKey=time
            // 第二次执行: data=time, currentKey=h
            return data[currentKey];
        }, vm.$data);
    },
    model: function (node, value, vm) { // value=time.h
        /*node.value = vm.$data[value]; // vm.$data[time.h] --> vm.$data[time] --> time[h]*/
        let val = this.getValue(vm, value);
        node.value = val;
    },
    html: function (node, value, vm) {
        let val = this.getValue(vm, value);
        node.innerHTML = val;
    },
    text: function (node, value, vm) {
        let val = this.getValue(vm, value);
        node.innerText = val;
    }
}
class Nue {
    constructor(options){
        // 1.保存创建时候传递过来的数据
        if(this.isElement(options.el)){
            this.$el = options.el;
        }else{
            this.$el = document.querySelector(options.el);
        }
        this.$data = options.data;
        // 2.根据指定的区域和数据去编译渲染界面
        if(this.$el){
            new Compiler(this);
        }
    }
    // 判断是否是一个元素
    isElement(node){
        return node.nodeType === 1;
    }
}
class Compiler {
    constructor(vm){
        this.vm = vm;
        // 1.将网页上的元素放到内存中
        let fragment = this.node2fragment(this.vm.$el);
        // 2.利用指定的数据编译内存中的元素
        this.buildTemplate(fragment);
        // 3.将编译好的内容重新渲染会网页上
        this.vm.$el.appendChild(fragment);
    }
    node2fragment(app){
        // 1.创建一个空的文档碎片对象
        let fragment = document.createDocumentFragment();
        // 2.编译循环取到每一个元素
        let node = app.firstChild;
        while (node){
            // 注意点: 只要将元素添加到了文档碎片对象中, 那么这个元素就会自动从网页上消失
            fragment.appendChild(node);
            node = app.firstChild;
        }
        // 3.返回存储了所有元素的文档碎片对象
        return fragment;
    }
    buildTemplate(fragment){
        let nodeList = [...fragment.childNodes];
        nodeList.forEach(node=>{
            // 需要判断当前遍历到的节点是一个元素还是一个文本
            // 如果是一个元素, 我们需要判断有没有v-model属性
            // 如果是一个文本, 我们需要判断有没有{{}}的内容
            if(this.vm.isElement(node)){
                // 是一个元素
                this.buildElement(node);
                // 处理子元素(处理后代)
                this.buildTemplate(node);
            }else{
                // 不是一个元素
                this.buildText(node);
            }
        })
    }
    buildElement(node){
        let attrs = [...node.attributes];
        attrs.forEach(attr => {
            let {name, value} = attr; // v-model="name" / name:v-model / value:name
            if(name.startsWith('v-')){ // v-model / v-html / v-text / v-xxx
                // 截取-后面的指令
                let [_, directive] = name.split('-'); // v-model -> [v, model]
                // 调用上方工具类处理对应指令
                CompilerUtil[directive](node, value, this.vm);
            }
        })
    }
    buildText(node){
        let content = node.textContent;
        let reg = /\{\{.+?\}\}/gi;
        if(reg.test(content)){
            console.log('是{{}}的文本, 需要我们处理', content);
        }
    }
}
3.2编译模板数据
let CompilerUtil = {
    getValue(vm, value){
        // time.h --> [time, h]
       return value.split('.').reduce((data, currentKey) => {
            // 第一次执行: data=$data, currentKey=time
            // 第二次执行: data=time, currentKey=h
            return data[currentKey.trim()];
        }, vm.$data);
    },
    // 处理模板方法
    getContent(vm, value){
        // console.log(value); //  {{name}}-{{age}} -> 李南江-{{age}}  -> 李南江-33
        let reg = /\{\{(.+?)\}\}/gi;
        let val = value.replace(reg, (...args) => {
            // 第一次执行 args[1] = name
            // 第二次执行 args[1] = age
            // console.log(args);
            return this.getValue(vm, args[1]); // 李南江, 33
        });
        // console.log(val);
        return val;
    },
    model: function (node, value, vm) {
        let val = this.getValue(vm, value);
        node.value = val;
    },
    html: function (node, value, vm) {
        let val = this.getValue(vm, value);
        node.innerHTML = val;
    },
    text: function (node, value, vm) {
        let val = this.getValue(vm, value);
        node.innerText = val;
    },
    // 处理模板方法
    content: function (node, value, vm) {
        // console.log(value); // {{ name }} -> name -> $data[name]
        let val = this.getContent(vm, value);
        node.textContent = val;
    }
}
class Nue {
    constructor(options){
        // 1.保存创建时候传递过来的数据
        if(this.isElement(options.el)){
            this.$el = options.el;
        }else{
            this.$el = document.querySelector(options.el);
        }
        this.$data = options.data;
        // 2.根据指定的区域和数据去编译渲染界面
        if(this.$el){
            new Compiler(this);
        }
    }
    // 判断是否是一个元素
    isElement(node){
        return node.nodeType === 1;
    }
}
class Compiler {
    constructor(vm){
        this.vm = vm;
        // 1.将网页上的元素放到内存中
        let fragment = this.node2fragment(this.vm.$el);
        // 2.利用指定的数据编译内存中的元素
        this.buildTemplate(fragment);
        // 3.将编译好的内容重新渲染会网页上
        this.vm.$el.appendChild(fragment);
    }
    node2fragment(app){
        // 1.创建一个空的文档碎片对象
        let fragment = document.createDocumentFragment();
        // 2.编译循环取到每一个元素
        let node = app.firstChild;
        while (node){
            // 注意点: 只要将元素添加到了文档碎片对象中, 那么这个元素就会自动从网页上消失
            fragment.appendChild(node);
            node = app.firstChild;
        }
        // 3.返回存储了所有元素的文档碎片对象
        return fragment;
    }
    buildTemplate(fragment){
        let nodeList = [...fragment.childNodes];
        nodeList.forEach(node=>{
            // 需要判断当前遍历到的节点是一个元素还是一个文本
            if(this.vm.isElement(node)){
                // 是一个元素
                this.buildElement(node);
                // 处理子元素(处理后代)
                this.buildTemplate(node);
            }else{
                // 不是一个元素
                this.buildText(node);
            }
        })
    }
    buildElement(node){
        let attrs = [...node.attributes];
        attrs.forEach(attr => {
            let {name, value} = attr; // v-model="name" / name:v-model / value:name
            if(name.startsWith('v-')){ // v-model / v-html / v-text / v-xxx
                let [_, directive] = name.split('-'); // v-model -> [v, model]
                CompilerUtil[directive](node, value, this.vm);
            }
        })
    }
    buildText(node){
        let content = node.textContent;
        let reg = /\{\{.+?\}\}/gi;
        if(reg.test(content)){
            // 处理模板数据
            CompilerUtil['content'](node, content, this.vm);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值