资讯专栏INFORMATION COLUMN

浅谈 JS 对象添加 getter与 setter 的5种方法以及如何让对象属性不可配置或枚举

Dean / 705人阅读

摘要:需被定义或修改的属性名。当且仅当该属性出现在相应的对象枚举属性中。可以是任何有效的值数值,对象,函数等。方法将返回用作属性的值。该方法将收到作为唯一参数的新值分配给属性。属性用来声明欲修饰属性是否可以被枚举。

定义 getter 与 setter

通过对象初始化器定义

使用 Object.create 方法

使用 Object.defineProperty 方法

使用 Object.defineProperties 方法

使用 Object.prototype.__defineGetter__ 以及 Object.prototype.__defineSetter__ 方法

什么是属性描述符

创建属性不可配置不可枚举的对象

Enumerable 特性

Configurable 特新

提高及扩展

定义 getter 与 setter

1.通过对象初始化器在创建对象的时候指明(也可以称为通过字面值创建对象时声明)

(function () {
    var o = {
        a : 7,
        get b(){return this.a +1;},//通过 get,set的 b,c方法间接性修改 a 属性
        set c(x){this.a = x/2}
    };
    console.log(o.a);
    console.log(o.b);
    o.c = 50;
    console.log(o.a);
})();

在 chrome 中调试视图如下:

可以看到对象下多了 get 属性以及 set 属性
输出结果如下:

当然 get 语句与 set 语句可以声明多次用来对应多个 gettersetter
使用这种方法的好处是可以在声明属性的时候同时声明对应的 gettersetter
这里就有人问了,能不能将o 对象的 getset 方法的方法名都改成 “a”,这样就可以直接通过“.”来访问方法直接操作

(function () {
    var o = {
        a : 7,
        get a(){return this.a +1;},//死循环
        set a(x){this.a = x/2}
    };
    console.log(o.a);
    console.log(o.b);
    o.c = 50;
    console.log(o.a);
})();

打开 chrome 查看创建后的视图如下:

可以看到这个时候的 getset 方法已经和上面不同,但是是否真的能起作用呢,答案是否定的,当我们通过 o.a 调用的是 get语句 声明的 a方法,进入到该方法后遇到 this.a 方法继续调用该方法形成死循环最终导致死循环报内存溢出错误。

新语法(ES6):暂时只有 firefox 支持,其他浏览器会报错

(function () {
    var b = "bb";
    var c = "cc";
    var o = {
        a : 7,
        get [b](){return this.a +1;},
        set [c](x){this.a = x/2},
    };
    console.log(o.a);
    console.log(o[b]);
    o["cc"] = 50;
    console.log(o.a);
})();

打开 firefox 查看调试:

输出结果如下:

2.使用 Object.create 方法

引用 MDN:

概述
    Object.create() 方法创建一个拥有指定原型和若干个指定属性的对象。

语法
    Object.create(proto, [ propertiesObject ])

我们都知道使用 Object.create 方法传递一个参数的时候可以创建一个以该参数为原型的对象 浅谈 JS 创建对象的 8 种模式
第二个参数是可选项,是一个匿名的参数对象,该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符(包扩数据描述符或存取描述符,具体解释看后面的内容 什么是属性描述符)。
通过属性描述符我们可以实现为新创建的对象添加 get 方法以及 set 方法

(function () {
    var o = null;
    o = Object.create(Object.prototype,//指定原型为 Object.prototype
            {
                bar:{
                    get :function(){
                        return 10;
                    },
                    set : function (val) {
                        console.log("Setting `o.bar` to ",val);
                    }
                }
            }//第二个参数
        );
    console.log(o.bar);
    o.bar = 12;
})();

在 chrome 中调试试图如下:

可以看到新创建对象通用多了 get 以及 set 属性
输出结果如下:

上面这个例子并没有用来针对的 get 方法以及 set 方法使用的属性

