Object properties in JavaScript

 

 Blog post “ Protecting objects in JavaScript ” (
Object.preventExtensions(), Object.seal(), Object.freeze()

  

).

Properties determine the state of an object in JavaScript. This blog post examines in detail how they work.

Kinds of properties

JavaScript has three different kinds of properties: named data properties, named accessor properties and internal properties.
Named data properties (“properties”)
“Normal” properties of objects map string names to values. For example, the following object  obj has a data property whose name is the string  "prop" and whose value is the number 123.
  var obj = {
        prop: 123
    };

  

You can get (read) a property:
 console.log(obj.prop); // 123
    console.log(obj["prop"]); // 123

  

And you can set (write) a property:
  obj.prop = "abc";
    obj["prop"] = "abc";

  

Named accessor properties
Alternatively, getting and setting a property value can be handled via functions. Those functions are called accessor functions. A function that handles getting is called a getter. A function that handles setting is called a setter.
var obj = {
        get prop() {
            return "Getter";
        },
        set prop(value) {
            console.log("Setter: "+value);
        }
    }

  

Let’s interact with  obj:
   > obj.prop
    'Getter'
    > obj.prop = 123;
    Setter: 123

  

Internal properties
Some properties are only used by the specification. They are called “internal”, because they are not directly accessible via the language, but they do influence its behavior. Internal properties have special names that are written in double square brackets. Two examples:
  • The internal property [[Prototype]] points to the prototype of an object. It can be read via Object.getPrototypeOf(). Its value can only be set by creating a new object that has a given prototype, e.g. via Object.create() or __proto__[1].
  • The internal property [[Extensible]] determines whether or not one can add properties to an object. It can be read via Object.isExtensible(). It can be set false via Object.preventExtensions(). Once false, it cannot be becometrue again.

Property attributes

All of the state of a property, both its data and its meta-data, is stored in  attributes. They are fields that a property has, much like an object has properties. Attribute keys are often written in double brackets.

The following attributes are specific to named data properties:

  • [[Value]] hold the property’s value, its data.
  • [[Writable]] holds a boolean indicating whether the value of a property can be changed.
The following attributes are specific to named accessor properties:
  • [[Get]] holds the getter, a function that is called when a property is read. That function computes the result of the read access.
  • [[Set]] holds the setter, a function that is called when a property is set to a value. The function receives that value as a parameter.
All properties have the following attributes:
  • [[Enumerable]] holds a boolean. Making a property non-enumerable hides it from some operations (see below).
  • [[Configurable]] holds a boolean. If false, you cannot delete a property, change any of its attributes (except [[Value]]) or convert between data property and accessor property. In other words, [[Configurable]] controls the writability of a property’s meta-data.
Default values
If you don’t specify attributes, the following defaults are used:

 

Attribute keyDefault value
[[Value]]undefined
[[Get]]undefined
[[Set]]undefined
[[Writable]]false
[[Enumerable]]false
[[Configurable]]false

These defaults are especially important for property descriptors (see below).

Property descriptors

A property descriptor encodes the attributes of a property as an object. Each of the properties of that object corresponds to an attribute. For example, the following is the descriptor of a read-only property whose value is 123:
  {
        value: 123,
        writable: false,
        enumerable: true,
        configurable: false
    }

  

You can achieve the same goal, immutability, via accessors. Then the descriptor looks as follows:
 {
        get: function () { return 123 },
        enumerable: true,
        configurable: false
    }

  

Functions that use property descriptors
The following functions allow you to work with property descriptors:
  • Object.defineProperty(obj, propName, propDesc)
    

      


    Create or change a property on obj whose name is propName and whose attributes are specified via propDesc. Return the modified object. Example:
     var obj = Object.defineProperty({}, "foo", {
            value: 123,
            enumerable: true
            // writable and configurable via defaults
        });
    

      

  • Object.defineProperties(obj, propDescObj)
    The batch version of Object.defineProperty(). Each property of propDescObjholds a property descriptor. The names of the properties and their values tellObject.defineProperties what properties to create or change on obj. Example:
      var obj = Object.defineProperties({}, {
            foo: { value: 123, enumerable: true },
            bar: { value: "abc", enumerable: true }
        });
    

      

  • Object.create(proto, propDescObj?)
    First, create an object whose prototype is proto. Then, if the optional parameter propDescObj has been specified, add properties to it – in the same manner as Object.defineProperties. Finally, return the result. For example, the following code snippet produces the same result as the previous snippet:
     var obj = Object.create(Object.prototype, {
            foo: { value: 123, enumerable: true },
            bar: { value: "abc", enumerable: true }
        });
    

      

  • Object.getOwnPropertyDescriptor(obj, propName)
    Returns the descriptor of the own (non-inherited) property of obj whose name is propName. If there is no such property, undefined is returned.
     > Object.getOwnPropertyDescriptor(Object.prototype, "toString")
        { value: [Function: toString],
          writable: true,
          enumerable: false,
          configurable: true }
    
        > Object.getOwnPropertyDescriptor({}, "toString")
        undefined
    

      

