美文网首页
vue源码解析

vue源码解析

作者: 瘾_95f1 | 来源:发表于2019-04-19 10:13 被阅读0次

    知识要点

    • vue工作机制
    • vue响应式的原理
    • 依赖收集与追踪
    • 编译compile
    vue工作机制
    初始化

    new Vue()之后。Vue会调用进行初始化,会初始化生命周期、事件、props、methods、data、computed与watch等。其中最重要的是通过Object.defineProperty设置settergetter,用来实现响应式以及依赖收集

    初始化后调用$mount挂载组件。

    new Vue() init初始化后,$mount挂载到你准备渲染的DOM节点中,然后compile() 会进行编译,生成渲染更新函数render function 更改的都是虚拟DOM节点,更新之前会做diff算法的比较,计算出最小的DOM更新。然后执行到patch()进行更新打补丁(用js的时间换DOM操作的时间)减少页面渲染的次数和数量,最后更新到真实的DOM。
    Watcher 观察者,当数据变化时进行更新

    编译

    编译模式分为3个阶段

    1. parse

      • 使用正则解析template中的vue的指令(v-xxx)变量等等,形成语法书AST
    2. optimize

      • 标记一些静态节点,用做后面的性能优化,在diff的时候直接略过
    3. generate

      • 把第一部分生成的AST转化成渲染函数render function
    响应式原理 defineProperty
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>defineProperty</title>
    </head>
    <body>
        <div id="app">
            <p id="name"></p>
        </div>
        <script>
            var obj = {};
            Object.defineProperty(obj, 'name', {
                get: function () {
                    return document.querySelector('#name').innerHTML;
                },
                set: function (val) {
                    document.querySelector('#name').innerHTML = val;
                }
            })
            obj.name = "htf";
        </script>
    </body>
    </html>
    
    自己实现一个响应式
    • html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>HVue</title>
    </head>
    <body>
        
    </body>
    <script src="./HVue.js"></script>
    <script>
        const app = new HVue({
            data: {
                test: "I am test",
                foo: {
                    bar: "bar"
                }
            }
        });
        app.$data.test = "hello, HTF";
        app.$data.foo.bar = "oh my bar";
    </script>
    </html>
    
    • js
    class HVue {
        constructor(options) {
            this.$options = options;
            // 数据响应化
            this.$data = options.data;
            this.observe(this.$data);
        }
        observe(value) {
            if(!value || typeof value !== 'object') return;
            // 遍历该对象
            Object.keys(value).forEach(key => {
                this.defineReactive(value, key, value[key])
            })
        }
        // 数据响应化
        defineReactive(obj, key, val) {
            this.observe(val);  // 递归遍历,对象中的嵌套属性
            Object.defineProperty(obj, key, {
                get() {
                    return val
                },
                set(newVal) {
                    if(newVal === val) return;
                    val = newVal
                    console.log(`${key}属性更新了:${val}`);
                }
            })
        }
    };
    

    ps: vue数据响应的原理:
    vue利用defineProperty进行数据劫持,简单描述下,vue利用的了Object.defineProperty()属性,把data里面的数据的每一个属性都定义了一个getter和setter的方法,让我们有机会去监听这些属性的变化,当这些属性发生变化的时候,可以通知那些需要更新的地方去更新

    依赖收集与追踪

    理解下面例子

    new Vue({
      template:
         `<div>
              <span>{{ name1 }}</span>
              <span>{{ name2 }}</span>
              <span>{{ name1 }}</span>
        </div>`
      data: {
        name1: 'name1',
        name2: 'name2',
        name3: 'name3'
      },
      created() {
        this.name1 = "HTF",
        this.name3 = "HZH"
      }
    })
    

    name1被修改,视图更新,且需要更新两处
    name2被修改,视图更新
    name3没用到,不需要更新
    如何实现呢,需要扫描视图收集依赖,知道视图中到底哪些地方对数据有依赖,这样当数据变化的时候就能知道需
    要更新哪个,不需要更新哪个。

    demo

    html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>HVue</title>
    </head>
    <body>
        
    </body>
    <script src="./HVue.js"></script>
    <script>
        const app = new HVue({
            data: {
                test: "I am test",
                foo: {
                    bar: "bar"
                }
            }
        });
        app.$data.test = "hello, HTF";
        app.$data.foo.bar = "oh my bar";
    </script>
    </html>
    

    HVue.js

    class HVue {
        constructor(options) {
            this.$options = options;
            // 数据响应化
            this.$data = options.data;
            this.observe(this.$data);
            // 模拟一下watcher创建
            new Watcher();
            this.$data.test;
            new Watcher();
            this.$data.foo.bar;
        }
        observe(value) {
            if(!value || typeof value !== 'object') return;
            // 遍历该对象
            Object.keys(value).forEach(key => {
                this.defineReactive(value, key, value[key])
            })
        }
        // 数据响应化
        defineReactive(obj, key, val) {
            this.observe(val);  // 递归遍历,对象中的嵌套属性
            const dep = new Dep();
            Object.defineProperty(obj, key, {
                get() {
                    // 判断这个依赖项是否存在,存在就加到dep中
                    Dep.target && dep.addDep(Dep.target)
                    return val
                },
                set(newVal) {
                    if(newVal === val) return;
                    val = newVal
                    console.log(`${key}属性更新了:${val}`);
                    // 通知需要更新的依赖项
                    dep.notify();
                }
            })
        }
    };
    
    // Dep: 用来管理Watcher
    class Dep {
        constructor() {
            // 这里存放若干依赖(watcher)
            this.deps = [];
        }
        addDep(dep) {
            this.deps.push(dep)
        }
        notify() {
            this.deps.forEach(dep => dep.update())
        }
    }
    // Watcher
    class Watcher {
        constructor() {
            // 将当前Watcher实例指定到Dep静态属性target
            Dep.target = this;
        }
        update() {
            console.log("属性更新了")
        }
    }
    
    编译compile

    核心逻辑获取dom,遍历dom,获取{{}}格式的变量,以及每个dom的属性,截取k-和@开头的设置响应式

    所有代码逻辑

    index2.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>HVue</title>
    </head>
    <body>
        <div id="app">
            <p>{{name}}</p>
            <p k-text="name"></p>
            <p>{{age}}</p>
            <p>{{doubleAge}}</p>
            <input type="text" k-model="name">
            <button @click="changeName">呵呵</button>
            <div k-html="html"></div>
        </div>
    </body>
    <script src="./HVue.js"></script>
    <script src="./compile.js"></script>
    <script>
        const Htf = new HVue({
            el: "#app",
            data: {
                name: "I am test",
                age: "12",
                html: "<button>这是一个按钮</button>"
            },
            created () {
                console.log("开始了");
                setTimeout(() => {
                    this.name = "我是测试"
                }, 1500);
            },
            methods: {
                changeName() {
                    this.name = "哈喽, htf";
                    this.age = 1
                }
            }
        })
    </script>
    </html>
    

    HVue.js

    class HVue {
        constructor(options) {
            this.$options = options;
            // 数据响应化
            this.$data = options.data;
            this.observe(this.$data);
            // 模拟一下watcher创建
            // new Watcher();
            // this.$data.test;
            // new Watcher();
            // this.$data.foo.bar;
            // 初始化编译器
            new Compile(options.el, this);
            // created的执行
            if (options.created) {
                options.created.call(this);
            }
        }
        observe(value) {
            if(!value || typeof value !== 'object') return;
            // 遍历该对象
            Object.keys(value).forEach(key => {
                this.defineReactive(value, key, value[key]);
                // 代理data中的属性到vue实例上
                this.proxyData(key);
            })
        }
        // 数据响应化
        defineReactive(obj, key, val) {
            this.observe(val);  // 递归遍历,对象中的嵌套属性
            const dep = new Dep();
            Object.defineProperty(obj, key, {
                get() {
                    // 判断这个依赖项是否存在,存在就加到dep中
                    Dep.target && dep.addDep(Dep.target)
                    return val
                },
                set(newVal) {
                    if(newVal === val) return;
                    val = newVal
                    console.log(`${key}属性更新了:${val}`);
                    // 通知需要更新的依赖项
                    dep.notify();
                }
            })
        }
        proxyData(key) {
            Object.defineProperty(this, key, {
                get() {
                    return this.$data[key];
                },
                set(newVal) {
                    this.$data[key] = newVal;
                }
            })
        }
    };
    
    // Dep: 用来管理Watcher
    class Dep {
        constructor() {
            // 这里存放若干依赖(watcher)
            this.deps = [];
        }
        addDep(dep) {
            this.deps.push(dep)
        }
        notify() {
            this.deps.forEach(dep => dep.update())
        }
    }
    // Watcher
    class Watcher {
        constructor(vm, key, cd) {
            this.vm = vm;
            this.key = key;
            this.cd = cd;
            // 将当前Watcher实例指定到Dep静态属性target
            Dep.target = this;
            this.vm[this.key]; // 触发getter,添加依赖
            Dep.target = null;
        }
        update() {
            console.log("属性更新了")
            this.cd.call(this.vm, this.vm[this.key])
        }
    }
    

    compile.js

    // 用法 new Compile(el, vm)
    class Compile{
        constructor(el, vm) {
            // 要遍历的宿主节点
            this.$el = document.querySelector(el);
            this.$vm = vm;
            // 编译
            if(this.$el) {
                // 转换内部内容为片段fragment
                this.$fragment = this.node2Fragment(this.$el);
                // console.log(this.$fragment)
                // 执行编译
                this.compile(this.$fragment);
                // 将编译完的结果追加到$el
                this.$el.appendChild(this.$fragment);
            }
        };
        // 将宿主元素中的代码片段拿出来遍历,这样子做比较高效
        node2Fragment(el) {
            const frag = document.createDocumentFragment();
            // 将el中所有子元素搬至frag中
            let child;
            while(child = el.firstChild ) {
                frag.appendChild(child);
            }
            return frag;
        };
        compile(el) {
            console.log(el.childNodes)
            const childNodes = el.childNodes;
            Array.from(childNodes).forEach(node => {
                // 类型判断
                if(this.isElement(node)) {
                    // 元素
                    // console.log(`编译元素${node.nodeName}`);
                    // 查找k-, @, :
                    const nodeAttrs = node.attributes;
                    Array.from(nodeAttrs).forEach(attr => {
                        const attrName = attr.name; // 属性名字
                        const exp = attr.value; // 属性值
                        if(this.isDirective(attrName)) {
                            // k-text
                            const dir = attrName.substring(2);
                            // 执行指令
                            this[dir] && this[dir](node, this.$vm, exp);
                        }
                        if(this.isEvent(attrName)) {
                            const dir = attrName.substring(1);  // @click
                            this.eventHandler(node, this.$vm, exp, dir);
                        }
                    })
                }else if(this.isInterpolation(node)) {
                    // 文本
                    // console.log(`编译文本${node.textContent}`);
                    this.compileText(node);
                }
                // 递归子节点
                if(node.childNodes && node.childNodes.length > 0) {
                    this.compile(node)
                }
            })
        };
        compileText(node) {
            // 其下的正则进行匹配分组所可以用.$1获取到插值表达式里面的值
            // console.log(RegExp.$1)
            this.updated(node, this.$vm, RegExp.$1, 'text' )
        }
        // 更新函数
        updated (node, vm, exp, dir) {
            const updaterFn = this[dir+'Updater'];
            // 初始化
            updaterFn && updaterFn(node, vm[exp]);
            // 依赖收集
            new Watcher(vm, exp, function(value) {
                updaterFn && updaterFn(node, value);
            })
        }
        text(node, vm, exp) {
            this.updated(node, vm, exp, 'text' )
        }
        // 双向绑定的处理
        model(node, vm, exp) {
            // 指定input的value属性
            this.updated(node, vm, exp, "model");
            // 视图对于模型的响应
            node.addEventListener("input", e => {
                vm[exp] = e.target.value;
            })
        }
        // k-html 的绑定
        html(node, vm, exp) {
            this.updated(node, vm, exp, "html");
        }
        // 事件处理器
        eventHandler(node, vm, exp, dir) {
            const fn = vm.$options.methods && vm.$options.methods[exp];
            if(dir && fn) {
                node.addEventListener(dir, fn.bind(vm));
            }
        }
        textUpdater(node, value) {
            node.textContent = value;
        }
        modelUpdater(node, value) {
            node.value = value;
        }
        htmlUpdater(node, value) {
            node.innerHTML = value;
        }
        isEvent(attr) {
            return attr.indexOf('@') == 0;
        }
        isDirective(attr) {
            return attr.indexOf('k-') == 0;
        }
        isElement(node) {
            return node.nodeType === 1;
        };
        // 差值文本
        isInterpolation(node) {
            return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
        }
    }
    

    vue编译过程是怎样的?

    什么是编译,首先vue写的模板这些东西浏览器是根本就不识别的,通过编辑可以进行依赖收集,通过依赖收集就可以吧data中的这些数据模型和视图之间产生了依赖关系,如果以后模型发生变化的时候,我们就可以通知这些依赖的地方,让他们进行更新,这就是执行编译的目的,这样就可以做到模型驱动视图的变化。

    双向绑定的原理是什么?

    我们在做双向数据绑定的时候通常会使用v-model的指令,我们在编译的过程中可以解析出这个v-model,然后我在做操作的时候有两件事情,第一件事是在v-model所属的事件源加了一个事件监听,如果这个input发生变化的时候,我就可以把最新的值设置到vue的实例上,因为vue的实例上已经实现了数据的响应化,他的响应化的setter函数会触发界面中所有模型依赖的更新,会通知所有那些依赖做更新操作,所以界面中跟这些数据相关的所有部分就更新了。

    如何监听数据的push

    组件的实现

    相关文章

      网友评论

          本文标题:vue源码解析

          本文链接:https://www.haomeiwen.com/subject/posoiqtx.html