美文网首页
手撸Vue双向数据绑定(3.0之前版本)

手撸Vue双向数据绑定(3.0之前版本)

作者: 敦敦实实 | 来源:发表于2019-04-08 13:49 被阅读0次

我的博客主页:笔头博客

Vue 3.0 马上就要发布了,3.0之前使用Object.defineProperty 这个api来实现,3.0采用新的API Proxy来实现。
记得在来现在公司面试时大神(一位资深大牛,我的朋友,我的行业领路人)问我能不能手写双向数据绑定。
转眼间已经一年半了,天资愚钝的我终于可以手撸双向数据绑定

首先我们要了解什么是双向数据绑定

简单的说 双向数据绑定就像数据库的互为主从的配置,大家数据共享,都不藏着掖着,即:视图数据改变时Model中的数据也会相应变化,当Model中的数据改变时视图也会改变,两者变化同步。

社会是现实的,编程也是如此,如果没有好处我们为什么要用双向数据绑定

好处当然很多了,首先我们不需要挨个去获取dom节点然后在去获取数据,其次数据变化时我们也不需要在去找对应的节点然后去更新,想一想这省去了程序猿多少的麻烦

下面我们开始手撸一下双向数据绑定

1、我们先了解一下双向数据绑定的整个流程

67.png

由上图可以了解到,整个双向数据绑定的过程是由:劫持监听部分、解析指令部分、更新视图,三部分组成,
劫持监听部分:是整个双向数据绑定的核心,它的作用是整理初始化时传入的数据,然后在数据更改时触发视图更新事件。
指令解析部分:遍历所有的DOM节点并按照不同的指令分配不同的监听。
更新部分:顾名思义就是更新视图

2、实现过程

2.1 基础架构

<body>
    <div id="myApp">
        <div v-text="message"></div>
        <input v-model="message" text="input" />
    </div>
    <script>
        class Vue {
            constructor(options) {
                this.el = document.querySelector(options.el);
                this.data = options.data;
                // 存储 监听
                this.events = {};
            }
            // 数据劫持
            observer(data) {
                
            }
            // 解析指令
            compile(dom) {
                
            }
        }
        // 监听器
        class Watcher {
            
        }
    </script>
    <script>
        new Vue ({
            el: '#myApp',
            data: {
                message: '手撸Vue'
            }
        })
    </script>
</body>

2.2 初始化指令,将所有数据都准备一个数组来存放所有绑定该数据节点

observer(data) {
    for(let key in data) {
        // 初始化 指令
        this.events[key] = [];
    }
}

2.3 递归遍历所有节点,对绑定指令的节点放入所对应的数据数组中

compile(dom) {
    // 获取节点下子节点
    const childrens = dom.children;
    for(let i = 0; i < childrens.length; i++) {
        const dom = childrens[i];
        if(dom.hasAttribute('v-text')) {
        }
        if(dom.hasAttribute('v-model')) {
        }
        // 递归 解析所有节点
        if(dom.children.length > 0) {
            this.compile(dom);
        }
    }
}

2.4 准备监听器,并初始化view

class Watcher {
    constructor(dom, attr, vue, key) {
        this.dom = dom;
        this.attr = attr;
        this.vue = vue;
        this.key = key;
        this.update();
    }
    // 更新视图
    update() {
        this.dom[this.attr] = this.vue.data[this.key];
    }
}

2.5 绑定监听

if(dom.hasAttribute('v-text')) {
    const attr = dom.getAttribute('v-text');
    // 插入 监听器
    this.events[attr].push(new Watcher(dom, 'textContent', this, attr));
}
if(dom.hasAttribute('v-model')) {
    const attr = dom.getAttribute('v-model');
    // 插入 监听器
    this.events[attr].push(new Watcher(dom, 'value', this, attr));
}

到这里我们就已经能够在页面看到数据了


68.png

2.5 接下来就是监听输入框输入事件,并更新Model数据,在这里我们要使用input的input事件,因为change事件只有在失去焦点是触发,显然不是我们所需要的。

// 监听 输入框事件
dom.addEventListener('input', () => {
        this.data[attr] = dom.value;
})

2.6 这是最后一步,数据劫持然后更新视图

let val = this.data[key];
// 双向数据绑定的精髓
Object.defineProperty(this.data, key, {
    get: () => val,
    set: newVal => {
        if(newVal !== val) {
            val = newVal;
            // 触发 所有绑定该指令的节点更新
            this.events[key].forEach(watch => {
                watch.update();
            })
        }
    }
})

完整代码

<!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>手撸Vue</title>
</head>
<body>
    <div id="myApp">
        <div v-text="message"></div>
        <input v-model="message" text="input" />
    </div>
    <script>
        class Vue {
            constructor(options) {
                this.el = document.querySelector(options.el);
                this.data = options.data;
                // 存储 监听
                this.events = {};
                this.observer(this.data);
                this.compile(this.el);
            }
            // 数据劫持
            observer(data) {
                for(let key in data) {
                    // 初始化 指令
                    this.events[key] = [];
                    let val = this.data[key];
                    // 双向数据绑定的精髓
                    Object.defineProperty(this.data, key, {
                        get: () => val,
                        set: newVal => {
                            if(newVal !== val) {
                                val = newVal;
                                // 触发 所有绑定该指令的节点更新
                                this.events[key].forEach(watch => {
                                    watch.update();
                                })
                            }
                        }
                    })
                }
            }
            // 解析指令
            compile(dom) {
                // 获取节点下子节点
                const childrens = dom.children;
                for(let i = 0; i < childrens.length; i++) {
                    const dom = childrens[i];
                    if(dom.hasAttribute('v-text')) {
                        const attr = dom.getAttribute('v-text');
                        // 插入 监听器
                        this.events[attr].push(new Watcher(dom, 'textContent', this, attr));
                    }
                    if(dom.hasAttribute('v-model')) {
                        const attr = dom.getAttribute('v-model');
                        // 插入 监听器
                        this.events[attr].push(new Watcher(dom, 'value', this, attr));
                        // 监听 输入框事件
                        dom.addEventListener('input', () => {
                            this.data[attr] = dom.value;
                        })
                    }
                    // 递归 解析所有节点
                    if(dom.children.length > 0) {
                        this.compile(dom);
                    }
                }
            }
        }
        // 监听器
        class Watcher {
            constructor(dom, attr, vue, key) {
                this.dom = dom;
                this.attr = attr;
                this.vue = vue;
                this.key = key;
                this.update();
            }
            // 更新视图
            update() {
                this.dom[this.attr] = this.vue.data[this.key];
            }
        }
    </script>
    <script>
        new Vue ({
            el: '#myApp',
            data: {
                message: '手撸Vue'
            }
        })
    </script>
</body>
</html>

小结:技术日新月异,小编刚刚掌握双向数据绑定的原理,3.0就要弃用了,面对当前的局势你难道还要停滞不前么?

相关文章

网友评论

      本文标题:手撸Vue双向数据绑定(3.0之前版本)

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