Enumerability

This section explains which operations are influenced by enumerability and which aren’t. Below, we are assuming that the following definitions have been made:
  var proto = Object.defineProperties({}, {
        foo: { value: 1, enumerable: true },
        bar: { value: 2, enumerable: false }
    });
    var obj = Object.create(proto, {
        baz: { value: 1, enumerable: true },
        qux: { value: 2, enumerable: false }
    });

  

Note that objects (including  proto) normally have at least the prototype Object.prototype  [2]:
 > Object.getPrototypeOf({}) === Object.prototype
    true

  

Object.prototype is where standard methods such as  toString and  hasOwnProperty are defined.
Operations affected by enumerability
Enumerability only affects two operations: The for-in loop and  Object.keys().

The for-in loop iterates over the names of all enumerable properties, including inherited ones (note that none of the non-enumerable properties of Object.prototype show up):

 > for (var x in obj) console.log(x);
    baz
    foo

  

Object.keys() returns the names of all own (non-inherited) enumerable properties:

  > Object.keys(obj)
    [ 'baz' ]

  

If you want the names of all own properties, you need to use Object.getOwnPropertyNames() (see example below).
Operations that ignore enumerability
All other operations ignore enumerability. Some read operations take inheritance into consideration:
 > "toString" in obj
    true
    > obj.toString
    [Function: toString]

  

Other read operations only work with own properties:
 > Object.getOwnPropertyNames(obj)
    [ 'baz', 'qux' ]

    > obj.hasOwnProperty("qux")
    true
    > obj.hasOwnProperty("toString")
    false

    > Object.getOwnPropertyDescriptor(obj, "qux")
    { value: 2,
      writable: false,
      enumerable: false,
      configurable: false }
    > Object.getOwnPropertyDescriptor(obj, "toString")
    undefined

  

Creating, deleting and defining properties only affects the first object in a prototype chain:
  obj.propName = value
    obj["propName"] = value

    delete obj.propName
    delete obj["propName"]

    Object.defineProperty(obj, propName, desc)
    Object.defineProperties(obj, descObj)

  

Best practices

The general rule is that properties created by the system are non-enumerable, while properties created by users are enumerable:
  > Object.keys([])
    []
    > Object.getOwnPropertyNames([])
    [ 'length' ]
    > Object.keys(['a'])
    [ '0' ]

  

That especially holds for the methods in prototype objects:
 > Object.keys(Object.prototype)
    []
    > Object.getOwnPropertyNames(Object.prototype)
    [ hasOwnProperty',
      'valueOf',
      'constructor',
      'toLocaleString',
      'isPrototypeOf',
      'propertyIsEnumerable',
      'toString' ]

  

Thus, for your code, you should ignore enumerability. You normally shouldn’t add properties to built-in prototypes and objects, but if you do, you should make them non-enumerable to avoid breaking code.

As we have seen, non-enumerability mostly benefits for-in and ensures that legacy code using it won’t break. The non-enumerable properties create the illusion that for-in only iterates over the user-created own properties of an object. In your code, you should avoid for-in if you can [3].

If you use objects as maps from strings to values, you should only work with own properties and ignore enumerability. But there are more pitfalls for this use case [4].

Conclusion

In this post, we have examined the nature of properties, which is defined via so-called attributes. Note that actual JavaScript implementations do not necessarily organize properties via attributes, they are mainly an abstraction used by the ECMAScript specification. But one that is sometimes visible in the language itself, for example in property descriptors.

Further reading on 2ality:

References

  1. JavaScript: __proto__
  2. What object is not an instance of Object?
  3. Iterating over arrays and objects in JavaScript
  4. The pitfalls of using objects as maps in JavaScript

转载于:https://www.cnblogs.com/hephec/p/4589999.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值