vue双向数据绑定原理剖析

2020-03-29  本文已影响0人  欢欣的膜笛
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue双向数据绑定原理剖析</title>
</head>
<body>
    <div id="app">
        <div>{{name}}</div>
        <p v-text="name"></p>
        <p>{{age}}</p>
        <input type="text" v-model="name" placeholder="enter something" />
        <button @click="changeName">changeName</button>
        <div v-html="html"></div>
    </div>

    <script src="./vue.js"></script>
    <script src="./compile.js"></script>
    <script>
        new Vue({
            // 挂载视图
            el: '#app',
            // 定义数据
            data: {
                name: 'I am name',
                age: 12,
                html: '<button>我是按钮</button>'
            },
            created() {
                setTimeout(() => {
                    this.name = '我是name,我变了';
                }, 800);
            },
            methods: {
                changeName() {
                    this.name = '我是name,我又又又又又又又变了';
                    this.age = 18;
                }
            }
        })
    </script>
</body>
</html>
class Vue {
    constructor(options) {
        this.$options = options;
        // 处理 data
        this.$data = options.data;
        // 添加属性观察对象(实现属性挟持)
        this.observe(this.$data);
        // 创建模板编译器,来解析视图
        new Compile(options.el, this);
        if (options.created) {
            options.created.call(this);
        }
    }

    observe(obj) {
        if (!obj || typeof obj !== 'object') {
            return;
        }
        Object.keys(obj).forEach(key => {
            this.defineReactive(obj, key, obj[key]);
            this.proxyData(key);
        })
    }

    // 针对当前对象属性的重新定义(挟持)
    defineReactive(obj, key, val) {
        const dep = new Dep();
        Object.defineProperty(obj, key, {
            get() {
                // 将Dep.target添加到dep中
                // 由于需要在闭包内添加watcher,所以通过Dep定义一个全局target属性,暂存watcher, 添加完移除
                Dep.target && dep.addDep(Dep.target);
                return val;
            },
            set(newVal) {
                if (newVal !== val) {
                    val = newVal;
                    // 通知,触发update操作
                    dep.notify();
                }
            }
        })
        this.observe(val);
    }

    // 给MVVM实例添加一个属性代理的方法,使访问vm的属性代理为访问vm._data的属性
    proxyData(key) {
        Object.defineProperty(this, key, {
            get() {
                return this.$data[key];
            },
            set(newVal) {
                this.$data[key] = newVal;
            }
        })
    }
}

// 消息订阅器,维护一个数组,用来收集订阅者,数据变动触发notify,再调用订阅者的update方法
class Dep {
    constructor() {
        this.deps = [];
    }
    addDep(dep) {
        this.deps.push(dep);
    }
    notify() {
        // 调用订阅者的update方法,通知变化
        this.deps.forEach(dep => dep.update());
    }
}

// Observer和Compile之间通信的桥梁
class Watcher {
    constructor(vm, key, callback) {
        this.vm = vm;
        this.key = key;
        this.callback = callback;
        // 将当前订阅者指向自己
        Dep.target = this;
        // 为了触发属性的getter,从而在dep添加watcher
        this.vm[this.key];
        // 添加完毕,重置
        Dep.target = null;
    }
    update() {
        this.callback.call(this.vm, this.vm[this.key]);
    }
}
class Compile {
    constructor(el, vm) {
        this.$el = document.querySelector(el);
        // vm为创建的vue实例
        this.$vm = vm;
        // 判断视图是否存在
        if (this.$el) {
            // 提取宿主中模板内容到Fragment(文档片段)标签,dom操作会提高效率
            this.$fragment = this.node2Fragment(this.$el);
            // 编译模板内容,同时进行依赖收集
            this.compileNode(this.$fragment);
            this.$el.appendChild(this.$fragment);
        }
    }

    // 核心方法(节省内存)把模板放入内存,等待解析
    node2Fragment(el) {
        // 创建内存片段
        const fragment = document.createDocumentFragment();
        let child;
        // 模板内容放到内存
        // appendChild会把原来的child给移动到新的文档中,当el.firstChild为空时,
        // while也会结束 a = undefined  => 返回 undefined
        while (child = el.firstChild) {
            fragment.appendChild(child);
        }
        return fragment;
    }

    compileNode(el) {
        const childNodes = el.childNodes;
        Array.from(childNodes).forEach(node => {
            // 判断节点类型
            if (node.nodeType === 1) {
                // element 节点
                this.compileElement(node);
            } else if (this.isInterpolation(node)) {
                // 插值表达式
                this.compileText(node);
            }
            // 递归子节点
            if (node.childNodes && node.childNodes.length) {
                this.compileNode(node)
            }
        })
    }

    compileElement(node) {
        // <div k-model="foo" k-text="test" @click="onClick">
        let nodeAttrs = node.attributes;
        Array.from(nodeAttrs).forEach(attr => {
            const attrName = attr.name;
            const attrValue = attr.value;
            if (this.isDirective(attrName)) {
                const dir = attrName.substring(2);
                this[dir] && this[dir](node, attrValue);
            }
            if (this.isEvent(attrName)) {
                const dir = attrName.substring(1);
                this.eventHandle(node, attrValue, dir);
            }
        })
    }

    // 指令
    isDirective(attrName) {
        return attrName.includes('v-');
    }

    // 双向绑定
    model(node, key) {
        // data -> view
        this.update(node, key, 'model');
        // view -> data
        node.addEventListener('input', event => {
            this.$vm.$data[key] = event.target.value;
        })
    }

    text(node, key) {
        this.update(node, key, 'text');
    }

    html(node, key) {
        this.update(node, key, 'html');
    }

    isInterpolation(node) {
        // 文本 && {{}}
        return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
    }

    compileText(node) {
        // RegExp.$1, 缓存最近一次的正则里面的值
        this.update(node, RegExp.$1, 'text');
    }

    update(node, key, dir) {
        // 找到更新规则对象的更新方法
        let updatrFn = this[dir + 'Updater'];
        // 第一次初始化视图
        updatrFn && updatrFn(node, this.$vm.$data[key]);
        // 每解析一个表达式(非事件指令)都会创建一个对应的watcher对象, 并建立watcher与dep的关系
        new Watcher(this.$vm, key, (value) => {
            // 一旦属性值有变化,会收到通知执行此更新函数,更新视图
            updatrFn && updatrFn(node, value);
        })
    }

    textUpdater(node, value) {
        node.textContent = value;
    }

    htmlUpdater(node, value) {
        node.innerHTML = value;
    }

    modelUpdater(node, value) {
        node.value = value;
    }

    // 事件
    isEvent(attrName) {
        return attrName.includes('@');
    }

    // 事件绑定
    eventHandle(node, key, dir) {
        const fn = this.$vm.$options.methods && this.$vm.$options.methods[key];
        if (dir && fn) {
            node.addEventListener(dir, () => { fn.call(this.$vm) });
        }
    }
}
上一篇 下一篇

猜你喜欢

热点阅读