(function () {
    var o = null;
    o = Object.create(Object.prototype,//指定原型为 Object.prototype
            {
                bar:{
                    get :function(){
                        return this.a;
                    },
                    set : function (val) {
                        console.log("Setting `o.bar` to ",val);
                        this.a = val;
                    },
                    configurable :true
                }
            }//第二个参数
        );
    o.a = 10;
    console.log(o.bar);
    o.bar = 12;
    console.log(o.bar);
})();

亦或:

(function () {
    var o = {a:10};
    o = Object.create(o,//指定原型为 o 这里实际可以理解为继承
            {
                bar:{
                    get :function(){
                        return this.a;
                    },
                    set : function (val) {
                        console.log("Setting `o.bar` to ",val);
                        this.a = val;
                    },
                    configurable :true
                }
            }//第二个参数
        );
    console.log(o.bar);
    o.bar = 12;
    console.log(o.bar);
})();

输出结果如下:

使用这种方式的好处是可配置性高,但初学者容易迷糊。

3.使用 Object.defineProperty 方法
引用 MDN:

概要
    Object.defineProperty() 方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。
语法
    Object.defineProperty(obj, prop, descriptor)
参数
    obj
        需要定义属性的对象。
    prop
        需被定义或修改的属性名。
    descriptor
        需被定义或修改的属性的描述符。
(function () {
    var o = { a : 1}//声明一个对象,包含一个 a 属性,值为1
    Object.defineProperty(o,"b",{
        get: function () {
            return this.a;
        },
        set : function (val) {
            this.a = val;
        },
        configurable : true
    });

    console.log(o.b);
    o.b = 2;
    console.log(o.b);
})();

这个方法与前面两种的区别是:使用前面两种只能在声明定义的时候指定 gettersetter,使用该方法可以随时的添加或修改。

如果说需要一次性批量添加 getter 与 setter 也是没问题的,使用如下方法:

4.使用 Object.defineProperties方法

MDN:

概述
    Object.defineProperties() 方法在一个对象上添加或修改一个或者多个自有属性,并返回该对象。
语法
    Object.defineProperties(obj, props)
参数
    obj
    将要被添加属性或修改属性的对象
    props
    该对象的一个或多个键值对定义了将要为对象添加或修改的属性的具体配置
    
    

不难看出用法与 Object.defineProperty 方法类似

(function () {
    var obj = {a:1,b:"string"};
    Object.defineProperties(obj,{
        "A":{
            get:function(){return this.a+1;},
            set:function(val){this.a = val;}
        },
        "B":{
            get:function(){return this.b+2;},
            set:function(val){this.b = val}
        }
    });

    console.log(obj.A);
    console.log(obj.B);
    obj.A = 3;
    obj.B = "hello";
    console.log(obj.A);
    console.log(obj.B);
})();

输出结果如下:

5.使用 Object.prototype.__defineGetter__ 以及 Object.prototype.__defineSetter__ 方法

(function () {
    var o = {a:1};
    o.__defineGetter__("giveMeA", function () {
        return this.a;
    });
    o.__defineSetter__("setMeNew", function (val) {
        this.a  = val;
    })
    console.log(o.giveMeA);
    o.setMeNew = 2;
    console.log(o.giveMeA);
})();
输出结果为1和2

查看 MDN 有如下说明:

什么是属性描述符

MDN:

对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。

数据描述符是一个拥有可写或不可写值的属性。

存取描述符是由一对 getter-setter 函数功能来描述的属性。

描述符必须是两种形式之一;不能同时是两者。

数据描述符和存取描述符均具有以下可选键值:

configurable
    当且仅当这个属性描述符值为 true 时,该属性可能会改变,也可能会被从相应的对象删除。默认为 false。
enumerable  
    true 当且仅当该属性出现在相应的对象枚举属性中。默认为 false。

数据描述符同时具有以下可选键值:

value 
    与属性相关的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
writable 
    true 当且仅当可能用 赋值运算符 改变与属性相关的值。默认为 false。

存取描述符同时具有以下可选键值:

get 
    一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。方法将返回用作属性的值。默认为 undefined。
set
    一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将收到作为唯一参数的新值分配给属性。默认为 undefined。

