ES6高阶2:数据劫持、发布订阅者模式

什么是数据劫持?

举一个vue的例子

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
    <div id="app">
        <!-- 插值表达式 -->
        {{ message }}
        <input v-model="message" />
        <div v-html="htmlData"></div>
    </div>
</body>
<script>
    // 1.怎么实现数据驱动?把数据渲染到视图里;
    let vm = new Vue({
        el: "#app",
        data: {
            message: "测试数据",
            htmlData: "<h1>标题</h1>"
        }
    })
    // 2.二次渲染;数据劫持;
    // console.log(vm._data)
    setTimeout(function () {
        vm._data.message = "修改的数据"
    }, 1000);
</script>

</html>

数据劫持就是修改内容后,差值表达式能够知道修改并作出变化

1 通过defineProperty实现数据劫持

defineProperty创建对象、修改对象

let obj = {
        name:"张三"
    }
let value = obj["name"]
Object.defineProperty(obj,"name",{
    configurable:true,
    enumerable:false,
    get(){
        return value;
    },
    set(newValue){
        console.log("set...",newValue);
        value = newValue;
    }
})
console.log(obj);
obj.name = "王五";

// configurable: false,外部无法获取对象属性
for (let key in obj) {
    console.log(key)
}
delete obj.name;

参数:

  1. enumerable 是否可枚举 默认为true
    为false时外部就无法获取这个对象内的属性
  2. configurable 是否可删除
    当configurable: true时,对象属性就无法被删除

1.1 利用自定义事件实现vue数据劫持

class Vue extends EventTarget {
    constructor(opts) {
        super();
        this.opts = opts;
        this._data = this.opts.data;
        this.observe(this._data);
        this.compile();
    }
    observe(data) {
        for (let key in data) {
            let _this = this;
            let value = data[key];
            Object.defineProperty(data, key, {
                configurable: true,
                enumerable: true,
                get() {
                    console.log("get...");
                    return value;
                },
                set(newValue) {
                    console.log("set...");
                    // 触发事件更新;
                    let event = new CustomEvent(key, {
                        detail: newValue
                    });
                    _this.dispatchEvent(event);
                    value = newValue;
                }
            });
        }
    }

    // 初次编译到dom里;
    compile() {
        let ele = document.querySelector(this.opts.el);
        let childNodes = ele.childNodes;
        this.compileNode(childNodes);
    }
    compileNode(childNodes) {
        childNodes.forEach(node => {
            if (node.nodeType === 3) {
                // 获取文本的内容;
                let textContent = node.textContent;
                // 分组匹配插入表达式里的变量
                let reg = /\{\{\s*([^\{\}\s*]+)\s*\}\}/g;
                if (reg.test(textContent)) {
                    let $1 = RegExp.$1;
                    console.log(this.opts.data[$1]);
                    node.textContent = node.textContent.replace(reg, this._data[$1]);
                    // 绑定自定义事件;
                    this.addEventListener($1, e => {
                        let newValue = e.detail;
                        let oldValue = this._data[$1];
                        let updateReg = new RegExp(oldValue);
                        node.textContent = node.textContent.replace(updateReg, newValue);
                    });
                }
            } else if (node.nodeType === 1) {
                // 判断节点内是否有子节点;
                if (node.childNodes.length > 0) {
                    // 有子节点
                    this.compileNode(node.childNodes);
                }
            }
        });

    }
}

1.2 通过发布订阅模式实现数据劫持

什么是发布订阅模式?

  • 分别定义一个收集器订阅者
  • 通过收集器收集订阅者
// 收集器;
class Dep {
    constructor() {
        this.subs = [];
    }
    addSub(sub) {
        this.subs.push(sub);
    }
    notify(newValue) {
        this.subs.forEach(sub => {
            sub.update(newValue);
        });
    }
}
// 订阅者;
class Watcher {
    constructor(data, key, cb) {
        Dep.target = this;
        data[key];
        this.cb = cb;
        Dep.target = null;
    }
    update(newValue) {
        this.cb(newValue);
    }
}

数据劫持

class Vue {
    constructor(opts) {
        this.opts = opts;
        this._data = this.opts.data;
        this.observe(this._data);
        this.compile();
    }
    observe(data) {
        for (let key in data) {
            let dep = new Dep();
            let value = data[key];
            Object.defineProperty(data, key, {
                configurable: true,
                enumerable: true,
                get() {
                    // 收集订阅者
                    console.log("get...");
                    // 如果有实例,就将实例push到收集器里
                    Dep.target && dep.addSub(Dep.target);
                    return value;
                },
                set(newValue) {
                    console.log("set...");
                    dep.notify(newValue);
                    value = newValue;
                }
            });
        }
    }


