Skip to content

Language 03

Property flags

  • Object properties, besides a value, have three special attributes (so-called “flags”):
    • writable – if true, the value can be changed, otherwise it’s read-only.
    • enumerable – if true, then listed in loops, otherwise not listed.
    • configurable – if true, the property can be deleted and these attributes can be modified, otherwise not.
  • By default – all true

Property flags 2

1
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
let user = {
    name: "John"
};

let descriptor = Object.getOwnPropertyDescriptor(user, "name");

alert(JSON.stringify(descriptor, null, 2));

/* property descriptor
{
    "value": "John",
    "writable": true,
    "enumerable": true,
    "configurable": true
}
*/

Property flags 3

To change the flags

1
Object.defineProperty(obj, propertyName, descriptor)

If the property exists, defineProperty updates its flags. Otherwise, it creates the property with the given value and flags; in that case, if a flag is not supplied, it is assumed false.

Property flags 4

  • Writable:false – can’t be reassigned
  • Enumerable:false – won’t appear in for..in loop
  • Configurable:false - can’t be configured
    • Can’t change configurable flag.
    • Can’t change enumerable flag.
    • Can’t change writable: false to true (the other way round works).
    • Can’t change get/set for an accessor property (but can assign them if absent).

Property flags 5

  • Accessor descriptors
  • For accessor properties, there is no value or writable, but instead there are get and set functions.
    • get – a function without arguments, that works when a property is read,
    • set – a function with one argument, that is called when the property is set,
  • Enumerable and configurable – same as for data properties,
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let user = {
    name: "John",
    surname: "Smith"
};

Object.defineProperty(user, "fullName", {
    get() {
        return `${this.name} ${this.surname}`;
    },
    set(value) {
        [this.name, this.surname] = value.split(" ");
    }
});

Prototypal inheritance

  • In JavaScript, all objects have a hidden [[Prototype]] property that’s either another object or null.
  • We can use obj.__proto__ to access it (a historical getter/setter).
  • In modern language it is replaced with functions Object.getPrototypeOf() and Object.setPrototypeOf()
  • The object referenced by [[Prototype]] is called a “prototype”.
  • If we want to read a property of object or call a method, and it doesn’t exist, then JavaScript tries to find it in the prototype.
  • Write/delete operations act directly on the object, they don’t use the prototype (assuming it’s a data property, not a setter).
  • If we call obj.method(), and the method is taken from the prototype, this still references obj. So methods always work with the current object even if they are inherited.
  • The for..in loop iterates over both its own and its inherited properties. All other key/value-getting methods only operate on the object itself.

Prototypal inheritance

F.prototype property of constructor function

  • If F.prototype is an object, then the new operator uses it to set [[Prototype]] for the new object.
  • F.prototype means a regular property named "prototype" on some function F
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let animal = {
    eats: true
};

function Rabbit(name) {
    this.name = name;
}

Rabbit.prototype = animal;

let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal;

alert(rabbit.eats); // true

Default F.prototype, constructor property - 1

  • Every function has the "prototype" property even if we don’t supply it.
  • The default "prototype" is an object with the only property “constructor” that points back to the function itself.
1
2
3
4
5
function Rabbit() {}

/* default prototype
Rabbit.prototype = { constructor: Rabbit };
*/

Default F.prototype, constructor property - 2

  • …JavaScript itself does not ensure the right "constructor" value.
  • If we replace the default prototype as a whole, then there will be no "constructor" in it.
  • On regular objects the ”prototype” is nothing special, just a property

Native prototypes

  • The "prototype" property is widely used by the core of JavaScript itself. All built-in constructor functions use it.
  • The short notation obj = {} is the same as obj = new Object(), where Object is a built-in object constructor function, with its own prototype referencing a object with toString and other methods.
1
2
let obj = {};
alert(obj); // "[object Object]" ?

Built in prototypes 1

Native objects inheritance tree.

Prototype inheritance tree

Built in prototypes 2

  • Native prototypes can be modified. Add a method to String.prototype, it becomes available to all strings.
  • Prototypes are global, so it’s easy to get a conflict.
  • Primitives – string, number, bool
    • They are not objects. But if we try to access their properties, temporary wrapper objects are created using built-in constructors String, Number and Boolean. They provide the methods and disappear.
1
2
3
4
5
String.prototype.show = function() {
    alert(this);
};

"BOOM".show(); // BOOM!

Classes 1

  • JS Class syntax
  • constructor() method is created automatically
  • No comma between class methods
  • Strict mode – always in use inside class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class MyClass {
  // class methods
  constructor() { ... }
  method1() { ... }
  method2() { ... }
  method3() { ... }
  ...
}

let x = new MyClass();

Classes 2

  • What is class in JS?
    • It’s kind of function
    • Creates a function named with ClassName
    • Function code is taken from constructor
    • All class methods are stored in function prototype
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// rewriting class User in pure functions

function User(name) {
    this.name = name;
}

// any function prototype has constructor property by default so we don't need to create it

// 2. Add the method to prototype
User.prototype.sayHi = function() {
    alert(this.name);
}

// Usage
let user = new User("John");
user.sayHi();

Class methods and prototype

Classes 3

Is it just syntactic sugar? Not entirely:

  • function created by class is labelled by a special internal property [[FunctionKind]]:"classConstructor"
  • a class constructor must be called with new
  • string representation of a class constructor in most JavaScript engines starts with the "class…"

Class Expression

