zoukankan      html  css  js  c++  java
  • 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 key Default 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 prototypeObject.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 useObject.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
  • 相关阅读:
    什么是软件质量?
    软件生存周期及其模型是什么?
    给你一个网站,你如何测试?
    jquery中$.get()提交和$.post()提交有区别吗?
    JQuery有几种选择器?
    ajax和layui总结
    md5加密
    Collection接口相关介绍
    JS 中document.URL 和 windows.location.href 的区别
    window.location.href的用法(动态输出跳转)
  • 原文地址:https://www.cnblogs.com/hephec/p/4589999.html
Copyright © 2011-2022 走看看