vue2.0响应式原理之Object.defineProperty

247 阅读6分钟

Object.defineProperty()

Object->defineProperty() 定义属性

  • (obj,prop,descriptor)括号里面有三个参数:

    1、需要被定义的属性对象obj

    2、定义的属性的名称

    3、描述符:

    ​ 即 descriptor 描述项集合 又叫配置集合 类似于:

    //这样一个对象
    {
       a:1,
    
       b:1
    
    }
    
  • 声明一个属性

    function defineProperty() {
    
      var _obj = {};
    
      Object.defineProperty(_obj, 'a', {
    
        // 描述符第一个配置项就是value
    
        value: 1
    
      });
    
      return _obj
    
    }
    
    // 声明一个obj
    
    var obj = defineProperty();
    
    // 通过上面这种方式可以将默认值定下来
    
    console.log(obj);//{a: 1}
    

    **注:**相当于声明一个函数,在里面有一个空对象,Object.defineProperty 做的实际上就是给空对象增加一个 a 的属性,然后给其相应的配置

  • 声明多个属性

    function defineProperty() {
        var _obj = {};
    
        // 声明多个属性
        Object.defineProperties(_obj, {
            a: {
                value: 1
            },
            b: {
                value: 2
            }
        });
    
        return _obj;
    }
    //声明一个obj
    var obj = defineProperty();
    //通过上面这种方式可以将默认值定下来
    console.log(obj);//{a: 1, b: 2}
    
    obj.a = 5;
    console.log(obj);//{a:1,b:2}
    //发现结果的属性值不可修改
    
    for(var k in obj){
        console.log(k+':'+obj[k]);  //无反应
     	//发现:属性也不可枚举
    }
    
    delete obj.a;
    console.log(obj); //结果发现 不可删除 结果:{a:1,b:2}
    
    // 如果用 Object.defineProperty 的一系列的方法来对属性进行操作,发现没有用
    // 结果:只要用 Object.defineProperty 定义后的属性,不可以修改,删除,枚举
    

    补充:

    1、枚举: JavaScript中不存在枚举类型,通俗的说枚举就是去拿对象的key值,能不能拿到key值,就说可不可以枚举,比如:

    {
        a:1,
        b:2
    }
    //去拿a 对应的值 1 ,能拿到说嘛可以枚举,反之,不能。
    
  • 在操作过程中,我们总有需要修改属性的时候,那么怎么修改属性呢?

    function defineProperty() {
        var _obj = {};
    
        // 声明多个属性
        Object.defineProperties(_obj,{
            a:{
                value:1,
                writable:true,//是否可写
                enumerable:true,//是否可枚举
                configurable:true//是否删除
            },
            b:{
                value:2
            }
        });
    
        return _obj;
    }
    
    var obj = defineProperty();
    
    //每一个对象的配置项里面有一个 writale(是否可写?)属性 默认值是flase ,修改为true
    
    obj.a=5;
    obj.b=6;
    console.log(obj);//{a: 5, b: 2}
    
    //enumerable (可否枚举) 默认值flase ,修改为true
    
    for(var k in obj){
        console.log(k+':'+obj[k]); //a:5
    } 
    
    //configurable (是否可删除) 默认值flase ,修改为true
    
    delete obj.a;
    console.log(obj); //{b: 2}
    

    一般来说,在一个项目下:

    conf -> 一般指文件夹

    config -> 一般指文件

    configurable -> 指可配置的 可操作的

  • get和set

    //又比如:有一个对象obj
    var obj = {};
    obj.a = 1;
    console.log(obj.a);//1
    
    /*
    实际上就是从 obj对象中中取出属性 a 的那个键值 但是我们往往觉得这样的东西很肤浅 ,不深度,往往我们在获取属性或者赋值的时候  有一些额外的操作 比如:
     this.data.a = 1 的时候 我们希望在视图部分 在HTML某个标签中显示'1'  原本是'0' 但是
     我们一设置this.data.a = 1 的时候,这个地方需要变为'1'
     需要这样的效果,应该怎么做呢?
    这个时候就要用到getter 和setter 的内置方法
    */
    
    function defineProperty() {
        var _obj = {};
        
        //每一个属性定义的时候  都会产生一个叫 getter 和setter 的机制。 
        // 这两个内置的方法本身是存在的 不写代码也存在。
        // 这两个方法留给我们是做什么的呢?
        // 是让我们对属性进行各种操作的时候的反应,特别是对属性获取和重新赋值的时候,应该进行怎么样的程序逻辑
        
        Object.defineProperties(_obj,{
            a:{
                //get
                get(){
    
                },
                set(newVal){
    
                }
            },
            b:{
    
            }
        })
    }
    
  • 实现get和set的使用

    function defineProperty() {
        var _obj = {};
        /*
           每一个属性定义的时候,都会产生一个叫getter 和setter的机制,这两个内置的方法本身是存在的,不写代码也存在
           这两个方法留给我们是做什么的呢?
              是让我们对属性进行各种操作的时候的反应,特别是对属性获取和重新赋值的时候,应该进行怎么样的程序逻辑
        */
        Object.defineProperties(_obj,{
            a:{
                //get
                get(){
                    
                },
                set(newVal){
                    //一设置的时候  内部的代码
                    a = newVal;
                    var oP = document.getElementsByTagName('p')[0];//p标签的DOM节点
                    oP.innerHTML = a;
                }
            },
            b:{
    
            }
        })
        return _obj;
    }
    
    //当我们一设置,就要自动的在HTML中设置上
    var obj = defineProperty();
    console.log(obj); 
    obj.a = 1; //页面就会显示 1 
    
  • 数据劫持的自我理解

    function defineProperty() {
         var _obj = {};
    
         Object.defineProperties(_obj, {
            a: {
                //get
                get() {
                    //get的时候操作
                    return '"a"\'s value is ' + a + '.';
                },
                set(newVal) {
                    //set的时候的操作
                    console.log('The value "a" has been designed a new value "' + newVal + '".');
                }
            },
            b: {
    
            }
        })
        return _obj;
    }
    //数据劫持
    var obj = defineProperty();
    console.log(obj.a);//  本身想返回  1   但是却  返回"a"'s value is1.
    obj.a = 1; //给一个属性设置值  本身不返回值  但是却返回 The value "a" has been designed a new value "1".  
    
    /*
    以上就是数据劫持: 
    	对一个对象,它的取值和设置值,有一系列的配置和阻止的方法,这些东西就叫做对一组数据的属性的劫持
    
    数据劫持:把一个对象里的属性,进行可配置,可写和可枚举的配置,通过get和set方法对存取值进行逻辑上的扩展
    
    */
    
  • get、set、value、writable的理解

    function defineProperty() {
        var _obj = {};
    
        Object.defineProperties(_obj, {
            a: {
                 value:1,
                 writable:true,
                //如果此处选择声明 a,并设置value值为1 或者 writable设置为true,下面log处就会报错
                
                //test.js:251 Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object> at Function.defineProperties 
    
    
                /*
                	为什么会报错呢?
                     规定:value和writable任意一项的出现都不可在下面使用get或者set方法
                    
                    如果a对象里面不存在get、set、value、writable,只存在enumerable和configurable就叫做纯数据描述符;
                    当产生了value、writable、get、set的话、就有一个特殊的要求,value和writable任意一项的出现,都不可在下面使用get或者set方法
                    也就是说value和writable任意一项跟get和set的任意一项不能同时出现
                    所以报错了
    			*/
    
                
                get() {
                    //get的时候操作
                    return '"a"\'s value is ' + a + '.';
                },
                set(newVal) {
                    //set的时候的操作
                    console.log('The value "a" has been designed a new value "' + newVal + '".');
                }
            },
            b: {
    
            }
        })
    
        return _obj;
    }
    
    var obj = defineProperty();
    
    console.log(obj.a);
    obj.a = 1; 
    
  • defineProperty对数据进行操作

    //defineProperty本身不具备对数组进行操作的,有没有办法对数组进行操作呢?
    function DataArr() {
        var _val = null;
        var _arr = [];//最后将arr抛出去,并运用方法拿到他
    
        //也就是说每一次都需要先把val拿到,然后放到arr中,怎么做呢?
        //this 指向 dataArr 对象,在dataArr的下面去造一个个的属性:名称叫val
        
        Object.defineProperty(this,'val',{
            get:function(){
                return _val;
            },
            set:function(newVal){
                _val = newVal;
                _arr.push({
                    val:_val
                });
                console.log('A new value "'+_val+'" has been pushed to _arr');
            }
        });
    
        this.getArr = function(){
            return _arr;
        }
    }
    
    var dataArr = new DataArr();
    
    dataArr.val = 123;
    dataArr.val = 234;
    console.log(dataArr.getArr());
    //A new value "123" has been pushed to _arr
    //A new value "234" has been pushed to _arr
    
    /*
        (2) [{…}, {…}]
            0: {val: 123}
            1: {val: 234}
            length: 2
            __proto__: Array(0)
    */
    
  • 下面进行一个简单的 计算器 demo

    //index.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style type="text/css">
            .btn-group button.current{
                background-color:orange;
                color: #fff;
            }
        </style>
    </head>
    <body>
        <!-- 我们在html中有个p标签 -->
        <!-- <p>0</p> -->
        <!-- 计算器demo -->
        <div class="J_calculator">
            <div class="result">0</div>
            <div class="input-group">
                <input type="text" value="0" class="f-input"><br>
                <input type="text" value="0" class="s-input">
            </div>
            <div class="btn-group">
                <button data-field="plus" class="current">+</button>
                <button data-field="minus">-</button>
                <button data-field="mul">*</button>
                <button data-field="div">/</button>
            </div>
        </div>
        <script src="js/index.js"></script>
        <!-- <script src="js/test.js"></script> -->
    </body>
    </html>
    
    //index.js
    class Compute {
    
        plus(a,b){
            return a+b;
        }
    
        minus(a,b){
            return a - b;
        }
    
        mul(a,b){
            return a * b;
        }
    
        div(a,b){
            return a / b;
        }
    }
    
    class Calculator  extends  Compute{
        constructor(doc) {
            super();
            //拿到外层盒子
            const oCal = doc.getElementsByClassName('J_calculator')[0];
    
            //拿到两个input
            this.fInput = oCal.getElementsByTagName('input')[0];
            this.sInput = oCal.getElementsByTagName('input')[1];
    
            //做事件代理用
            this.oBtnGroup = oCal.getElementsByClassName('btn-group')[0];
            //所有button集合
            this.oBtnItems = this.oBtnGroup.getElementsByTagName('button');
    
            this.oResult = oCal.getElementsByClassName('result')[0];
    
            this.data = this.defineData();//一组数据
    
            this.btnIdx = 0;//button初始值
    
            console.log(this.data);
    
        }
    
        init() {
            this.bindEvent();
        }
        //事件处理函数的绑定执行
        bindEvent(){
            this.oBtnGroup.addEventListener('click',this.onFieldBtnClick.bind(this),false)
            this.fInput.addEventListener('input',this.onNumberInput.bind(this),false);
            this.sInput.addEventListener('input',this.onNumberInput.bind(this),false);
        }
        //数据
        defineData() {
            let _obj = {};
            let fNumber = 0;
            let sNumber = 0;
            let field = 'plus';
    
            const _self = this;//保存外界的this指向
    
            Object.defineProperties(_obj, {
                fNumber: {
                    get() {
                        console.log('"fNumber" is being got.');
                        return fNumber;
                    },
                    set(newVal) {
                        fNumber = newVal;
                        _self.computeResult(fNumber,sNumber,field);//核心 
                        console.log(`The value "fNumber" has been changed.[${fNumber}]`);
                    }
                },
                sNumber: {
                    get() {
                        console.log('"sNumber" is being got.');
                        return sNumber;
                    },
                    set(newVal) {
                        sNumber = newVal;
                        _self.computeResult(fNumber,sNumber,field);
                        console.log(`The value "sNumber" has been changed.[${sNumber}]`);
                    }
                },
                field: {
                    get() {
                        console.log('"field" is being got.');
                        return field;
                    },
                    set(newVal) {
                        field = newVal;
                        _self.computeResult(fNumber,sNumber,field);
                        console.log(`The value "field" has been changed.[${field}]`);
                    }
                }
            });
            return _obj;
        }
        //时间代理函数
        onFieldBtnClick(ev){
            const e = ev || window.event,
                  tar = e.target || e.srcElement,
                  tagName = tar.tagName.toLowerCase();
            tagName === 'button' && this.fieldUpdate(tar);
        }
        //执行更新
        fieldUpdate(target){
            console.log(this.oBtnItems);
            this.oBtnItems[this.btnIdx].className = '';
            this.btnIdx = [].indexOf.call(this.oBtnItems,target);
            target.className += 'current';
            this.data.field = target.getAttribute('data-field');
        }
    
        onNumberInput(ev){
            const e = ev || window.Event,
                  tar = e.target || e.srcElement,
                  className = tar.className,
                  val = Number(tar.value.replace(/\s+/g,'')) || 0;
            switch (className) {
                case "f-input":
                    this.data.fNumber = val;
                    break;
                case "s-input":
                    this.data.sNumber = val;
                    break;
                default:
                    break;
            }
        }
        //两个input的值想操作,比如相加  相减
        computeResult(fNumber,sNumber,field){
            this.oResult.innerText = this[field](fNumber,sNumber);
        }
    }
    new Calculator(document).init(); //初始化