• 【Vue】-- 数据双向绑定的原理 --Object.defineProperty()


      Object.defineProperty()方法被许多现代前端框架(如Vue.js,React.js)用于数据双向绑定的实现,当我们在框架Model层设置data时,框架将会通过Object.defineProperty()方法来绑定所有数据,并在数据变化的同时修改虚拟节点,最终修改页面的Dom结构。

     一、语法

    Object.defineProperty(obj, prop, descriptor)
            obj:必须,被定义或修改属性的对象;
     
            prop:必须,要定义或修改的属性名称;
     
            descriptor:必须,属性的描述描述符

    返回值

    函数将返回传递给他的obj对象本身。

    二、描述符(descriptor)说明

            该方法允许开发者精确的对对象属性的定义和修改。通过正常赋值进行属性添加而构建的属性会被枚举器方法(如for…in循环或Object.keys方法)获取,从而导致属性值被外部方法改变或删除。而Object.defineProperty()可以避免以上描述的情况,默认的,通过Object.defineProperty()添加的属性是默认不可改变的。
      属性描述参数(descriptor)主要由两部分构成:数据描述符(data descriptor)和访问器描述符(accessor descriptor)。
      数据描述符就是一个包含属性的值,并说明这个值可读或不可读的对象;访问器描述符就是包含该属性的一对getter-setter方法的对象。一个完整的属性描述(descriptor)必须是这两者之一,并且不可以两者都有。

    数据描述符和访问器描述符各自都是对象,有以下键值对:

    configurable(必须)

    仅当设置的属性的描述符需要被修改或需要通过delete来删除该属性时,configurable属性设置为true。默认为false。

    enumerable(必须)

    仅当设置的属性需要被枚举器(如for…in)访问时设置为true。默认为false。

    value(可选)

     设置属性的值,可以是任何JavaScript值类型(number,object,function等类型)。默认为undefined。

    writable(可选)

     仅当属性的值可以被赋值操作修改时设置为true。默认为false。
     

    访问器描述符可以包含以下可选键值对:

    get

    属性的getter方法,若属性没有getter方法则为undefined。该方法的返回为属性的值。默认为undefined

    set

    属性的setter方法,若属性没有setter方法则为undefined。该方法接收唯一的参数,作为属性的新值。默认为undefined。

     

    请牢记,这些描述符的属性并不是必须的,从原型链继承而来的属性也可填充。为了保证这些描述符属性被填充为默认值,你可能会使用形如预先冻结Object.prototype、明确设置每个描述符属性的值、使用Object.create(null)来获取空对象等方式。
    // using __proto__
    var obj = {};
    var descriptor = Object.create(null); // no inherited properties
     
    //所有描述符的属性被设置为默认值
    descriptor.value = 'static';
    Object.defineProperty(obj, 'key', descriptor);
     
    //明确设置每个描述符的属性
    Object.defineProperty(obj, 'key', {
    enumerable: false,
    configurable: false,
    writable: false,
    value: 'static'
    });
     
    //重用同一个对象作为描述符
    function withValue(value) {
    var d = withValue.d || (
    withValue.d = {
    enumerable: false,
    writable: false,
    configurable: false,
    value: null
    }
    );
    d.value = value;
    return d;
    }
    Object.defineProperty(obj, 'key', withValue('static'));
     
    //如果Object.freeze方法可用,则使用它来防止对对象属性的修改
    (Object.freeze || Object)(Object.prototype);
     

    使用示例

    创建一个属性

    如果当前对象不存在我们要设置的属性,Object.defineProperty()会根据方法设置为对象创建一个新的属性。如果描述符参数缺失,则会被设置为默认值。所有布尔型描述符属性会被默认设置为false。而value,get,set会被默认设置为undefined。一个未设置get/set/value/writable的属性被称为一个“原生属性(generic)”,并且他的描述符(descriptor)会被“归类”为一个数据描述符(data descriptor)。
    var o = {}; //创建一个对象
     
    //使用数据描述符来为对象添加属性
    Object.defineProperty(o, 'a', {
    value: 37,
    writable: true,
    enumerable: true,
    configurable: true
    });
    //属性”a”被设置到对象o上,并且值为37
     
    //使用访问器描述符来为对象添加属性
    var bValue = 38;
    Object.defineProperty(o, 'b', {
    get: function() { return bValue; },
    set: function(newValue) { bValue = newValue; },
    enumerable: true,
    configurable: true
    });
    o.b; // 38
    //属性”b”被设置到对象o上,并且值为38。
    //现在o.b的值指向bValue变量,除非o.b被重新定义
     
    //你不能尝试混合数据、访问器两种描述符
    Object.defineProperty(o, 'conflict', {
    value: 0x9f91102,
    get: function() { return 0xdeadbeef; }
    });
    //抛出一个类型错误: value appears only in data descriptors, get appears only in accessor descriptors(value只出现在数据描述符中,get只出现在访问器描述符中)
     

    修改一个属性

    当某个属性已经存在了,Object.defineProperty()会根据对象的属性配置(configuration)和新设置的值来尝试修改该属性。如果该属性的configurable被设置为false,则该属性无法被修改(这种情况下有个特殊情况:如果之前的writable设置为true,则我们仍可以将writable设置为false,一旦这么做之后,任何描述符属性将变得不可设置)。如果属性的configurable设置为false,则我们无法将属性的描述符在数据描述符和访问器描述符之间转换。
    如果新设置的属性和该属性不同,并且该属性的configurable被设置为false,则一个类型错误(TypeError)会被抛出(除了上一段文字中说的特殊情况)。若新旧属性完全相同,则什么都不会发生。

    可写特性-writable

    当一个属性的writable被设置为false,这个属性就成为“不可写的(non-writable)”。该属性不可被重新赋值。
    var obj = {};
    Object.defineProperty(obj,"name",{
        value:"张三",
        writable:false//当设置为false的时候当前对象的属性值不允许被修改
    })
    
    obj.name="李四"
    console.log(obj.name)//张三
    
    
    var obj = {};
    Object.defineProperty(obj,"name",{
        value:"张三",
        writable:true//当设置为true的时候当前对象的属性值允许被修改
    })
    
    obj.name="李四"
    console.log(obj.name)//李四

    可枚举特性-enumerable

    属性的enumerable值定义对象的属性是否会出现在枚举器(for…in循环和Object.keys())中。
    var obj = {name:"张三",age:"李四"}
    
    Object.defineProperty(obj,"name",{
        enumerable:false//当设置为false的时候对象的属性不可被枚举
    })
    
    Object.defineProperty(obj,"age",{
        enumerable:false
    })
    
    console.log(Object.keys(obj))//[]
    
    
    var obj = {name:"张三",age:"李四"}
    
    Object.defineProperty(obj,"name",{
        enumerable:true//当设置为true的时候对象的属性可被枚举
    })
    
    Object.defineProperty(obj,"age",{
        enumerable:true
    })
    
    console.log(Object.keys(obj))//["name",age]
     

    可配置特性-configurable

    属性的configurable值控制一个对象的属性可否被delete删除,同时也控制该属性描述符的配置可否改变(除了前文所述在configurable为false时,若writable为true,则仍可以进行一次修改将writable改变为false)。
    var obj = {};
    Object.defineProperty(obj,"name",{
        value:"张三",
        configurable:false//当设置为false的时候对象的属性不允许被删除
    })
    
    delete obj.name;
    
    console.log(obj.name)//张三
    
    
    
    var obj = {};
    Object.defineProperty(obj,"name",{
        value:"张三",
        configurable:true//当设置为true的时候对象的属性允许被删除
    })
    
    delete obj.name;
    
    console.log(obj.name)//undefined

    get和set

    Get:指读取属性时调用的函数。
    Set:指写入属性时调用的函数

    var obj = {name:"张三"}
    
    Object.defineProperty(obj,"name",{
        get(){
            console.log("被访问了")//当被访问的时候会触发get()方法
       
        },
        set(newVal){
            console.log("被设置了"+newVal)//当被设置的时候会触发set()方法
        }
    })
    obj.name//输出:被访问了
    obj.name="李四";//输出:被设置了李四

    添加属性时的默认值

    考虑描述符特性的默认值如何被应用是非常重要的。正如下面示例所示,简单的使用"."符号来设置一个属性和使用Object.defineProperty()是有很大区别的。
    var o = {};
     
    o.a = 1;
    //等同于:
    Object.defineProperty(o, 'a', {
    value: 1,
    writable: true,
    configurable: true,
    enumerable: true
    });
     
     
    //另一方面,
    Object.defineProperty(o, 'a', { value: 1 });
    //等同于:
    Object.defineProperty(o, 'a', {
    value: 1,
    writable: false,
    configurable: false,
    enumerable: false
    });

    configurable和writable

    一种特殊情况:当configurable为false时,我们唯一仍能改变的属性就是将设置为true的writable设置为false。
    var a={};
    Object.defineProperty(a,"o",{
    configurable:false,
    value:10,
    writable:true
    });
     
    console.log(a.o);//10
    a.o=12;//不报错
    console.log(a.o);//12
     
    Object.defineProperty(a,"o",{
    configurable:false,
    value:14,
    writable:true
    });
    console.log(a.o);//14
     
    Object.defineProperty(a,"o",{
    configurable:false,
    value:14,
    writable:false
    });
    a.o=16;//不报错
    console.log(a.o);//14
     
    //报错
    Object.defineProperty(a,"o",{
    configurable:false,
    value:16,
    writable:false
    });
    由以上代码可以得出结论,对于描述符(descriptor)为数据描述符(data descriptor)的情况:
    1.使用“.”操作符来设置属性的值永远不会报错,仅当writable为false时无效。
    2.只要writable为true,不论configurable是否为false,都可以通过Object.defineProperty()来修改value的值。
    由此得出结论,各大浏览器运营商实现的Object.defineProperty()和标准描述在configurable的定义上稍有偏差。描述符为数据描述符时值的改变与否仅受writable的控制。
  • 相关阅读:
    Ajax 传递json字符串到客户端时报 Internal server error
    Java 判断字符串的存储长度
    5个数组Array方法: indexOf、filter、forEach、map、reduce使用实例
    databales详解(一)
    JQuery总结
    《JavaScript 高级程序设计》总结
    ASP.NET MVC中controller和view相互传值的方式
    Jquery tmpl详解
    @section script{}的使用
    messager(消息窗口)
  • 原文地址:https://www.cnblogs.com/xuelanying/p/10556571.html
Copyright © 2020-2023  润新知