数据双向绑定的原理分析

2020-02-18  本文已影响0人  祝家庄打烊

模板编译

1、所有的DOM操作,都放到内存fragment中执行(创建fragment内存空间,把所有的子元素都移动到fragment中包裹,操作完DOM元素后,在把fragment移动到根节点上)。

function compile(el,data){
    var fragment = document.createDocumentFragment();
    var first = null;
    while(first=el.firstChild){
        fragment.appendChild(first);
    }
    renderDom(fragment);
    el.appendChild(fragment);
}

2、HTML中存在v-html和{{}}内容编译成对应的数据(遍历fragment子元素,判断是元素节点还是文本节点,文本节点去掉{{}}找到对应的数据来源,在给相应的元素赋值)
区分DOM节点

function renderDom(fragment){
    var _that = this;
    Array.from(fragment.childNodes).forEach(function(item){
        if(item.nodeType==1){
            //元素节点
            renderElement(item);
            if(item.childNodes.length>0) renderDom(item);
        }else{
            //文本节点
            renderText(item)
        };
    })
}

遍历元素节点的属性值,找到对应的v-

function renderElement(dom){
    Array.from(dom.attributes).forEach(function(attr){
        if(attr.name=="v-html"||attr.name=="v-text"||attr.name=="v-model"){
            eleRender(attr.value,dom);
        }
    })
}

元素节点赋值

function eleRender(value,dom){
    if(dom.tagName.toLocaleLowerCase()=="input"){
        dom.value = compileData(value);
    }else{
        dom.innerHTML = compileData(value);
    }
    
}

文本节点赋值

function renderText(text){
    var reg = /\{\{([^}]+)\}\}/;
    text.textContent.replace(reg,function(){
        new Watch(arguments[1],function(newValue){
            text.textContent = newValue;
        })
        text.textContent = compileData(arguments[1]);
    })
}

数据劫持

当数据data发生改变的时候,重新编译模板。(利用的是Object.defineProperty监听数据变化,watch方法对每个元素进行监听,发布订阅者模式订阅watch对象,发布watch对象中update方法)
1、Object.defineProperty监听数据变化(是对象上的每个属性进行监听,而且要监听到数据的最底层)

function observer($data){
    if(!$data || typeof($data)!="object"){
        return false;
    }
    for(var key in $data){
        defineWatch($data,key,$data[key])
    }
}
function defineWatch($data,key,value){
    observer(value);
    //监听对象中某个属性的变化
    var subScript = new Subscribe();
    Object.defineProperty($data, key, {
        enumerable: true,
        configurable: true,
        get:function(){
            subScript.order(Subscribe.dep);
            return value;
        },
        set:function(newValue){
            value=newValue;
            subScript.push();
        }
    })
}

2、定义watch方法,对每个元素进行绑定,当监听到的数据改变时,触发watch方法,对元素重新赋值

function Watch(explor,callback){
    Subscribe.dep = this;
    this.oldValue = compileData(explor);
    Subscribe.dep = null;
    this.update = function(){
        var newValue = compileData(explor);
        console.log("newValue",newValue)
        if(this.oldValue!=newValue){
            callback(newValue)
        }
    }
}
image.png

3、定义发布订阅者模式,当获取数据的时候,表明元素正在进行赋值操作,这时,需要订阅watch对象,当改变数据的时候,表明赋值完成,这时,需要发布watch对象上的update方法,对监听到元素进行赋值操作。

function Subscribe(){
    this.store = [];
    this.order = function(watch){
        this.store.push(watch);
    }
    this.push = function(){
        if(this.store.length==0) return false;
        this.store.forEach(function(item){
            item && item.update()
        })
    }
}

项目案例

上一篇下一篇

猜你喜欢

热点阅读