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
| 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
| 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.
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.
| 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.
| let obj = {};
alert(obj); // "[object Object]" ?
|
Built in prototypes 1
Native objects 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.
| 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
| 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();
|
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
|
Like literal objects, classes may include getters/setters, computed properties etc.
| 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
| class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
|
From 2020/2021 forward we can also declare public properties directly
| 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.
| super([arguments]); // calls the parent constructor
super.functionOnParent([arguments]);
|
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
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);
| 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.
| // file sayHi.js
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
|
| 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:
- 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:
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.
| <script src="test.js" type="module"></script>
|
not as
| <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.
| let {hi, bye} = await import('./say.js’);
|