手寫簡單實現vue數據綁定

index.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="word">
        <p v-bind="word"></p>
        <button v-click="sayHi">change model</button>
    </div>
</body>
<script src="myMvvm.js"></script>
<script src="watcher.js"></script>
</html>
<script>
  var app= new myMvvm({
                el: '#app',
                data: {
                    word:"hello world"
                },
                methods:{
                    sayHi:function () {
                        this.word="改變後"
                        console.log("點擊了")
                    }
                }
            })
</script>

watcher.js

/*
*   watcher,用來綁定更新函數,實現對DOM元素的更新:
*   由_obverse對數據監聽而觸發對DOM更新.
*   本函數包含對某個DOM的某個更新的任務和方法
*
* */

function watcher(name,el,vm,exp,attr){
    this.name=name;//指令名稱--這裏尚未用到
    this.el=el;//指令對應的DOM元素
    this.vm=vm;//指令所屬的myMVVM實例
    this.exp = exp;//指令對應的值--也就是指令所對應的data中的屬性名
    this.attr=attr;//綁定的屬性值。好比innerHTML

    /*
    *   更新DOM
    *   好比 H3.innerHTML = this.data.number; 當number改變時,會觸發這個update函數,保證對應的DOM內容進行了更新。
    * */
    this.update=function(){
        /*
        * /好比 H3.innerHTML = this.data.number; 當number改變時
        * */
        this.el[this.attr]=this.vm.$data[this.exp];
    }
    //實例該對象的時候執行更新dom方法
    this.update();

}

myMvvm.js

var  myMvvm=function(options){
    this.init(options);
}

myMvvm.prototype.init=function(options){
    /*
    * _binding保存着model與view的映射關係,也就是咱們前面定義的Watcher的實例。當model改變時,咱們會觸發其中的指令類更新,保證view也能實時更新
    * this._binding={
    *       'data中的某值':{
    *                    _directives[watcher.....]
    *                   }
    *  }
    *  也就是說:data中的某值發生了變化,就遍歷data中的某值對應_directives,執行每一個watcher裏面的更新方法來對DOM更新。實現一個值變化,多個dom跟着改變。
    * */
    this._binding={}
    //---------------------
    this.$options=options;
    this.$el=document.querySelector(options.el);
    this.$data=options.data;
    this.$methods=options.methods;
    this._obverse(this.$data);

    this._compile(this.$el);
}
//監聽數據發生變化
myMvvm.prototype._obverse=function(data){
    //若是data數據爲空
    if(!data){
        return ;
    }
    if(typeof data !=="object"){
        throw new Error("data必須是個對象");
    }
    var self=this;
    /*
    * 遍歷data中全部屬性
    * Object.keys(obj)函數返回一個由一個給定對象的自身可枚舉屬性組成的數組
    * */
    Object.keys(data).forEach(function (v,k) {
        /*
        * 遞歸執行本observer函數.
        * 判斷當前屬性對應的值可能仍是個對象。因此遞歸。
        * 本功能不完善,因此這個遞歸函數沒用,由於本功能沒對data數據中的對象進行完善,也就是本功能還不支持data屬性的值爲對象
        * */
        if(data[v] &&(typeof data[v]=='object')){self._obverse(data[v]);return;}

        //當前屬性的值
        var oldValue=data[v];

        /*
        *   按照前面的數據
        *   _binding = {
        *                word:{
        *                      _directives: [...watch實例]
        *                      }
        *             }
        *   這裏是先聲明一個空數組,之後全部和data中某值有關係的都會追加到相應的_directives中。
        *   爲什麼要在這裏聲明?
        *   由於這裏要根據_binding的key必須爲data中的屬性(鍵)
        *   那這個數組什麼時候纔有東西呢?
        *   解析指令(好比v-bind)的時候push進去的,由於解析指令的時候,會解析每個dom,而後解析出你的是點擊事件仍是修改文本內容。
        *   而後在生成watcher追加進去這個數組-之後全部和data中某值發生改變只要循環執行這個數組中watcher的update更新方法就能夠更新全部和這個data中的某值相關聯的dom
        * */
        self._binding[v]={
            _directives:[]
        }
        //獲取本data某屬性對應的_directives
        var binding= self._binding[v];

        /*爲對象全部屬性添加set和get方法--這樣可監聽數據變化
        * 不熟悉object.defineProperty方法的看:https://my.oschina.net/u/3112095/blog/1797618
        * */
        Object.defineProperty(data,v,{
            enumerable: true, // 可枚舉--可被for-in和Object.keys()枚舉。
            configurable: false, // 目標屬性不能被刪除和不能修再被改特性
            get:function () {
                //發現這個oldValue若是替換成data[v]會形成堆棧溢出。
                return oldValue;
            },
            set:function(value){
                if(oldValue==value)return;
                console.log("監聽到--設置值,值變化了");
                //發現這個oldValue若是替換成data[v]會形成堆棧溢出。
                oldValue=value;
                // 當data中某屬性改變時,觸發_binding['某值']._directives 中的綁定的Watcher類的更新--這樣實現一個data中的值發生改變,和它相關聯的dom更新。
                binding._directives.forEach(function(item){
                    item.update();
                })
            }
        });
    })
}


//解析指令(v-bind,v-model,v-click)等,並在過程當中對view和model進行綁定
//這裏參數root爲id爲app的Element元素,也就是咱們的根元素
//這裏的解析指令只作了簡單的解析--也就是data中的屬性不支持對象。
myMvvm.prototype._compile=function (root) {
    var _this=this;
    var nodes=root.children;
    //遍歷全部節點
    for(var i=0;i<nodes.length;i++){
        var node=nodes[i];
        //若是子節點還有節點,遞歸調用本函數
        //由於這個demo沒作解析data中屬性是對象的狀況--因此這個暫時沒什麼用
        if(node.children.length){this._compile(node)}
        //若是有v-click屬性,就給他添加監聽
        if(node.hasAttribute("v-click")){
            node.onclick=(function(){
                var attrVal=node.getAttribute("v-click");
                //bind是使data的做用域與method函數的做用域保持一致
                return  _this.$methods[attrVal].bind(_this.$data);
            })()
        }
        //v-model
        if(node.hasAttribute('v-model')&&(node.tagName=="INPUT" ||node.tagName=="TEXTAREA")){
            var attrVal=node.getAttribute('v-model');
            _this._binding[attrVal]._directives.push(new watcher(
                'input',
                node,
                _this,
                attrVal,
                'value'
            ));
            node.addEventListener('input',function () {
                    _this.$data[attrVal]=this.value;
                    console.log(_this.$data[attrVal])
            })
        }
        //v-bind 若是有v-bind屬性,咱們只要使node的值及時更新爲data中word的值便可
        if(node.hasAttribute("v-bind")){
            var attrVal=node.getAttribute("v-bind");
            _this._binding[attrVal]._directives.push(new watcher(
                'text',
                 node,
                _this,
                attrVal,
                'innerHTML'
            ))
        }
    }
}
相關文章
相關標籤/搜索