首页 > 技术文章 > 双向绑定数据的实现(new Proxy 版本)

adouwt 2020-09-13 13:12 原文

之前有用 Object.defineProperty 实现了一个双向绑定,过于繁琐,这是 基于 Proxy 实现了一版,也优化了模版渲染的部分,代码更简洁。

调用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>myvue</title>
    <script src="myvue.js"></script>
</head>
<body>
    <div id="app">
        姓名:{{name}}
        <div>
            年龄:{{age}}
            <p>
                身高:{{height}}
            </p>
            <p>
                体重:{{weight}}
            </p>
        </div> 
        <div>
            双向数据绑定:<input type="text" v-model="inputData">
            {{inputData}}
        </div>
    </div>
    <script>
        let vm = new Myvue({
            el: '#app',
            data: {
                name: 'lili',
                age: 18,
                height: '170cm',
                weight: '65kg',
                inputData: '双向数据绑定'
            }
        }) 
    </script>
</body>
</html>

myvue.js 核心实现

// 基础知识点扫盲
// - 自定义事件,和调用自定义事件 new CustomEvent   this.dispathEvent
// - 监听拦截数据方法 Proxy, new Proxy(data, set(target, prop, newValue){})
// - EventTarget  是一个 DOM 接口,由可以接收事件

class Myvue extends EventTarget{
    constructor(option) {
        super();
        this.option = option
        this._data = this.option.data;
        this.el = document.querySelector(option.el);
        this.observe(this._data); // 监听观察数据
        this.complieNode(this.el) // 编译节点,渲染数据
    }

    observe(data) {
        let _this = this;
        // 每次访问、设置等 data 的时候,都会经过 proxy 代理过后的对象,便于拦截,监听等操作
        this._data = new Proxy(data, {
            set(target, prop, newValue){
                // 监听到新值变化
                // 如何通知视图修改数据?
                console.log(newValue);
                // 自定义事件
                let event = new CustomEvent(prop, {
                    detail: newValue
                });
                _this.dispatchEvent(event);
                return Reflect.set(...arguments)  // 设置值
            }
        })

    }
    complieNode(el) {
        let child = el.childNodes;
        // 类数组转成数组
        [...child].forEach(element => {
            if(element.nodeType === 3) {
                // console.log('文本节点')
                let textContent = element.textContent;
                // console.log(textContent) {{name}} {{age}}
                let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
                if(reg.test(textContent)) {
                    let $1 = RegExp.$1;
                    // console.log($1); name age
                    if(this._data[$1]) {
                        element.textContent = textContent.replace(reg, this._data[$1]);
                        // 绑定自定义事件,自定义事件名就是 双向数据绑定的值 name age inputData
                        this.addEventListener($1, e => {
                            console.log(e)
                            element.textContent = textContent.replace(reg, e.detail);
                        })
                    }
                }
            } else if(element.nodeType===1){
                // console.log('元素节点');
                let attr = element.attributes;
                if(attr.hasOwnProperty('v-model')) {
                    let keyName = attr['v-model'].nodeValue; // inputData
                    element.value = this._data[keyName];
                    // 绑定input 事件, 修改input内容时候,同时更新掉所有 {{inputData}} 数据, 即监听 inputData 
                    element.addEventListener('input', e => {
                        this._data[keyName] = element.value ;
                        // this.observe(this._data);
                    })
                }
                this.complieNode(element)
            }
        });
    }
}


推荐阅读