    // 初次编译到dom里;
    compile() {
        let ele = document.querySelector(this.opts.el);
        let childNodes = ele.childNodes;
        this.compileNode(childNodes);
    }
    compileNode(childNodes) {
        childNodes.forEach(node => {
            if (node.nodeType === 3) {
                // 获取文本的内容;
                let textContent = node.textContent;
                let reg = /\{\{\s*([^\{\}\s*]+)\s*\}\}/g;
                if (reg.test(textContent)) {
                    let $1 = RegExp.$1;
                    node.textContent = node.textContent.replace(reg, this._data[$1]);
                    // 触发
                    new Watcher(this._data, $1, (newValue) => {
                        let oldValue = this._data[$1];
                        let updateReg = new RegExp(oldValue);
                        node.textContent = node.textContent.replace(updateReg, newValue);
                    });


                }
            } else if (node.nodeType === 1) {
                let attrs = node.attributes;
                Array.from(attrs).forEach(attr => {
                    let attrName = attr.name;
                    let attrValue = attr.value;
                    if (attrName.indexOf("v-") === 0) {
                        if (attrName === "v-model") {
                            // console.log("有v-model指令")
                            node.value = this._data[attrValue];
                            node.addEventListener("input", e => {
                                // 触发视图更新;
                                this._data[attrValue] = e.target.value;
                            });
                        }
                    }
                });
                // 判断节点内是否有子节点;
                if (node.childNodes.length > 0) {
                    // 有子节点
                    this.compileNode(node.childNodes);
                }
            }
        });
    }
}
// 实现 v-html 指令;

// 收集器;
class Dep {
    constructor() {
        this.subs = [];
    }
    addSub(sub) {
        this.subs.push(sub);
    }
    notify(newValue) {
        this.subs.forEach(sub => {
            sub.update(newValue);
        });
    }
}
// 订阅者;
class Watcher {
    constructor(data, key, cb) {
        Dep.target = this;  // 通过Dep的静态属性target指向watcher实例,判断收集器是否与其关联
        data[key];
        this.cb = cb;
        Dep.target = null;
    }
    update(newValue) {
        // 触发回调
        this.cb(newValue);
    }
}

2 改进:proxy 劫持数据

defineProperty劫持属性有一些缺点,比如说:

  1. 如果对象里有数组,数组更改无法劫持
  2. 新增对象属性无法触发视图更新

proxy设置修改属性

不需要再进行对象循环,直接通过内置对象在这里插入代码片方法中的target, keynewValue参数获取修改属性

    let obj = {
        name:"张三",
        age:20
    }

    let newObj = new Proxy(obj,{
        get(target,key){
            console.log("get...");
            return target[key];
        },
        set(target,key,newValue){
            console.log("set...",newValue);
            target[key] = newValue;
        }
    })
    newObj.name = "修改的";

改进之后的vue

class Vue {
    constructor(opts) {
        this.opts = opts;
        this._data = this.opts.data;
        this.observe(this._data);
        this.compile();
    }

    observe(data) {
        let dep = new Dep();
        this._data = new Proxy(data, {
            get(target, key) {
                if (Dep.target) {
                    dep.addSub(Dep.target);
                }
                return target[key];
            },
            set(target, key, newValue) {
                dep.notify(newValue);
                target[key] = newValue;
            }
        });
    }

    // 初次编译到dom里;
    compile() {
        let ele = document.querySelector(this.opts.el);
        let childNodes = ele.childNodes;
        this.compileNode(childNodes);
    }
    compileNode(childNodes) {
        childNodes.forEach(node => {
            if (node.nodeType === 3) {
                // 获取文本的内容;
                let textContent = node.textContent;
                let reg = /\{\{\s*([^\{\}\s*]+)\s*\}\}/g;
                if (reg.test(textContent)) {
                    let $1 = RegExp.$1;
                    node.textContent = node.textContent.replace(reg, this._data[$1]);
                    // 绑定自定义事件;
                    new Watcher(this._data, $1, (newValue) => {
                        // console.log("update",newValue);
                        let oldValue = this._data[$1];
                        let updateReg = new RegExp(oldValue);
                        node.textContent = node.textContent.replace(updateReg, newValue);
                    });


                }
            } else if (node.nodeType === 1) {
                // console.log("标签");
                let attrs = node.attributes;
                [...attrs].forEach(attr => {
                    let attrName = attr.name;
                    let attrValue = attr.value;
                    if (attrName.indexOf("v-") === 0) {
                        if (attrName === "v-model") {
                            // console.log("有v-model指令")
                            node.value = this._data[attrValue];
                            node.addEventListener("input", e => {
                                // 触发视图更新;
                                this._data[attrValue] = e.target.value;
                            });
                        }
                    }
                });
                // 判断节点内是否有子节点;
                if (node.childNodes.length > 0) {
                    // 有子节点
                    this.compileNode(node.childNodes);
                }
            }
        });
    }
}
// 实现 v-html 指令;

// 收集器;
class Dep {
    constructor() {
        this.subs = [];
    }
    addSub(sub) {
        this.subs.push(sub);
    }
    notify(newValue) {
        this.subs.forEach(sub => {
            sub.update(newValue);
        });
    }
}
// 订阅者;
class Watcher {
    constructor(data, key, cb) {
        Dep.target = this;
        data[key];
        this.cb = cb;
        Dep.target = null;
    }
    update(newValue) {
        this.cb(newValue);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值