Object.defineProperty() 以及 vue 中双数据绑定的底层原理

Posted w-819

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Object.defineProperty() 以及 vue 中双数据绑定的底层原理相关的知识,希望对你有一定的参考价值。

 

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 中双数据绑定的底层原理的主要内容,如果未能解决你的问题,请参考以下文章

理解Object.defineProperty方法

242 Object.defineProperty

所有浏览器的 Object.defineProperty?

Object.defineProperty

深入理解Object.defineProperty

Object.defineProperty()