<head><meta charset="UTF-8"><...">
最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 手撕Vue源码全过程(上)

    正文概述 掘金(山竹回家了)   2021-02-04   382

    1.构建自定义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="./testVue.js"></script>
    </head>
    
    <body>
        <div id="APP">
            <p>{{name}}</p>
            <input type="text" v-model='name'>
        </div>
        <script>
            let vue = new Vue({
                // 指出需要控制的区域
                el: '#APP',
                // 传递数据
                data: {
                    name: "山竹",
                    age: 18
                }
            })
            // console.log(vue.$el);
            // console.log(vue.$data);
        </script>
    </body>
    
    </html>
    
    // 模拟创建vue实例(类)
    class Vue {
        // 构造器,接收一个参数,参数为对象
        constructor(value) {
            // 判断el是否为一个节点
            if (this.isElement(value.el)) {
                this.$el = value.el
            } else {
                // 如果没有则根据传入的找
                this.$el = document.querySelector(value.el)
            }
            this.$data = value.data
            // 根据传入的位置和数据渲染
            // 先判断是否el是否存在,否则不渲染
            if (this.$el) {
                // 传入该实例
                new Compier(this)
            }
        }
        isElement(node) {
            // console.log(node.nodeType === 1);//false
            return node.nodeType === 1
        }
    
    }
    class Compier {
        constructor(vm) {
            // 保存vue实例
            this.vm = vm
        }
    }
    

    效果

    手撕Vue源码全过程(上)

    2.提取元素到内存

    接上编写

    // 模拟创建vue实例(类)
    class Vue {
        // 构造器,接收一个参数,参数为对象
        constructor(value) {
            // 判断el是否为一个节点
            if (this.isElement(value.el)) {
                this.$el = value.el
            } else {
                // 如果没有则根据传入的找
                this.$el = document.querySelector(value.el)
            }
            this.$data = value.data
            // 根据传入的位置和数据渲染
            // 先判断是否el是否存在,否则不渲染
            if (this.$el) {
                // 传入该实例
                new Compier(this)
            }
        }
        isElement(node) {
            // console.log(node.nodeType === 1);//false
            return node.nodeType === 1
        }
    
    }
    class Compier {
        constructor(vm) {
            // 保存vue实例
            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
            while (node) {
                // 判断是否还有元素
                // 注意点:只要元素添加到文档碎片对象中,name这个元素就会自动从网页上消失
                fragment.appendChild(node)
                node = app.firstChild
            }
            //3.返回储存了所有元素的文档碎片对象
            return fragment
        }
    }
    

    效果 手撕Vue源码全过程(上) 手撕Vue源码全过程(上)

    3.查找指令和模板

    // 模拟创建vue实例(类)
    class Vue {
        // 构造器,接收一个参数,参数为对象
        constructor(value) {
            // 判断el是否为一个节点
            if (this.isElement(value.el)) {
                this.$el = value.el
            } else {
                // 如果没有则根据传入的找
                this.$el = document.querySelector(value.el)
            }
            this.$data = value.data
            // 根据传入的位置和数据渲染
            // 先判断是否el是否存在,否则不渲染
            if (this.$el) {
                // 传入该实例
                new Compier(this)
            }
        }
        // 判断是否为元素节点,元素节点为1,属性节点为2
        isElement(node) {
            // console.log(node.nodeType === 1);//false
            return node.nodeType === 1
        }
    
    }
    class Compier {
        constructor(vm) {
            // 保存vue实例
            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) {
                // 判断是否还有元素
                // 注意点:只要元素添加到文档碎片对象中,name这个元素就会自动从网页上消失
                fragment.appendChild(node)
                node = app.firstChild
            }
            //3.返回储存了所有元素的文档碎片对象
            return fragment
        }
        // =========================================以下为新增=============================
        buildTemplate(fragment) {
            // 从元素获取所有节点,伪数组转为数组
            let nodeList = [...fragment.childNodes]
            // console.log(nodeList);//[text, p, text, input, text]
            // 循环判断当前的节点是一个元素还是一个文本
            nodeList.forEach(node => {
                if (this.vm.isElement(node)) {
                    //是一个元素
                    this.buildElement(node)
                    //处理子元素,递归
                    this.buildTemplate(node)
                } else {
                    //不是一个元素
                    this.buildText(node)
                }
            })
        }
        // 元素处理
        buildElement(node) {
            // attributes 属性返回指定节点的属性集合
            let attrs = [...node.attributes]
            attrs.forEach(attr => {
                // console.log(attr);//获取到属性
                let { name, value } = attr
                // console.log(name,value);//单独拿到类型与类型的取值
                if (name.startsWith('v-')) {
                    // startsWith() 方法用于检测字符串是否以指定的子字符串开始。
                    // console.log('是v-', name,value);//是v- v-model name
                }
            })
        }
        //文本处理
        buildText(node) {
            // textContent 属性设置或者返回指定节点的文本内容。
            let content = node.textContent
            let reg=/\{\{.+?\}\}/gi
            if(reg.test(content)){
                console.log('是{{}}',content);//是{{}} {{name}}
            }
        }
    }
    

    4.编译指令数据

    HTML继上添加

    <!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="./testVue.js"></script>
    </head>
    
    <body>
        <div id="APP">
            <p>{{name}}</p>
            <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'>我是div</div>
            <div v-text='text'>我是div</div>
        </div>
        <script>
            let vue = new Vue({
                // 指出需要控制的区域
                el: '#APP',
                // 传递数据
                data: {
                    name: "山竹",
                    age: 18,
                    // ======================新增===============
                    time:{
                        h:11,
                        m:22,
                        s:33
                    },
                    html:`<div>我是HTML</div>`,
                    text:`<div>我是text</div>`
                }
            })
            // console.log(vue.$el);
            // console.log(vue.$data);
        </script>
    </body>
    
    </html>
    

    js继上添加

    // ==============================创建工具类对应不同指令======================
    let CompilerUtil = {
        getValue(vm, value) {
            //time.h-->[time,h],利用reduce遍历逐层取
            //reduce接收的第一个参数为上一次调用回调返回的值,或者是提供的初始值
            //  第二个参数为数组中当前被处理的元素
            // vm.$data作为data第一次调用初始值
            return value.split('.').reduce((data, currentKey) => {
                //第一次执行:data=$data,currentKey=time
                // 第二次执行:data=time,currentKey=h
                return data[currentKey]
            }, vm.$data)
        },
        model: function (node, value, vm) {
            // console.log(node, value, vm);
            // v-model作用在input上
            // 根据被替换内容,获取对应的数据
            // 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
        }
    }
    // 模拟创建vue实例(类)
    class Vue {
        // 构造器,接收一个参数,参数为对象
        constructor(value) {
            // 判断el是否为一个节点
            if (this.isElement(value.el)) {
                this.$el = value.el
            } else {
                // 如果没有则根据传入的找
                this.$el = document.querySelector(value.el)
            }
            this.$data = value.data
            // 根据传入的位置和数据渲染
            // 先判断是否el是否存在,否则不渲染
            if (this.$el) {
                // 传入该实例
                new Compier(this)
            }
        }
        // 判断是否为元素节点,元素节点为1,属性节点为2
        isElement(node) {
            // console.log(node.nodeType === 1);//false
            return node.nodeType === 1
        }
    
    }
    class Compier {
        constructor(vm) {
            // 保存vue实例
            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) {
                // 判断是否还有元素
                // 注意点:只要元素添加到文档碎片对象中,name这个元素就会自动从网页上消失
                fragment.appendChild(node)
                node = app.firstChild
            }
            //3.返回储存了所有元素的文档碎片对象
            return fragment
        }
        buildTemplate(fragment) {
            // 从元素获取所有节点,伪数组转为数组
            let nodeList = [...fragment.childNodes]
            // console.log(nodeList);//[text, p, text, input, text]
            // 循环判断当前的节点是一个元素还是一个文本
            nodeList.forEach(node => {
                if (this.vm.isElement(node)) {
                    //是一个元素
                    this.buildElement(node)
                    //处理子元素,递归
                    this.buildTemplate(node)
                } else {
                    //不是一个元素
                    this.buildText(node)
                }
            })
        }
        // 元素处理
        buildElement(node) {
            // attributes 属性返回指定节点的属性集合
            let attrs = [...node.attributes]
            attrs.forEach(attr => {
                // console.log(attr);//获取到属性
                let { name, value } = attr
                // console.log(name,value);//单独拿到类型与类型的取值
                if (name.startsWith('v-')) {
                    // v-开头的有很多,比如v-model/v-html/v-if...
                    // startsWith() 方法用于检测字符串是否以指定的子字符串开始。
                    // console.log('是v-', name,value);//是v- v-model name
                    // =================更新部分=====================
                    // 切割并解构,不需要v-
                    let [, directive] = name.split('-')//v,model
                    // console.log(directive);//model
                    // 找到对应的工具类执行对应的方法
                    // node:修改的节点,value:修改的值,this.vm新值
                    CompilerUtil[directive](node, value, this.vm)
                }
            })
        }
        //文本处理
        buildText(node) {
            // textContent 属性设置或者返回指定节点的文本内容。
            let content = node.textContent
            let reg = /\{\{.+?\}\}/gi
            if (reg.test(content)) {
                // console.log('是{{}}',content);//是{{}} {{name}}
            }
        }
    }
    

    效果 手撕Vue源码全过程(上)

    5.编译模板数据

    let CompilerUtil = {
        getValue(vm, value) {
            //time.h-->[time,h],利用reduce遍历逐层取
            //reduce接收的第一个参数为上一次调用回调返回的值,或者是提供的初始值
            //  第二个参数为数组中当前被处理的元素
            // vm.$data作为data第一次调用初始值
            return value.split('.').reduce((data, currentKey) => {
                //第一次执行:data=$data,currentKey=time
                // 第二次执行:data=time,currentKey=h
                return data[currentKey.trim()]
            }, vm.$data)
        },
        // ================新增部分==========
        getContent(vm, value) {
            let reg = /\{\{(.+?)\}\}/gi
            let val = value.replace(reg, (...args) => {
                return this.getValue(vm, args[1])
            })
            return val
        },
        model: function (node, value, vm) {
            // console.log(node, value, vm);
            // v-model作用在input上
            // 根据被替换内容,获取对应的数据
            // 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
        },
        // ====================更新部分===============
        content: function (node, value, vm) {
            // console.log(value);//{{name}}-->取出name-->将$data[name]的数据赋值
            let val = this.getContent(vm, value)
            node.textContent = val
        }
    }
    // 模拟创建vue实例(类)
    class Vue {
        // 构造器,接收一个参数,参数为对象
        constructor(value) {
            // 判断el是否为一个节点
            if (this.isElement(value.el)) {
                this.$el = value.el
            } else {
                // 如果没有则根据传入的找
                this.$el = document.querySelector(value.el)
            }
            this.$data = value.data
            // 根据传入的位置和数据渲染
            // 先判断是否el是否存在,否则不渲染
            if (this.$el) {
                // 传入该实例
                new Compier(this)
            }
        }
        // 判断是否为元素节点,元素节点为1,属性节点为2
        isElement(node) {
            // console.log(node.nodeType === 1);//false
            return node.nodeType === 1
        }
    
    }
    class Compier {
        constructor(vm) {
            // 保存vue实例
            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) {
                // 判断是否还有元素
                // 注意点:只要元素添加到文档碎片对象中,name这个元素就会自动从网页上消失
                fragment.appendChild(node)
                node = app.firstChild
            }
            //3.返回储存了所有元素的文档碎片对象
            return fragment
        }
        buildTemplate(fragment) {
            // 从元素获取所有节点,伪数组转为数组
            let nodeList = [...fragment.childNodes]
            // console.log(nodeList);//[text, p, text, input, text]
            // 循环判断当前的节点是一个元素还是一个文本
            nodeList.forEach(node => {
                if (this.vm.isElement(node)) {
                    //是一个元素
                    this.buildElement(node)
                    //处理子元素,递归
                    this.buildTemplate(node)
                } else {
                    //不是一个元素
                    this.buildText(node)
                }
            })
        }
        // 元素处理
        buildElement(node) {
            // attributes 属性返回指定节点的属性集合
            let attrs = [...node.attributes]
            attrs.forEach(attr => {
                // console.log(attr);//获取到属性
                let { name, value } = attr
                // console.log(name,value);//单独拿到类型与类型的取值
                if (name.startsWith('v-')) {
                    // v-开头的有很多,比如v-model/v-html/v-if...
                    // startsWith() 方法用于检测字符串是否以指定的子字符串开始。
                    // console.log('是v-', name,value);//是v- v-model name
                    // 切割并解构,不需要v-
                    let [, directive] = name.split('-')//v,model
                    // console.log(directive);//model
                    // 找到对应的工具类执行对应的方法
                    // node:修改的节点,value:修改的值,this.vm新值
                    CompilerUtil[directive](node, value, this.vm)
                }
            })
        }
        //文本处理
        buildText(node) {
            // textContent 属性设置或者返回指定节点的文本内容。
            let content = node.textContent
            let reg = /\{\{.+?\}\}/gi
            if (reg.test(content)) {
                // console.log('是{{}}',content);//是{{}} {{name}}
                // ======================================新增部分=============================
                // 找到对应的工具类执行对应的方法
                // node:修改的节点,content:修改的值,this.vm新值
                CompilerUtil['content'](node, content, this.vm)
            }
        }
    }
    

    6.监听数据变化

    let CompilerUtil = {
        getValue(vm, value) {
            //time.h-->[time,h],利用reduce遍历逐层取
            //reduce接收的第一个参数为上一次调用回调返回的值,或者是提供的初始值
            //  第二个参数为数组中当前被处理的元素
            // vm.$data作为data第一次调用初始值
            return value.split('.').reduce((data, currentKey) => {
                //第一次执行:data=$data,currentKey=time
                // 第二次执行:data=time,currentKey=h
                return data[currentKey.trim()]
            }, vm.$data)
        },
        getContent(vm, value) {
            let reg = /\{\{(.+?)\}\}/gi
            let val = value.replace(reg, (...args) => {
                return this.getValue(vm, args[1])
            })
            return val
        },
        model: function (node, value, vm) {
            // console.log(node, value, vm);
            // v-model作用在input上
            // 根据被替换内容,获取对应的数据
            // 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
        },
        content: function (node, value, vm) {
            // console.log(value);//{{name}}-->取出name-->将$data[name]的数据赋值
            let val = this.getContent(vm, value)
            node.textContent = val
        }
    }
    // 模拟创建vue实例(类)
    class Vue {
        // 构造器,接收一个参数,参数为对象
        constructor(value) {
            // 判断el是否为一个节点
            if (this.isElement(value.el)) {
                this.$el = value.el
            } else {
                // 如果没有则根据传入的找
                this.$el = document.querySelector(value.el)
            }
            this.$data = value.data
            // 根据传入的位置和数据渲染
            // 先判断是否el是否存在,否则不渲染
            if (this.$el) {
                // ==================更新================
                // 1.给外界传入的所有数据都添加get/set方法,这样就可以监听数据变化了
                // 监听数据变化
                new Observer(this.$data)
                // 传入该实例
                new Compier(this)
            }
        }
        // 判断是否为元素节点,元素节点为1,属性节点为2
        isElement(node) {
            // console.log(node.nodeType === 1);//false
            return node.nodeType === 1
        }
    
    }
    class Compier {
        constructor(vm) {
            // 保存vue实例
            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) {
                // 判断是否还有元素
                // 注意点:只要元素添加到文档碎片对象中,name这个元素就会自动从网页上消失
                fragment.appendChild(node)
                node = app.firstChild
            }
            //3.返回储存了所有元素的文档碎片对象
            return fragment
        }
        buildTemplate(fragment) {
            // 从元素获取所有节点,伪数组转为数组
            let nodeList = [...fragment.childNodes]
            // console.log(nodeList);//[text, p, text, input, text]
            // 循环判断当前的节点是一个元素还是一个文本
            nodeList.forEach(node => {
                if (this.vm.isElement(node)) {
                    //是一个元素
                    this.buildElement(node)
                    //处理子元素,递归
                    this.buildTemplate(node)
                } else {
                    //不是一个元素
                    this.buildText(node)
                }
            })
        }
        // 元素处理
        buildElement(node) {
            // attributes 属性返回指定节点的属性集合
            let attrs = [...node.attributes]
            attrs.forEach(attr => {
                // console.log(attr);//获取到属性
                let { name, value } = attr
                // console.log(name,value);//单独拿到类型与类型的取值
                if (name.startsWith('v-')) {
                    // v-开头的有很多,比如v-model/v-html/v-if...
                    // startsWith() 方法用于检测字符串是否以指定的子字符串开始。
                    // console.log('是v-', name,value);//是v- v-model name
                    // 切割并解构,不需要v-
                    let [, directive] = name.split('-')//v,model
                    // console.log(directive);//model
                    // 找到对应的工具类执行对应的方法
                    // node:修改的节点,value:修改的值,this.vm新值
                    CompilerUtil[directive](node, value, this.vm)
                }
            })
        }
        //文本处理
        buildText(node) {
            // textContent 属性设置或者返回指定节点的文本内容。
            let content = node.textContent
            let reg = /\{\{.+?\}\}/gi
            if (reg.test(content)) {
                // console.log('是{{}}',content);//是{{}} {{name}}
                // 找到对应的工具类执行对应的方法
                // node:修改的节点,content:修改的值,this.vm新值
                CompilerUtil['content'](node, content, this.vm)
            }
        }
    }
    // =================================新增部分======================
    class Observer {
        // 只要将需要监听的那个对象传递给Observer这个类
        // 这个类就可以快速的给传入的对象的所有属性都添加get/set方法
        // data代表接收的对象
        constructor(data) {
          this.observer(data);
        }
        // 给属性添加get/set;
        observer(obj) {
          // 判断是不是对象
          if (obj && typeof obj === "object") {
            //遍历取出传入对象的所有属性,给遍历到的属性都增加get/set方法
            for (let key in obj) {
              // 参数为:对象,属性,值
              this.defineRecative(obj, key, obj[key]);
            }
          }
        }
        // obj:需要操作的对象
        // attr:需要新增get/set方法的属性
        // value:需要新增get/set方法属性的取值
        defineRecative(obj, attr, value) {
          //如果对象属性里面嵌套对象,则开始递归添加get/set
          this.observer(value);
          Object.defineProperty(obj, attr, {
            // get方法直接返回值
            get() {
              return value;
            },
            // set方法接收新值并返回
            set: (newValue) => {
              if (value !== newValue) {
                // 如果新值里面也有对象,也需要添加get/set
                this.observer(newValue);
                value = newValue;
                console.log("监听到数据的变化,需要去更新UI");
              }
            },
          });
        }}
    

    手撕Vue源码全过程(上)


    起源地下载网 » 手撕Vue源码全过程(上)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元