浅谈JS对象添加getter与setter的5种方法

定义 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 对象的 get 及 set 方法的方法名都改成 “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 查看创建后的视图如下:

可以看到这个时候的 get 与 set 方法已经和上面不同,但是是否真的能起作用呢,答案是否定的,当我们通过 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);
})();

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

如果说需要一次性批量添加 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:

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

  1. 数据描述符是一个拥有可写或不可写值的属性。
  2. 存取描述符是由一对 getter-setter 函数功能来描述的属性。
  3. 描述符必须是两种形式之一;不能同时是两者。

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

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 属性,第一个属性用来声明当前欲修饰的属性的值,第二个属性用来声明当前对象是否可写即是否可以修改

存取描述符就包括 get 与 set 属性用来声明欲修饰的象属性的 getter 及 setter

属性描述符内部,数据描述符与存取描述符只能存在其中之一,但是不论使用哪个描述符都可以同时设置 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.属性描述符中容易被误导的地方之 writable 与 configurable

(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"))
})();

这篇文章就介绍到这,更多的可以查看加我们之前发布的文章。

(0)

相关推荐

  • JavaScript之Getters和Setters 平台支持等详细介绍

    来自John Resig早年的文章,大致翻译了一下,以作备忘. 令人高兴的是,我想我终于可以说,"现在,JavaScript的Getters和Setters使用非常广泛,它和每个JavaScript开发者的切身利益息息相关".靠,我为了说这句话已经等了很久了. 首先,我们先来快速了解什么是Getters和Setters,以及它们为什么很有用.然后,我们来看看现在都有哪些平台支持Gettets和Setters. Getters和Setters Getters和Setters使你可以快速获

  • Javascript中的getter和setter初识

    前言 本文主要给大家介绍的关于Javascript中getter和setter的相关内容,第一次听说这个东西的时候是vue.js里面的数据绑定,只要绑定了数据,修改对象属性可以自动反馈到dom上,很神奇,后面也看到了文档里面实现是对对象定义了getter和setter并覆盖原属性,索性就来总结这两者的用法,下面话不多说了,来一起看看详细的介绍吧. 原理 利用Object.defineProperty来重写对象属性为getter和setter,通过getter和setter顺便改变绑定DOM节点的

  • javascript中的__defineGetter__和__defineSetter__介绍

    Getter是一种获取一个属性的值的方法,Setter是一种设置一个属性的值的方法.可以为任何预定义的核心对象或用户自定义对象定义getter和setter方法,从而为现有的对象添加新的属性. 有两种方法来定义Getter或Setter方法: 1.在对象初始化时定义 2.在对象定义后通过Object的__defineGetter__.__defineSetter__方法来追加定义 在使用对象初始化过程来定义Getter和Setter方法时唯一要做的事情就是在getter方法前面加上"get&qu

  • 谈谈因Vue.js引发关于getter和setter的思考

    起因 当我打印出Vue实例下的data对象里的属性时,发现了一个有趣的事情: 它的每个属性都有两个相对应的get和set方法,我觉的这是多此一举的,于是去网上查了查Vue双向绑定的实现原理,才发现它和Angular.js双向绑定的实现原理完全不同,Angular是用的数据脏检测,当Model发生变化,会检测所有视图是否绑定了相关数据,再更改视图.而Vue使用的发布订阅模式,是点对点的绑定数据. Vue的数据绑定只有两个步骤,compile=>link. 我一直在想,vue是通过什么去监听用户对M

  • js中getter和setter用法实例分析

    本文实例讲述了js中getter和setter用法.分享给大家供大家参考,具体如下: 在学习Vue计算属性时,有一句"计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter". getter和setter到底是什么?于是我查找了资料: 在Es5中可以使用getter和setter部分改写默认操作,但是只能应用在单个属性上,无法应用在整个对象上.getter是一个隐藏函数,会在获取属性值时调用.setter也是一个隐藏属性,会在设置属性值时调用. 例子: var m

  • JavaScript的setter与getter方法

    以前在写项目过程一直都没有使用过Javascript的setter与getter方法,所以对其是一种要懂不懂的概念:今天看书看到这个知识点,还是模模糊糊的,于是就打算研究研究: Javascript对象的属性是由名字,值和一组特性构成的.那么首先,来了解一下对象的两种属性: 数据属性,我们经常使用,应该很熟悉 访问器属性,也称存取器属性 何为存取器属性?就是一组获取和设置值的函数.在ECMAScript5中,属性值可以用一个或两个方法设置,这两个方法就是getter和setter:因此gette

  • JavaScript中setter和getter方法介绍

    javascript中的setter.getter是平时接触比较少的方法,其本身也并不是标准方法,只在非ie浏览器里支持(ie内核也许有其他方法可以做到呢?暂时不知其解),但是加以利用可以做许多事情,比如: 1.对数据的访问限制: a.value是对value变量的getter方法调用,如果在getter方法实现中抛出异常,可以阻止对value变量的访问 2.对dom变量进行监听: window.name是一个跨域非常好用的dom属性(大名鼎鼎,详见百度),如果覆盖window.name的set

  • 浅谈JS对象添加getter与setter的5种方法

    定义 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); })(); 在 chro

  • 浅谈js对象的创建和对6种继承模式的理解和遐想

    JS中总共有六种继承模式,包括原型链.借用构造函数.组合继承.原型式继承寄生式继承和寄生组合式继承.为了便于理解记忆,我遐想了一个过程,对6中模式进行了简单的阐述. 很长的一个故事,姑且起个名字叫女娲造人吧. 创建对象 女娲一个一个的捏人(创建对象),这样太慢,于是设计了一种机器(函数),想造什么样的,告诉他这个人有哪些特点和功能,机器来制造.这就是工厂模式的(使用同一个接口创建对象,回产生大量重复代码,由此发明了一种函数(模具)). 但是机器造人同样也比较麻烦(挖土.和泥.捏眼睛.捏鼻子...

  • 浅谈js对象属性 通过点(.) 和方括号([]) 的不同之处

    [JS对象属性的查询和设置] 可以通过点(.) 或 方括号([]) 运算符来获取属性的值.运算符左侧应当是一个表达式,它返回一个对象.对于点(.)来说,右侧必须是一个以属性名称命名的简单标识符.对于方括号([])来说,方括号里必须是一个计算结果为字符串的表达式,这个字符串就是属性的名字: <script type="text/javascript"> var author = book.author; //得到book的"author"属性 var na

  • 浅谈JS正则表达式的RegExp对象和括号的使用

    RegExp对象的创建: 常规的正则表达式的创建可用直接量,即斜杠 "/" 括起来的字符.但在要求参数变化的环境下,RegExp()构造函数是更好的选择: var reg1 = /'\w+'/g; var reg2 = new RegExp('\'\\w+\'','g'); 对比两种创建方式,RegExp中的第一个参数为要创建的正则字符串,一方面注意,因为不是直接量的表示形式,因此不用斜杠" / "括起来了:而是字符串中必须要对引号" ' "和转

  • 浅谈JS读取DOM对象(标签)的自定义属性

    DOM对象对于js来说,是个很基础的元素,我们写js一般来说,都一定会对它进行操作.我们可以很方便地给它加上自定义的属性,比如: <div id="test" class="hello"></div> var test = document.getElementById("test"); test.adang = "adang"; alert(test.adang); 我们会发现,已经给这个id为tes

  • 浅谈JS包装对象

    目录 概述 定义 实例方法 原始类型与实例对象的自动转换 自定义方法 Boolean 对象 概述 Boolean 函数的类型转换作用 Number 对象 概述 静态属性 实例方法 自定义方法 String 对象 概述 静态方法 概述 定义 对象是JavaScript语言最主要的数据类型,三种原始类型的值--数值.字符串.布尔值--在一定条件下,也会自动转为对象,也就是原始类型的"包装对象"(wrapper). 所谓"包装对象",指的是与数值.字符串.布尔值分别相对应

  • 浅谈JS封闭函数、闭包、内置对象

    一.变量作用域指的是变量的作用范围,javascript中的变量分为全局变量和局部变量 1.全局变量:在函数之外定义的变量,为整个页面公用,函数的内部外部都可以访问. 2.局部变量:在函数内部定义的变量,只能在定义该变量的函数内部访问,外部无法访问.函数内部访问变量时,先在内部查找是否有此变量,如果有,就使用内部,如果没有,就去外部查找 二.封闭函数封闭函数是javascript中匿名函数的另外一种写法,创建一个一开始就执行而不用命名的函数. 1.一般函数的定义和执行函数 2.封闭函数的定义和执

  • 浅谈js使用in和hasOwnProperty获取对象属性的区别

    in判断的是对象的所有属性,包括对象实例及其原型的属性: 而hasOwnProperty则是判断对象实例的是否具有某个属性. 示例代码: <script type="text/javascript"> function Person(){ } Person.prototype.name = "allen"; var person = new Person(); console.log(person.hasOwnProperty("name&qu

  • 浅谈JS使用[ ]来访问对象属性

    对象的属性由两种固定的方法来访问:"."记法和"[ ]"方括号记法: 使用"."号记法访问标准的对象属性,使用"[ ]"方括号记法访问由页面定义的对象属性.如下 document.forms["myformname"].elements["myinput"].value 这里,forms 是 document 的一个标准属性,而表单名 myformname 则是由页面所定义的.同时,el

  • 浅谈JS如何实现真正的对象常量

    前言 众所周知ES6新增的const关键字可以用来声明常量,但是它只对基本数据类型生效(Number.String.Boolean等),那如果我们想声明一个常量对象呢?该如何实现,Object内置对象早就替我们想到了,下面来具体看一下 正题 一.先来看一下const方式来声明基本类型常量 代码: const name = 'jack' name = 'lucy' // 修改name常量 运行结果: 可以看到,控制台报错了,所以基本类型常量一旦声明复制,就不能在被修改 二.再来用const方式来声

随机推荐