以上是摘自MDN的解释,看起来是很晦涩的,具体什么意思呢:
首先我们从以上解释知道该匿名参数对象有个很好听的名字叫属性描述符,属性描述符又分成两大块:数据描述符以及存取描述符(其实只是一个外号,给指定的属性集合起个外号)。

数据描述符包括两个属性 : value 属性以及 writable 属性,第一个属性用来声明当前欲修饰的属性的值,第二个属性用来声明当前对象是否可写即是否可以修改

存取描述符就包括 getset 属性用来声明欲修饰的象属性的 gettersetter

属性描述符内部,数据描述符与存取描述符只能存在其中之一,但是不论使用哪个描述符都可以同时设置 configurable 属性以及enumerable 属性。
configurable属性用来声明欲修饰的属性是否能够配置,仅有当其值为 true 时,被修饰的属性才有可能能够被删除,或者重新配置。
enumerable 属性用来声明欲修饰属性是否可以被枚举

知道了什么是属性描述符,我们就可以开始着手创建一些对象并开始配置其属性

创建属性不可配置不可枚举的对象

//使用默认值配置
(function () {
    var obj = {};//声明一个空对象
    Object.defineProperty(obj,"key",{
        value:"static"
                        //没有设置 enumerable 使用默认值 false
                        //没有 configurable 使用默认值 false
                        //没有 writable 使用默认值 false
    });

    console.log(obj.key);           //输出 “static”
    obj.key = "new"                 //尝试修改其值,修改将失败,因为 writable 为 false
    console.log(obj.key);           //输出 “static”
    obj.a = 1;//动态添加一个属性
    for(var item in obj){ //遍历所有 obj 的可枚举属性
         console.log(item);
    }//只输出一个 “a” 因为 “key”的 enumerable为 false
})();
//显示配置 等价于上面
(function () {
    var obj = {};
    Object.defineProperty(obj,"key",{
        enumerable : false,
        configurable : false,
        writable : false,
        value : "static"
    })
})();
//等价配置
(function () {
    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});
})();

Enumerable 特性
属性特性 enumerable 决定属性是否能被 for...in 循环或 Object.keys 方法遍历得到

(function () {
    var o = {};
    Object.defineProperty(o,"a",{value :1,enumerable :true});
    Object.defineProperty(o,"b",{value :2,enumerable :false});
    Object.defineProperty(o,"c",{value :2});//enumerable default to false
    o.d = 4;//如果直接赋值的方式创建对象的属性,则这个属性的 enumerable 为 true

    for(var item in o){ //遍历所有可枚举属性包括继承的属性
        console.log(item);
    }

    console.log(Object.keys(o));//获取 o 对象的所有可遍历属性不包括继承的属性

    console.log(o.propertyIsEnumerable("a"));//true
    console.log(o.propertyIsEnumerable("b"));//false
    console.log(o.propertyIsEnumerable("c"));//false
})();

输出结果如下:

Configurable 特性

(function () {
    var o = {};
    Object.defineProperty(o,"a",{get: function () {return 1;},
                                configurable : false} );
                                //enumerable 默认为 false,
                                //value 默认为 undefined,
                                //writable 默认为 false,
                                //set 默认为 undefined
                                  
    //抛出异常,因为最开始定义了 configurable 为 false,故后期无法对其进行再配置
    Object.defineProperty(o,"a",{configurable : true} );
    //抛出异常,因为最开始定义了 configurable 为 false,故后期无法对其进行再配置,enumerable 的原值为 false
    Object.defineProperty(o,"a",{enumerable : true} );
    //抛出异常,因为最开始定义了 configurable 为 false,set的原值为 undefined
    Object.defineProperty(o,"a",{set : function(val){}} );
    //抛出异常,因为最开始定义了 configurable 为 false,故无法进行覆盖,尽管想用一样的来覆盖
    Object.defineProperty(o,"a",{get : function(){return 1}});
    //抛出异常,因为最开始定义了 configurable 为 false,故无法将其进行重新配置把属性描述符从存取描述符改为数据描述符
    Object.defineProperty(o,"a",{value : 12});

    console.log(o.a);//输出1
    delete o.a;      //想要删除属性,将失败
    console.log(o.a);//输出1
    
})();