Like functions, classes can be defined inside another expression, passed around, returned, assigned, etc.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
let User = function() {
    sayHi() {
        alert("Hello");
    }
};

function makeClass(phrase) {
    // declare a class and return it
    return class {
        sayHi() {
            alert(phase);
        };
    };
}

// Create a new class
let User = makeClass("Hello");

new User().sayHi(); // Hello

Getters/Setters

Like literal objects, classes may include getters/setters, computed properties etc.

1
2
3
4
5
6
class User {
    ['say' + 'Hi']() {
        alert("Hello");
    }
}
new User().sayHi();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class User {
    constructor(name) {
        // invokes the setter
        this.name = name;
    }
    get name() {
        return this._name;
    }
    set name(value) {
        if (value.length < 4) {
            alert("Name is too short.");
            return;
        }
        this._name = value;
    }
}

let user = new User("John");
alert(user.name); // John
user = new User(""); // Name is too short.

Class properties

Instance properties must be defined inside of class methods

1
2
3
4
5
6
class Rectangle {
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
}

From 2020/2021 forward we can also declare public properties directly

1
2
3
4
5
6
class ClassWithField {
    instanceField;
    instanceFieldWithInitializer = "instance field";
    static staticField;
    static staticFieldWithInitializer = "static field";
}

Static class members

Static methods are often utility functions, such as functions to create or clone objects, whereas static properties are useful for caches, fixed-configuration, or any other data you don't need to be replicated across instances.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class ClassWithStaticMethod {

    static staticProperty = 'someValue';

    static staticMethod() {
        return 'static method has been called.';
    }

    static {
        console.log('Class static initialization block called');
    }
}
  • The static keyword defines a static member for a class.
  • Static methods are called without instantiating their class and cannot be called through a class instance.
  • Static methods are often used to create utility functions for an application.

Private class members

Start name with hash - #.
Supported from ca 2020/2021.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class ClassWithPrivate {
    #privateField;
    #privateFieldWithInitializer = 42;

    #privateMethod() {
        // …
    }

    static #privateStaticField;
    static #privateStaticFieldWithInitializer = 42;

    static #privateStaticMethod() {
        // …
    }
}

Private properties get created by using a hash # prefix and cannot be legally referenced outside of the class. The privacy encapsulation of these class properties is enforced by JavaScript itself.

Classes - sub classing - extends

  • class Dog extends Animal {
  • If there is a constructor present in the subclass, it needs to first call super() before using this.
  • When used in a constructor, the super keyword appears alone and must be used before the this keyword is used. The super keyword can also be used to call functions on a parent object.
1
2
super([arguments]); // calls the parent constructor
super.functionOnParent([arguments]);

Class extends and prototype

Operator instanceof

  • The instanceof operator allows to check whether an object belongs to a certain class.
  • It also takes inheritance into account.
  • It returns true if obj belongs to the Class or a class inheriting from it.
  • rabbit instanceof Animal

Class extends and prototype

Error handling, try..catch

  • try..catch only works for runtime errors
  • try..catch works synchronously
  • Error object
    • name – error name
    • Message – textual message
  • Throw your own - throw
  • Error, SyntaxError, ReferenceError, TypeError and others. let error = new Error(message);
1
2
3
4
5
6
7
try {
    //... try to execute the code ...
} catch (e) {
    //...handle errors ...
} finally {
    //...execute always ...
}

Modules 1

  • When projects get huge, we want to split our code into multiple files – “module”
  • Module usually contains a class or library or function
  • Variety of ways where invented
    • AMD – one of the most ancient module systems, initially implemented by the library require.js.
    • CommonJS – the module system created for Node.js server.
    • UMD – one more module system, suggested as a universal one, compatible with AMD and CommonJS.
  • The language-level module system appeared in the standard in 2015, gradually evolved since then, and is now supported by all major browsers and in Node.js.

Modules 2

  • Modules can load each other and use special directives export and import to interchange functionality, call functions of one module from another one:
    • export keyword labels variables and functions that should be accessible from outside the current module.
    • import allows the import of functionality from other modules.
1
2
3
4
// file sayHi.js
export function sayHi(user) {
    alert(`Hello, ${user}!`);
}
1
2
3
4
import {sayHi} from './sayHi.js';

alert(sayHi); // function...
sayHi('John'); // Hello, John!

Modules 3

  • Always in strict mode
  • Module has its own scope
  • A module code is evaluated only the first time when imported
    • Exports are generated, and then they are shared between importers!
  • Top level this is undefined

Modules - Export

  • Before declaration of a class/function/…:
    • export [default] class/function/variable ...
  • Standalone export:
    • export {x [as y], ...}.
  • Re-export:
    • export {x [as y], ...} from "module"
    • export * from "module" (doesn’t re-export default).
    • export {default [as y]} from "module" (re-export default).

Modules - Import

  • Named exports from module:
    • import {x [as y], ...} from "module"
  • Default export:
    • import x from "module"
    • import {default as x} from "module"
  • Everything:
    • import * as obj from "module"
  • Import the module (its code runs), but do not assign it to a variable:
    • import "module"


If you do not use build mechanism (webpack), then your code that you include inside html files needs to be included as module!
Imports are only supported inside modules.

1
<script src="test.js" type="module"></script>

not as

1
<script src="test.js" type="text/javascript"></script>

Modules - Dynamic import

The import(module) expression loads the module and returns a promise that resolves into a module object that contains all its exports. It can be called from any place in the code.

1
let {hi, bye} = await import('./say.js);