JSlang.dev

JavaScript Deep Dives with Wolfram. Since 2015
Inclusive, Test-Driven, Spec-Focused, Collaborative.

March 21, 2024
TestsSource
#70 Prototype

Source Code

The source code we wrote during the event.

import {strict as assert} from 'assert';

describe('creating a bare object give it prototype', () => {
  it('instantiating `Object(null)` has the prototype `undefined`', () => {
    const instance = new Object(null);
    assert.equal(instance.prototype, undefined);
  });
  it('`null` does not strictly equal `undefined`', () => {
    assert.notEqual(null, undefined);
  });
  it('instantiating `Object()` has the prototype `undefined`', () => {
    const instance = new Object();
    assert.equal(instance.prototype, undefined);
  });
  it('instantiating an object with {x: 1} the prototype is `undefined`', () => {
    const instance = new Object({x: 1});
    assert.equal(instance.prototype, undefined);
  });
  it('demonstrating instantiating a function that has a prototype', () => {
    function Klass() {}
    Klass.prototype = {
      x: function() { return 42; }
    };
    const instance = new Klass();
    assert.equal(instance.x(), 42);
  });
  it('instantiate a function with a prototype and we can set a class property inside the constructor', () => {
    function Klass() {
      this.a = 1;
    }
    Klass.prototype = {
      b: function() { return 2; }
    }
    const instance = new Klass();
    assert.equal(instance.a, 1);
    assert.equal(typeof instance.b, 'function');
  });

  it('we can get the prototype via __proto__ or Reflect.getPrototypeOf()', () => {
    function Klass() {}
    Klass.prototype = {};
    const instance = new Klass();
    assert(instance.__proto__ === Klass.prototype);
    assert(Reflect.getPrototypeOf(instance) === Klass.prototype);
  });
  it('get the prototype like above, just for a new class', () => {
    class Klass {}
    const instance = new Klass();
    assert(instance.__proto__ === Klass.prototype);
    assert(Reflect.getPrototypeOf(instance) === Klass.prototype);
  });
});

describe('is there a minimum prototype', () => {
  it('setting the prototype to null reports an empty prototype', () => {
    class Klass {}
    Reflect.setPrototypeOf(Klass, null);
    const instance = new Klass();
    assert.deepEqual(Reflect.getPrototypeOf(instance), {});
  });
  it('an instance of Object.create(null) has a prototype of null', () => {
    const obj = Object.create(null);
    assert.equal(Object.getPrototypeOf(obj), null);
  });
});

//
// Additions I have to make AFTER the meetup, to clarify some things we had been struggling with and didn't find the right answers to
// 
describe('clarifications', () => {
  describe('the prototype', () => {
    // https://tc39.es/ecma262/#sec-function-instances-prototype
    //    Function instances that can be used as a constructor have a "prototype" property. 
    //    Whenever such a Function instance is created another ordinary object is also created 
    //    and is the initial value of the function's "prototype" property. Unless otherwise specified, 
    //    the value of the "prototype" property is used to initialize the [[Prototype]] internal slot 
    //    of the object created when that function is invoked as a constructor.
    
    // [[construct]] does the following:
    //    OrdinaryCreateFromConstructor(newTarget, "%Object.prototype%").
    // which does the following, with `intrinsicDefaultProto` being `%Object.prototype%` (see above):
    //    https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor
    //    It creates an ordinary object whose [[Prototype]] value is retrieved from a constructor's "prototype" property, if it exists. 
    //    Otherwise the intrinsic named by intrinsicDefaultProto is used for [[Prototype]]
    
    // https://tc39.es/ecma262/#sec-built-in-function-objects
    //    The initial value of a built-in function object's [[Prototype]] internal slot is %Function.prototype%
    
    it('the prototype of the constructor (which is just a function) is the function prototype', () => {
      function KlassWithoutPrototype() {}
      assert.equal(Reflect.getPrototypeOf(KlassWithoutPrototype), Function.prototype);
      assert.equal(KlassWithoutPrototype.__proto__, Function.prototype);
    });
    it('the prototype of the instance points to `TheClass.prototype`', () => {
      function KlassWithoutPrototype() {}
      const instance = new KlassWithoutPrototype();
      assert.equal(Reflect.getPrototypeOf(instance), KlassWithoutPrototype.prototype);
      assert.equal(instance.__proto__, KlassWithoutPrototype.prototype); // the depreacted way
    });
    it('if no prototype is given, then an ordinary object is used', () => {
      function KlassWithoutPrototype() {}
      const instance = new KlassWithoutPrototype();
      
      const instancesPrototype = Reflect.getPrototypeOf(instance); // this one is just an ordinary object, so test for this below
      assert.equal(KlassWithoutPrototype.prototype instanceof Object, true);
      assert.equal(Reflect.getPrototypeOf(instancesPrototype), Object.prototype);
      assert.equal(instancesPrototype instanceof Object, true);
    });
  });
});


// inheritance explained:
//    https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
//    All ordinary objects have an internal slot called [[Prototype]]. The value of this internal slot is either null 
//    or an object and is used for implementing inheritance. Assume a property named P is missing from an ordinary object O 
//    but exists on its [[Prototype]] object. If P refers to a data property on the [[Prototype]] object, 
//    O inherits it for get access, making it behave as if P was a property of O. If P refers to a writable data property 
//    on the [[Prototype]] object, set access of P on O creates a new data property named P on O. 
//    If P refers to a non-writable data property on the [[Prototype]] object, set access of P on O fails. If P refers 
//    to an accessor property on the [[Prototype]] object, the accessor is inherited by O for both get access and set access.