[ Web Proxy ]
URL:
Viewing: https://javascript.info/prototype-methods [Back]  [Original]

Prototype methods, objects without __proto__
EN

We want to make this open-source project available for people all around the world.

Help to translate the content of this tutorial to your language!

    Search on Javascript.info:
    Search in the tutorial:
    Light themeDark theme
    DanskEnglishEspaolFranaisIndonesiaItalianoTrkeOzbek

    Prototype methods, objects without __proto__

    In the first chapter of this section, we mentioned that there are modern methods to setup a prototype.

    Setting or reading the prototype with obj.__proto__ is considered outdated and somewhat deprecated (moved to the so-called Annex B of the JavaScript standard, meant for browsers only).

    The modern methods to get/set a prototype are:

    The only usage of __proto__, thats not frowned upon, is as a property when creating a new object: { __proto__: ... }.

    Although, theres a special method for this too:

    For instance:

    let animal = {
      eats: true
    };
    
    // create a new object with animal as a prototype
    let rabbit = Object.create(animal); // same as {__proto__: animal}
    
    alert(rabbit.eats); // true
    
    alert(Object.getPrototypeOf(rabbit) === animal); // true
    
    Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}

    The Object.create method is a bit more powerful, as it has an optional second argument: property descriptors.

    We can provide additional properties to the new object there, like this:

    let animal = {
      eats: true
    };
    
    let rabbit = Object.create(animal, {
      jumps: {
        value: true
      }
    });
    
    alert(rabbit.jumps); // true

    The descriptors are in the same format as described in the chapter Property flags and descriptors.

    We can use Object.create to perform an object cloning more powerful than copying properties in for..in:

    let clone = Object.create(
      Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)
    );

    This call makes a truly exact copy of obj, including all properties: enumerable and non-enumerable, data properties and setters/getters everything, and with the right [[Prototype]].

    Brief history

    Therere so many ways to manage [[Prototype]]. How did that happen? Why?

    Thats for historical reasons.

    The prototypal inheritance was in the language since its dawn, but the ways to manage it evolved over time.

    • The prototype property of a constructor function has worked since very ancient times. Its the oldest way to create objects with a given prototype.
    • Later, in the year 2012, Object.create appeared in the standard. It gave the ability to create objects with a given prototype, but did not provide the ability to get/set it. Some browsers implemented the non-standard __proto__ accessor that allowed the user to get/set a prototype at any time, to give more flexibility to developers.
    • Later, in the year 2015, Object.setPrototypeOf and Object.getPrototypeOf were added to the standard, to perform the same functionality as __proto__. As __proto__ was de-facto implemented everywhere, it was kind-of deprecated and made its way to the Annex B of the standard, that is: optional for non-browser environments.
    • Later, in the year 2022, it was officially allowed to use __proto__ in object literals {...} (moved out of Annex B), but not as a getter/setter obj.__proto__ (still in Annex B).

    Why was __proto__ replaced by the functions getPrototypeOf/setPrototypeOf?

    Why was __proto__ partially rehabilitated and its usage allowed in {...}, but not as a getter/setter?

    Thats an interesting question, requiring us to understand why __proto__ is bad.

    And soon well get the answer.

    Dont change [[Prototype]] on existing objects if speed matters

    Technically, we can get/set [[Prototype]] at any time. But usually we only set it once at the object creation time and dont modify it anymore: rabbit inherits from animal, and that is not going to change.

    And JavaScript engines are highly optimized for this. Changing a prototype on-the-fly with Object.setPrototypeOf or obj.__proto__= is a very slow operation as it breaks internal optimizations for object property access operations. So avoid it unless you know what youre doing, or JavaScript speed totally doesnt matter for you.

    "Very plain" objects

    As we know, objects can be used as associative arrays to store key/value pairs.

    But if we try to store user-provided keys in it (for instance, a user-entered dictionary), we can see an interesting glitch: all keys work fine except "__proto__".

    Check out the example:

    let obj = {};
    
    let key = prompt("What's the key?", "__proto__");
    obj[key] = "some value";
    
    alert(obj[key]); // [object Object], not "some value"!

    Here, if the user types in __proto__, the assignment in line 4 is ignored!

    That could surely be surprising for a non-developer, but pretty understandable for us. The __proto__ property is special: it must be either an object or null. A string can not become a prototype. Thats why assigning a string to __proto__ is ignored.

    But we didnt intend to implement such behavior, right? We want to store key/value pairs, and the key named "__proto__" was not properly saved. So thats a bug!

    Here the consequences are not terrible. But in other cases we may be storing objects instead of strings in obj, and then the prototype will indeed be changed. As a result, the execution will go wrong in totally unexpected ways.

    Whats worse usually developers do not think about such possibility at all. That makes such bugs hard to notice and even turn them into vulnerabilities, especially when JavaScript is used on server-side.

    Unexpected things also may happen when assigning to obj.toString, as its a built-in object method.

    How can we avoid this problem?

    First, we can just switch to using Map for storage instead of plain objects, then everythings fine:

    let map = new Map();
    
    let key = prompt("What's the key?", "__proto__");
    map.set(key, "some value");
    
    alert(map.get(key)); // "some value" (as intended)

    But Object syntax is often more appealing, as its more concise.

    Fortunately, we can use objects, because language creators gave thought to that problem long ago.

    As we know, __proto__ is not a property of an object, but an accessor property of Object.prototype:

    []

    So, if obj.__proto__ is read or set, the corresponding getter/setter is called from its prototype, and it gets/sets [[Prototype]].

    As it was said in the beginning of this tutorial section: __proto__ is a way to access [[Prototype]], it is not [[Prototype]] itself.

    Now, if we intend to use an object as an associative array and be free of such problems, we can do it with a little trick:

    let obj = Object.create(null);
    // or: obj = { __proto__: null }
    
    let key = prompt("What's the key?", "__proto__");
    obj[key] = "some value";
    
    alert(obj[key]); // "some value"

    Object.create(null) creates an empty object without a prototype ([[Prototype]] is null):

    []

    So, there is no inherited getter/setter for __proto__. Now it is processed as a regular data property, so the example above works right.

    We can call such objects very plain or pure dictionary objects, because they are even simpler than the regular plain object {...}.

    A downside is that such objects lack any built-in object methods, e.g. toString:

    let obj = Object.create(null);
    
    alert(obj); // Error (no toString)

    But thats usually fine for associative arrays.

    Note that most object-related methods are Object.something(...), like Object.keys(obj) they are not in the prototype, so they will keep working on such objects:

    let chineseDictionary = Object.create(null);
    chineseDictionary.hello = "";
    chineseDictionary.bye = "";
    
    alert(Object.keys(chineseDictionary)); // hello,bye

    Summary

    • To create an object with the given prototype, use:

      The Object.create provides an easy way to shallow-copy an object with all descriptors:

      let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
    • Modern methods to get/set the prototype are:

    • Getting/setting the prototype using the built-in __proto__ getter/setter isnt recommended, its now in the Annex B of the specification.

    • We also covered prototype-less objects, created with Object.create(null) or {__proto__: null}.

      These objects are used as dictionaries, to store any (possibly user-generated) keys.

      Normally, objects inherit built-in methods and __proto__ getter/setter from Object.prototype, making corresponding keys occupied and potentially causing side effects. With null prototype, objects are truly empty.

    Tasks

    importance: 5

    Theres an object dictionary, created as Object.create(null), to store any key/value pairs.

    Add method dictionary.toString() into it, that should return a comma-delimited list of keys. Your toString should not show up in for..in over the object.

    Heres how it should work:

    let dictionary = Object.create(null);
    
    // your code to add dictionary.toString method
    
    // add some data
    dictionary.apple = "Apple";
    dictionary.__proto__ = "test"; // __proto__ is a regular property key here
    
    // only apple and __proto__ are in the loop
    for(let key in dictionary) {
      alert(key); // "apple", then "__proto__"
    }
    
    // your toString in action
    alert(dictionary); // "apple,__proto__"
    solution

    The method can take all enumerable keys using Object.keys and output their list.

    To make toString non-enumerable, lets define it using a property descriptor. The syntax of Object.create allows us to provide an object with property descriptors as the second argument.

    let dictionary = Object.create(null, {
      toString: { // define toString property
        value() { // the value is a function
          return Object.keys(this).join();
        }
      }
    });
    
    dictionary.apple = "Apple";
    dictionary.__proto__ = "test";
    
    // apple and __proto__ is in the loop
    for(let key in dictionary) {
      alert(key); // "apple", then "__proto__"
    }
    
    // comma-separated list of properties by toString
    alert(dictionary); // "apple,__proto__"

    When we create a property using a descriptor, its flags are false by default. So in the code above, dictionary.toString is non-enumerable.

    See the chapter Property flags and descriptors for review.

    importance: 5

    Lets create a new rabbit object:

    function Rabbit(name) {
      this.name = name;
    }
    Rabbit.prototype.sayHi = function() {
      alert(this.name);
    };
    
    let rabbit = new Rabbit("Rabbit");

    These calls do the same thing or not?

    rabbit.sayHi();
    Rabbit.prototype.sayHi();
    Object.getPrototypeOf(rabbit).sayHi();
    rabbit.__proto__.sayHi();
    solution

    The first call has this == rabbit, the other ones have this equal to Rabbit.prototype, because its actually the object before the dot.

    So only the first call shows Rabbit, other ones show undefined:

    function Rabbit(name) {
      this.name = name;
    }
    Rabbit.prototype.sayHi = function() {
      alert( this.name );
    }
    
    let rabbit = new Rabbit("Rabbit");
    
    rabbit.sayHi();                        // Rabbit
    Rabbit.prototype.sayHi();              // undefined
    Object.getPrototypeOf(rabbit).sayHi(); // undefined
    rabbit.__proto__.sayHi();              // undefined
    Tutorial map

    Comments

    read this before commenting
    • If you have suggestions what to improve - please submit a GitHub issue or a pull request instead of commenting.
    • If you can't understand something in the article please elaborate.
    • To insert few words of code, use the <code> tag, for several lines wrap them in <pre> tag, for more than 10 lines use a sandbox (plnkr, jsbin, codepen)

    Web Proxy Viewer  |  New URL  |  Original Page