数据劫持,订阅者模式,双向绑定

数据劫持,订阅者模式,双向绑定

//index.html文件
<!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>Document</title>
    <script src="./cvue.js"></script>
</head>
<body>
    <div id="app">
        {{message}}
        <!-- <p>{{message}}</p> -->
        <input type="text" c-model="name" />{{name}}
    </div>
</body>
</html>
<script>

    let vm = new Cvue({
        el : '#app',
        data : {
            message : '测试数据',
            name : 'cher'
        }
    })

    vm._data.message = '11';
    vm._data.name = 'chun'

</script>


 

//cvue.js文件
class Cvue{
    constructor(options){
        this.$options = options; //为什么加 $;因为防止冲突,和vm里面的数据做区分
        this._data = options.data;
        this.observer(this._data); //数据劫持
        this.compile(options.el);
    }
    observer(data){
        Object.keys(data).forEach(key => {  //为每个data属性进行数据劫持
            let value = data[key];
            let dep = new Dep();
            Object.defineProperty(data,key,{
                configurable : true,
                enumerable : true,
                get(){
                    if(Dep.target){ //添加一个订阅者,必须要触发get (2)
                        dep.addSub(Dep.target)
                    }
                    return value;
                },
                set(newValue){
                    value = newValue;
                    dep.notify(newValue);  //提醒订阅者 (3)
                }
            })
        })
    }
    compile(el){
        let element = document.querySelector(el); //获取 #app节点
        this.compileNode(element);
    }
    compileNode(element){
        let childNodes = element.childNodes;    //获取 #app下的所有子节点
        Array.from(childNodes).forEach((node) => {
            if(node.nodeType == 3){ //文本节点
                let nodeContent = node.textContent; //文本内容
                let reg = /\{\{\s*(\S*)\s*\}\}/; //匹配文本节点
                if(reg.test(nodeContent)){
                    node.textContent = this._data[RegExp.$1]; //替换文本
                    new Watcher(this,RegExp.$1,newValue => {
                        node.textContent = newValue;  //更新视图
                    }); //初次渲染订阅者,this指Cvue (1)
                }   
            }else if(node.nodeType == 1){ //标签节点
                //双向绑定 c-model
                let attrs = node.attributes;
                Array.from(attrs).forEach(attr => {
                    let attrName = attr.name;
                    let attrValue = attr.value;
                    if(attrName.indexOf('c-') == 0){
                        attrName = attrName.substr(2);
                        if(attrName == 'model'){
                            node.value = this._data[attrValue]; //把data里的值放到c-model的值里
                        }
                        node.addEventListener('input',e => { //c-model值改变的时候data里的值也随着改变
                            this._data[attrValue] = e.target.value
                        })
                        new Watcher(this,attrValue,newValue => { //双向绑定
                            node.value = newValue; 
                        }); 
                    }
                })
            }
            if(node.childNodes.length > 0){ //递归
                this.compileNode(node);
            }
        })
    }
}

class Dep{  //发布者
    constructor(){
        this.subs = []; //要订阅的数据
    }
    addSub(sub){
        this.subs.push(sub); //添加那些订阅者
    }
    notify(newValue){ //提醒所有订阅者更新
        this.subs.forEach(v => {
            v.update(newValue); 
        })
    }
}

class Watcher{ //订阅者
    constructor(vm,exp,cb){
        Dep.target = this; //为了判断是否有watcher;这个this就是订阅者实例化的类
        this.cb = cb;
        vm._data[exp]; //触发get
        Dep.target = null; //不在重复添加
    }
    update(newValue){
        this.cb(newValue);
        console.log('更新了');   
    }
}

posted @ 2019-05-17 17:42 cher。 阅读(...) 评论(...) 编辑 收藏

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值