1. 程式人生 > >Object.defineProperty() 以及 vue 中雙數據綁定的底層原理

Object.defineProperty() 以及 vue 中雙數據綁定的底層原理

ble tlist 效果 先來 pan 圖片 setter pla es5

Object是在javascript中一個被我們經常使用的類型,而且JS中的所有對象都是繼承自Object對象的。雖說我們平時只是簡單地使用了Object對象來存儲數據,並沒有使用到太多其他功能,但是Object對象其實包含了很多很有用的屬性和方法,尤其是ES5增加的方法,今天我們先探討一下Object.defineProperty()。 Object.defineProperty 給一個對象添加或者修改屬性 返回的是一個對象 語法 Object.defineProperty(obj, prop, descriptor) 參數 obj要在其上定義屬性的對象。 prop要定義或修改的屬性的名稱。
descriptor將被定義或修改的屬性描述符。 屬性描述符是一個對象,參數如下 value: 設置屬性值 writeable: 設置當前屬性是否允許被修改 configurable:設置當前屬性是否可以刪除 enumerable:設置當前屬性是否可被枚舉 getter---get() 當獲取屬性值的時候觸發的函數 setter---set() 當設置屬性的時候觸發的函數 註意:當使用了get()方法或者set()方法的時候就不能使用value和writable中的任何一個屬性否則會報錯 Vue-model底層原理 利用了es5中的object.defineProperty()方法中的set()get()屬性對數據進行劫持
我們先來寫一個簡單版的mvvm 結構層為
<body>
    <input type="text" id="txt">
    <p id="msg"></p>
</body>
行為層代碼為
    var obj = {message:"mvvm-demo"}
    var oTxt = document.getElementById("txt");
    var oMsg = document.getElementById("msg");

    Object.defineProperty(obj,"message",{
        configurable:true,
        enumerable:true,
        set:function(newStr){
            oMsg.innerText = newStr;
        }
    })

    oTxt.addEventListener("input",function(){
        obj.message = oTxt.value;
    })

  實現效果圖如下

技術分享圖片

接下來我們來模擬一下vue中MVVM底層原理

結構層代碼

<body>
    <div id="app">
        <input type="text" v-model="message">
        <p>{{message}}</p>
     

        <input type="text" v-model="name">
        <p>{{name}}</p>
    </div>
    
</body>

行為層代碼

function Vue(options){
    this.el = document.querySelector(options.el);
    this.data = options.data;
    //是數據層和view之間的一個映射關系這裏面存放這個需要雙數據綁定的元素和當前元素的一些特征
    this.viewModel = {};

    this.init(this.data);
    this.eventType(this.el);
}

Vue.prototype = {
    constructor:Vue,
    init:function(obj){
        var _this = this;
        Object.keys(obj).forEach(function(key){
            var value = obj[key];
            //將當前key值作用的元素一些特征保存在這個數組裏面
            _this.viewModel[key] = {
                _directive:[]
            }
            console.log(_this.viewModel)
            Object.defineProperty(obj,key,{
                configurable:true,
                enumerable:true,
                get:function(){
                    return value;
                },
                set:function(newValue){
                    if(newValue!=value){
                        value = newValue;
                        //數據更新
                        _this.viewModel[key]._directive.forEach(function(item){
                            item.update();
                        })
                    }
                }
            })
        })
    },
    eventType:function(root){
        var childs = root.children;
        var _this = this;
        for(var i=0;i<childs.length;i++){
            if(childs[i].hasAttribute("v-model") && childs[i].tagName == "INPUT"){
                childs[i].addEventListener("input",(function(i){
                    var attr = childs[i].getAttribute("v-model");
                    _this.viewModel[attr]._directive.push(new watch(
                        childs[i].tagName,
                        "value",
                        childs[i],
                        _this,
                        attr
                    ))
                    return function(){
                        //vm.data.message = 
                        _this.data[attr] = childs[i].value;
                    }

                })(i))
            }
            
                                
            if(childs[i].innerText.replace(/\{\{|\}\}/g,"")){
                var dataAttr = childs[i].innerText.replace(/\{\{|\}\}/g,"");
                _this.viewModel[dataAttr]._directive.push(new watch(
                        childs[i].tagName,
                        "innerText",
                        childs[i],
                        _this,
                        dataAttr
                ))
            }
        }
    }
}


//更新數據的方法  標簽的名稱  當前元素  當前元素的屬性   vue的實例  data屬性
function watch(name,exp,el,vm,attr){
    this.name = name;
    this.exp = exp;
    this.el = el;
    this.vm = vm;
    this.attr = attr;
    this.update();
}
watch.prototype.update = function(){
    this.el[this.exp] = this.vm.data[this.attr];
}



var vm = new Vue({
    el:"#app",
    data:{
        message:"demo",
        name:"mvvm"
    }
})

  

效果實現圖如下

技術分享圖片




Object.defineProperty() 以及 vue 中雙數據綁定的底層原理