提高及扩展
1.属性描述符中容易被误导的地方之 writableconfigurable

(function () {
    var o = {};
    Object.defineProperties(o,{
        "a": {
            value:1,
            writable:true,//可写
            configurable:false//不可配置
            //enumerable 默认为 false 不可枚举
        },
        "b":{
            get :function(){
                return this.a;
            },
            configurable:false
        }
    });
    console.log(o.a);   //1
    o.a = 2;            //修改值成功,writable 为 true
    console.log(o.a);   //2
    Object.defineProperty(o,"a",{value:3});//同样为修改值成功
    console.log(o.a);   //3

    //将其属性 b 的属性描述符从存取描述符重新配置为数据描述符
    Object.defineProperty(o,"b",{value:3});//抛出异常,因为 configurable 为 false
})();

2.通过上面的学习,我们都知道传递属性描述符参数时,是定义一个匿名的对象,里面包含属性描述符内容,若每定义一次便要创建一个匿名对象传入,将会造成内存浪费。故优化如下:

(function () {
    var obj = {};

    //回收同一对象,即减少内存浪费
    function withValue(value){
        var d = withValue.d ||(
            withValue.d = {
                enumerable : false,
                configurable : false,
                writable : false,
                value :null
            }
            );
        d.value = value;
        return d;
    }
    Object.defineProperty(obj,"key",withValue("static"))
})();

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86080.html

相关文章

  • 浅谈 JS 对象之扩展、密封及冻结三大特性

    摘要:尝试删除一个密封对象的属性或者将某个密封对象的属性从数据属性转换成访问器属性,结果会静默失败或抛出异常严格模式。 扩展特性 Object.isExtensible 方法 Object.preventExtensions 方法 密封特性 Object.isSealed 方法 Object.seal 方法 冻结特性 Object.isFrozen 方法 Object...

    ChristmasBoy 评论0 收藏0
  • JavaScript || 对象

    摘要:通过对返回字符串切片第位到倒数第位即可获得对象的类型。测试对象是的深拷贝是的子集,不能表示中所有值。序列化结果是,对象序列化结果是日期字符串不能表示函数对象和只能序列化对象自有的可枚举属性。 对象 对象是JavaScript的基本数据类型:属性的无序集合。每个属性key: value和属性描述符descripter组成。 属性名key:字符串或合法的变量标识符; 属性值value:...

    lavor 评论0 收藏0
  • JS基础05对象

    摘要:对象是的数据类型。对象是动态的,可以随时新增和删除自有属性。客户端中表示网页结构的对象均是宿主对象。提供第二个可选参数,用以对对象的属性进行进一步描述。没有原型的对象为数不多,就是其中之一。运算符的左侧是属性名字符串,右侧是对象。 对象是 JavaScript 的数据类型。它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值,因此我们可以把它看成是从字符串到值的映射。对象是...

    frolc 评论0 收藏0
  • 《JavaScript 闯关记》之对象

    摘要:属性名可以是包含空字符串在内的任意字符串,但对象中不能存在两个同名的属性。客户端中表示网页结构的对象均是宿主对象。这里的函数称做构造函数,构造函数用以初始化一个新创建的对象。通过关键字和构造函数调用创建的对象的原型就是构造函数的属性的值。 对象是 JavaScript 的数据类型。它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值,因此我们可以把它看成是从字符串到值的映射...

    rozbo 评论0 收藏0
  • JavaScript对象详解

    摘要:属性描述符升级打怪必备技能对象有自己的属性和方法,对于我们对象的属性来讲,属性还有自己的属性,又称为属性描述符。这个方法接受三个参数,第一个是指定的对象,第二个是指定的对象参数,第三个当然是要修改的属性描述符了。 对象的声明有俩种: 字面量 通过new一个构造函数Object 两者唯一的区别就是,字面量形式,可以一次赋值多个,通过new Object就得一个一个赋值 数据类型 ...

    fancyLuo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<