vue源码-双向绑定

62 阅读1分钟

index.html

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
</head>
<body>
    <div id="app">
        <h1> {{ str }} </h1>
        {{ str }}
        <span> {{ val }} </span>
        <button @click="btn">按钮</button>
        <input v-model="str" />
    </div>
    <script type="text/javascript" src="vue.js"></script>
    <script type="text/javascript">
        new Vue({
            el: '#app',
            data: {
                str: '123',
                val: 'abc'
            },
            methods: {
                btn(event) {
                    this.str = '456';
                }
            },
            beforeCreate() {
                console.log('beforeCreated', this.$el, this.$data);
            },
            created() {
                console.log('created', this.$el, this.$data);
            },
            beforeMount() {
                console.log('beforeMounted', this.$el, this.$data);
            },
            mounted() {
                console.log('mounted', this.$el, this.$data);
            }
        })
    </script>
</body>
</html>

vue.js

    constructor(options) {
        console.log(this);
        this.$options = options;
        this.$watchEvent = {};
        options.beforeCreate.bind(this)();
        this.$data = options.data;
        this.proxyData();
        options.created.bind(this)();
        options.beforeMount.bind(this)();
        this.$el = document.querySelector(options.el);
        this.compile(this.$el);
        options.mounted.bind(this)();
    }
    // 数据代理,给new Vue对象实例添加属性
    proxyData() {
        for (let key in this.$data) {
            Object.defineProperty(this, key, {
                get() {
                    return this.$data[key];
                },
                set(val) {
                    this.$data[key] = val;
                    
                    // 更新视图
                    this.observe(key);
                }
            })
        }
    }
    // 视图更新
    observe(key) {
        if (this.$watchEvent[key]) {
            this.$watchEvent[key].forEach((item, index) => {
                item.update();
            })
        }
    }
    // 节点监听
    addWatch(key, item, attr) {
        let watch = new Watcher(this, key, item, attr)
        if(this.$watchEvent[key]) {
            this.$watchEvent[key].push(watch);
        } else {
            this.$watchEvent[key] = [];
            this.$watchEvent[key].push(watch);                           
        }
    }
    // 模板解析
    compile(node) {
        node.childNodes.forEach((item, index) => {
            if (item.nodeType === 1) {
                if (item.hasAttribute('@click')) {
                    let key = item.getAttribute('@click');
                    item.addEventListener('click', (event) => {
                        let fn = this.$options.methods[key].bind(this);
                        fn(event);
                    });
                }

                if (item.hasAttribute('v-model')) {
                    let key = item.getAttribute('v-model').trim();
                    if (this.hasOwnProperty(key)) {
                        item.value = this[key];
                        this.addWatch(key, item, 'value')
                    }
                    item.addEventListener('input', (event) => {
                        this[key] = item.value;
                    });
                }

                if (item.childNodes.length > 0) {
                    this.compile(item);
                }
            }
            if (item.nodeType === 3) {
                let reg = /\{\{(.*?)\}\}/g;
                let text = item.textContent;
                item.textContent = text.replace(reg, (match, key) => {
                    key = key.trim();
                    if (this.hasOwnProperty(key)) {
                        this.addWatch(key, item, 'textContent')
                    }
                    return this.$data[key.trim()];
                })
            }
        });
    }
}

class Watcher {
    constructor(vm, key, node, attr) {
        this.vm = vm;
        this.key = key;
        this.node = node;
        this.attr = attr;
    }
    update() {
        this.node[this.attr] = this.vm[this.key];
    }
}