demo2s-3

Must Watch!



MustWatch



Object Data Properties

Data properties contain a single location for a data value. Values are read from and written to this location. Data properties have four attributes describing their behavior:
Property Description
[[Configurable]] Indicates if the property may be redefined by removing the property via delete, changing the property's attributes, or changing the property into an accessor property.
By default, this is true for all properties defined directly on an object.
[[Enumerable]] Indicates if the property will be returned in a for-in loop.
By default, this is true for all properties defined directly on an object.
[[Writable]] Indicates if the property's value can be changed.
By default, this is true for all properties defined directly on an object.
[[Value]] Contains the actual data value for the property.
This is the location from which the property's value is read and the location to which new values are saved.
The default value for this attribute is undefined.
When a property is explicitly added to an object, [[Configurable]], [[Enumerable]], and [[Writable]] are all set to true while the [[Value]] attribute is set to the assigned value. For example: let person = { name: "CSS" }; Here, the property called name is created and a value of "CSS" is assigned. That means [[Value]] is set to "CSS", and any changes to that value are stored in this location. To change any of the default property attributes, you must use the Object.defineProperty() method. This method accepts three arguments: the object on which the property should be added or modified, the name of the property, and a descriptor object. The properties on the descriptor object match the attribute names: configurable, enumerable, writable, and value. You can set one or all of these values to change the corresponding attribute values. For example: let person = {}; Object.defineProperty(person, "name", { writable: false, value: "CSS" }); console.log(person.name); // "CSS" person.name = "HTML"; console.log(person.name); // "CSS" This example creates a property called name with a value of "CSS" that is read-only. The value of this property can't be changed, and any attempts to assign a new value are ignored in non strict mode. In strict mode, an error is thrown when an attempt is made to change the value of a read-only property. Similar rules apply to creating a non-configurable property. For example: let person = {}; Object.defineProperty(person, "name", { configurable: false, value: "CSS" }); console.log(person.name); // "CSS" delete person.name; console.log(person.name); // "CSS" Here, setting configurable to false means that the property cannot be removed from the object. Calling delete on the property has no effect in non strict mode and throws an error in strict mode. Additionally, once a property has been defined as nonconfigurable, it cannot become configurable again. Any attempt to call Object.defineProperty() and change any attribute other than writable causes an error: let person = {}; Object.defineProperty(person, "name", { configurable: false, value: "CSS" }); // Throws an error Object.defineProperty(person, "name", { configurable: true, value: "CSS" }); So although you can call Object.defineProperty() multiple times for the same property, there are limits once configurable has been set to false. When you are using Object.defineProperty(), the values for configurable, enumerable, and writable default to false unless otherwise specified.

Object Accessor Properties

Accessor properties do not contain a data value. Instead, they contain a combination of a getter function and a setter function. When an accessor property is read from, the getter function is called, and it's the function's responsibility to return a valid value; when an accessor property is written to, a function is called with the new value, and that function must decide how to react to the data. Accessor properties have four attributes:
Property Description
[[Configurable]] Indicates if the property may be redefined by removing the property via delete, changing the property's attributes, or changing the property into a data property.
By default, this is true for all properties defined directly on an object.
[[Enumerable]] Indicates if the property will be returned in a for-in loop.
By default, this is true for all properties defined directly on an object.
[[Get]] The function to call when the property is read from.
The default value is undefined.
[[Set]] The function to call when the property is written to.
The default value is undefined.
It is not possible to define an accessor property explicitly; you must use Object.defineProperty(). Here's a simple example: // Define object with pseudo-private member 'year_' and // public member 'edition' let book = {// ww w . d e m o 2s .c om year_: 2017, edition: 1 }; Object.defineProperty(book, "year", { get() { return this.year_; }, set(newValue) { if (newValue > 2017) { this.year_ = newValue; this.edition += newValue - 2017; } } }); book.year = 2018; console.log(book.edition); // 2 In this code, an object book is created with two default properties: year_ and edition. The underscore on year_ indicates that a property is not intended to be accessed from outside of the object's methods. The year property is defined to be an accessor property where the getter function simply returns the value of year_ and the setter does some calculation to determine the correct edition. So changing the year property to 2018 results in both year_ and edition changing to 2. This is a typical use case for accessor properties, when setting a property value results in some other changes to occur. It's not necessary to assign both a getter and a setter. Assigning just a getter means that the property cannot be written to and attempts to do so will be ignored. In strict mode, trying to write to a property with only a getter throws an error. Likewise, a property with only a setter cannot be read and will return the value undefined in non strict mode, while doing so throws an error in strict mode. There is no way to modify [[Configurable]] or [[Enumerable]] in browsers that don't support Object.defineProperty().

Object Defining Multiple Properties

To define more than one property on an object, ECMAScript provides the Object.defineProperties() method. This method allows you to define multiple properties using descriptors at once. There are two arguments: the object on which to add or modify the properties and an object whose property names correspond to the properties' names to add or modify. For example: let book = {};/* w w w . d e m o 2 s . c o m */ Object.defineProperties(book, { year_: { value: 2017 }, edition: { value: 1 }, year: { get() { return this.year_; }, set(newValue) { if (newValue > 2017) { this.year_ = newValue; this.edition += newValue - 2017; } } } }); This code defines two data properties, year_ and edition, and an accessor property called year on the book object. The only difference is that all of these properties are created at the same time.

Object Reading Property Attributes

We can retrieve the property descriptor for a given property by using the Object.getOwnPropertyDescriptor() method. This method accepts two arguments: the object on which the property resides and the name of the property whose descriptor should be retrieved. The return value is an object with properties for configurable, enumerable, get, and set for accessor properties or configurable, enumerable, writable, and value for data properties. For example: let book = {};/*w w w . d e m o 2 s .c o m*/ Object.defineProperties(book, { year_: { value: 2017 }, edition: { value: 1 }, year: { get: function() { return this.year_; }, set: function(newValue){ if (newValue > 2017) { this.year_ = newValue; this.edition += newValue - 2017; } } } }); let descriptor = Object.getOwnPropertyDescriptor(book, "year_"); console.log(descriptor.value); // 2017 console.log(descriptor.configurable); // false console.log(typeof descriptor.get); // "undefined" let descriptor = Object.getOwnPropertyDescriptor(book, "year"); console.log(descriptor.value); // undefined console.log(descriptor.enumerable); // false console.log(typeof descriptor.get); // "function" For the data property year_, value is equal to the original value, configurable is false, and get is undefined. For the accessor property year, value is undefined, enumerable is false, and get is a pointer to the specified getter function. ECMAScript Object.getOwnPropertyDescriptors() static method performs on Object.getOwnPropertyDescriptor() on all own properties and returns them in a new object. For the previous example, using this static method would return the following object: let book = {};// w w w . d e m o2 s . c om Object.defineProperties(book, { year_: { value: 2017 }, edition: { value: 1 }, year: { get: function() { return this.year_; }, set: function(newValue){ if (newValue > 2017) { this.year_ = newValue; this.edition += newValue - 2017; } } } }); console.log(Object.getOwnPropertyDescriptors(book)); Output:

Object Merging Objects

JavaScript developers will often find that it is useful to be able to perform a "merge" of two objects. This merge will move all the local properties of one source object into a destination object. This behavior is also referred to as using a "mixin," in which the destination object is augmented by mixing in the properties of a source object. ECMAScript 6 introduces this exact behavior with the Object.assign() method. This method accepts one destination object, and one or many source objects, and for each source object copies the enumerable (Object.propertyIsEnumerable returns true) and own (Object.hasOwnProperty returns true) properties onto the destination object. Properties keyed with strings and symbols will be copied. For each suitable property, the method will use [[Get]] to retrieve a value from the source object and [[Set]] on the destination object to assign the value. The following code shows a simple copy. let dest, src, result;. dest = {}; src = { id: 'src' }; result = Object.assign(dest, src); // Object.assign mutates the destination object // and also returns that object after exiting. console.log(dest === result); // true console.log(dest !== src); // true console.log(result); // { id: src } console.log(dest); // { id: src } Multiple source objects dest = {}; result = Object.assign(dest, { a: 'foo' }, { b: 'bar' }); console.log(result); // { a: foo, b: bar } Invoke Getters and setters dest = { set a(val) { console.log('Invoked dest setter with param ${val}'); } }; src = { get a() { console.log('Invoked src getter'); return 'foo'; } }; Object.assign(dest, src); // Invoked src getter // Invoked dest setter with param foo // Since the setter does not perform an assignment, // no value is actually transferred console.log(dest); // { set a(val) {...} } Output: Object.assign() is effectively performing a shallow copy from each source object. If multiple source objects have the same property defined, the last one to be copied will be the ultimate value. Any value retrieved from accessor properties, such as a getter, on a source object will be assigned as a static value on the destination object. It cannot transfer getters and setters between objects. let dest, src, result; /** * Overwritten properties */ dest = { id: 'dest' }; result = Object.assign(dest, { id: 'src1', a: 'foo' }, { id: 'src2', b: 'bar' }); // Object.assign will overwrite duplicate properties. console.log(result); // { id: src2, a: foo, b: bar } This can be observed by using a setter on the destination object: dest = { set id(x) { console.log(x); } }; Object.assign(dest, { id: 'first' }, { id: 'second' }, { id: 'third' }); Output: Object references in object merge dest = {}; src = { a: {} }; Object.assign(dest, src); // Shallow property copies means only object references copied. console.log(dest); // { a :{} } console.log(dest.a === src.a); // true If an error is thrown during the assignment, it will discontinue and exit with the thrown error.

Object Identity and Equality

ECMAScript 6 Object.is() behaves mostly as === does but accounts for the corner cases. The method accepts exactly two arguments: console.log(Object.is(true, 1)); // false console.log(Object.is({}, {})); // false console.log(Object.is("2", 2)); // false // Correct 0, -0, +0 equivalence/nonequivalence: console.log(Object.is(+0, -0)); // false console.log(Object.is(+0, 0)); // true console.log(Object.is(-0, 0)); // false // Correct NaN equivalence: console.log(Object.is(NaN, NaN)); // true To check more than two objects, it is trivial to recursively use transitive equality: function recursivelyCheckEqual(x, ...rest) { return Object.is(x, rest[0]) && (rest.length < 2 || recursivelyCheckEqual(...rest)); }

Object Property Value Shorthand

We may want to add a variable to an object, and the property name used to key that variable is the variable name itself. For example: let name = 'HTML'; let person = { name: name }; console.log(person); // { name: 'HTML' } The property value shorthand convention was introduced. It allows you to use the variable itself without the colon notation, and the interpreter will automatically use the variable name as the property key. If the variable name is not found, a ReferenceError will be thrown. The following code is equivalent: let name = 'HTML'; let person = { name }; console.log(person); // { name: 'HTML' }

Deleting Properties from Object

The delete operator removes a property from an object. Its single operand should be a property access expression. delete book.author; // The book object now has no author property. delete book["main title"]; // Now it doesn't have "main title", either. The delete operator only deletes own properties, not inherited ones. To delete an inherited property, you must delete it from the prototype object in which it is defined. Doing this affects every object that inherits from that prototype. A delete expression evaluates to true if the delete succeeded or if the delete had no effect, such as deleting a non existent property. delete also evaluates to true when used with an expression that is not a property access expression: let o = {x: 1}; // o has own property x and inherits property toString delete o.x // => true: deletes property x delete o.x // => true: does nothing (x doesn't exist) but true anyway delete o.toString // => true: does nothing (toString isn't an own property) delete 1 // => true: nonsense, but true anyway delete does not remove properties that have a configurable attribute of false. Certain properties of built-in objects are non-configurable, as are properties of the global object created by variable declaration and function declaration. In strict mode, attempting to delete a non-configurable property causes a TypeError. In non-strict mode, delete simply evaluates to false in this case: // In strict mode, all these deletions // throw TypeError instead of returning false delete Object.prototype // => false: property is non-configurable var x = 1; // Declare a global variable delete globalThis.x // => false: can't delete this property function f() {} // Declare a global function delete globalThis.f // => false: can't delete this property either When deleting configurable properties of the global object in non-strict mode, you can omit the reference to the global object and follow the delete operator with the property name: globalThis.x = 1; // Create a configurable global property (no let or var) delete x // => true: this property can be deleted In strict mode, delete raises a SyntaxError if its operand is an unqualified identifier like x, and you have to be explicit about the property access: delete x; // SyntaxError in strict mode delete globalThis.x; // This works

Object Properties Testing

JavaScript objects can be thought of as sets of properties, and we can test for membership in the set to check whether an object has a property with a given name. You can do this with the in operator, with the hasOwnProperty() and propertyIsEnumerable() methods, or simply by querying the property. The examples shown here all use strings as property names, but they also work with Symbols. The in operator expects a property name on its left side and an object on its right. It returns true if the object has an own property or an inherited property by that name: let o = { x: 1 }; "x" in o // => true: o has an own property "x" "y" in o // => false: o doesn't have a property "y" "toString" in o // => true: o inherits a toString property The hasOwnProperty() method of an object tests whether that object has an own property with the given name. It returns false for inherited properties: let o = { x: 1 }; o.hasOwnProperty("x") // => true: o has an own property x o.hasOwnProperty("y") // => false: o doesn't have a property y o.hasOwnProperty("toString") // => false: toString is an inherited property The propertyIsEnumerable() refines the hasOwnProperty() test. It returns true only if the named property is an own property and its enumerable attribute is true. Certain built-in properties are not enumerable. Properties created by normal JavaScript code are enumerable. let o = { x: 1 }; o.propertyIsEnumerable("x") // => true: o has an own enumerable property x o.propertyIsEnumerable("toString") // => false: not an own property Object.prototype.propertyIsEnumerable("toString") // => false: not enumerable Instead of using the in operator, it is often sufficient to query the property and use !== to make sure it is not undefined: let o = { x: 1 }; o.x !== undefined // => true: o has a property x o.y !== undefined // => false: o doesn't have a property y o.toString !== undefined // => true: o inherits a toString property There is one thing the in operator can do that the simple property access technique shown here cannot do. in operator can distinguish between properties that do not exist and properties that exist but have been set to undefined. Consider this code: let o = { x: undefined }; // Property is explicitly set to undefined o.x !== undefined // => false: property exists but is undefined o.y !== undefined // => false: property doesn't even exist "x" in o // => true: the property exists "y" in o // => false: the property doesn't exist delete o.x; // Delete the property x "x" in o // => false: it doesn't exist anymore

Object Enumerating Properties

Instead of testing for the existence of individual properties, we can iterate through or obtain a list of all the properties of an object. There are a few different ways to do this. The for/in loop runs the body of the loop for each enumerable property (own or inherited) of the specified object, assigning the name of the property to the loop variable. Built-in methods that objects inherit are not enumerable, but the properties that your code adds to objects are enumerable by default. For example: let o = {x: 1, y: 2, z: 3}; // Three enumerable own properties o.propertyIsEnumerable("toString") // => false: not enumerable for(let p in o) { // Loop through the properties console.log(p); // Prints x, y, and z, but not toString } Output: To guard against enumerating inherited properties with for/in, you can add an explicit check inside the loop body: for(let p in o) { if (!o.hasOwnProperty(p)) continue; // Skip inherited properties } for(let p in o) { if (typeof o[p] === "function") continue; // Skip all methods } We can get an array of property names for an object and then loop through that array with a for/of loop. There are four functions you can use to get an array of property names:
Method
Object.keys()
Meaning
returns an array of the names of the enumerable own properties of an object.
It does not include non-enumerable properties, inherited properties, or properties whose name is a Symbol.
Object.getOwnPropertyNames() returns an array of the names of non-enumerable own properties as well, as long as their names are strings.
Object.getOwnPropertySymbols() returns own properties whose names are Symbols, whether or not they are enumerable.
Reflect.ownKeys() returns all own property names, both enumerable and non-enumerable, and both string and Symbol.

Object Computed Property Keys

The following code use constant value as key for object. For example: const nameKey = 'name'; const ageKey = 'age'; const jobKey = 'job'; let person = {}; person[nameKey] = 'HTML'; person[ageKey] = 27; person[jobKey] = 'Mark up'; console.log(person); // { name: 'HTML', age: 27, job: 'Mark up' } With computed properties, the property assignment can occur inside the object literal's initial definition. Square brackets around the object property key instruct the runtime to evaluate its contents as a JavaScript expression instead of a string: const nameKey = 'name'; const ageKey = 'age'; const jobKey = 'job'; let person = { [nameKey]: 'HTML', [ageKey]: 27, [jobKey]: 'Mark up' }; console.log(person); // { name: 'HTML', age: 27, job: 'Mark up' } Because the contents are evaluated as a JavaScript expression, We can use the contents of the computed property complex expressions on instantiation: const nameKey = 'name'; const ageKey = 'age'; const jobKey = 'job'; let uniqueToken = 0; function getUniqueKey(key) { return '${key}_${uniqueToken++}'; } let person = { [getUniqueKey(nameKey)]: 'HTML', [getUniqueKey(ageKey)]: 27, [getUniqueKey(jobKey)]: 'Mark up' }; console.log(person); // { name_0: 'HTML', age_1: 27, job_2: 'Mark up' } Any errors thrown in a computed property key expression will abort the creation of the object.

Object Concise Method Syntax

When defining function properties of an object, the format almost always takes the form of a property key referencing an anonymous function expression, as follows: let person = { sayName: function(name) { console.log('My name is ${name}'); } }; person.sayName('HTML'); // My name is HTML The new shorthand method syntax can shorten a function property. The following is behaviorally identical to the previous code: let person = { sayName(name) { console.log('My name is ${name}'); } }; person.sayName('HTML'); // My name is HTML This is also applicable to the getter and setter object conventions: let person = { name_: '', get name() { return this.name_; }, set name(name) { this.name_ = name; }, sayName() { console.log('My name is ${this.name_}'); } }; person.name = 'HTML'; person.sayName(); // My name is HTML Shorthand method syntax and computed property keys are mutually compatible: const methodKey = 'sayName'; let person = { [methodKey](name) { console.log('My name is ${name}'); } } person.sayName('HTML'); // My name is HTML You will find that the shorthand method syntax is more useful in the context of ECMAScript 6 classes.

Object Destructuring

ECMAScript 6 introduced object destructuring, which allows you to perform one or many operations using nested data within a single statement. We can perform assignments from object properties using syntax that matches the structure of an object. The following is an example of two equivalent code snippets, first without object destructuring: // Without object destructuring let person = { name: 'HTML', age: 27 }; let personName = person.name, personAge = person.age; console.log(personName); // HTML console.log(personAge); // 27 Second, using object destructuring: // With object destructuring let person = { name: 'HTML', age: 27 }; let { name: personName, age: personAge } = person; console.log(personName); // HTML console.log(personAge); // 27 Destructuring allows you to declare multiple variables and simultaneously perform multiple assignments all inside a single object literal-like syntax. If you want to reuse the property name as a local variable name, you can use a shorthand syntax, as follows: let person = { name: 'HTML', age: 27 }; let { name, age } = person; console.log(name); // HTML console.log(age); // 27 Destructuring assignments do not have to match what is inside the object. You are able to ignore properties when performing an assignment; conversely, if you reference a property that does not exist, undefined will be assigned: let person = { name: 'HTML', age: 27 }; let { name, job } = person; console.log(name); // HTML console.log(job); // undefined We can define default values, which will be applied in the event that a property does not exist in the source object: let person = { name: 'HTML', age: 27 }; let { name, job='Mark up' } = person; console.log(name); // HTML console.log(job); // Mark up Destructuring uses the internal function ToObject(), which is not directly accessible in the runtime, to coerce a source into an object. The primitive values will be treated as objects when used in a destructuring operation. By definition in the ToObject() specification, null and undefined cannot be destructured and will throw an error. let { length } = 'foobar'; console.log(length); // 6 let { constructor: c } = 4; console.log(c === Number); // true let { _ } = null; // TypeError let { _ } = undefined; // TypeError Destructuring does not demand that variable declarations occur inside the destructuring expression. However, doing so requires the assignment expression to be contained inside parentheses: let personName, personAge; let person = { name: 'HTML', age: 27 }; ({name: personName, age: personAge} = person); console.log(personName, personAge); // HTML, 27

Object Nested Destructuring

There are no restrictions on referencing nested properties or assignment targets. This allows you to do things like perform copies of object properties: let person = { name: 'HTML', age: 27, job: { title: 'Mark up' } }; let personCopy = {}; ({ name: personCopy.name, age: personCopy.age, job: personCopy.job } = person); Because an object reference was assigned into personCopy, changing a property inside the person.job object will be propagated to personCopy: person.job.title = 'Hacker' console.log(person); // { name: 'HTML', age: 27, job: { title: 'Hacker' } } console.log(personCopy); // { name: 'HTML', age: 27, job: { title: 'Hacker' } } Destructuring assignments can be nested to match nested property references: let person = { name: 'HTML', age: 27, job: { title: 'Mark up' } }; Declares 'title' variable and assigns person.job.title as its value let { job: { title }} = person; console.log(title); // Mark up You cannot use nested property references when an outer property is undefined. This is true for both source objects and destination objects: let person = { job: { title: 'Mark up' } }; let personCopy = {}; // 'foo' is undefined on the source object ({ foo: { bar: personCopy.bar } } = person); TypeError: Cannot destructure property 'bar' of 'undefined' or 'null'. // 'job' is undefined on the destination object ({ job: { title: personCopy.job.title } } = person); TypeError: Cannot set property 'title' of undefined

Object Partial Destructuring Completion

A destructured assignment involving multiple properties is a sequential operation with independent outcomes. If the initial assignments succeed but a later one throws an error, the destructured assignment will exit having only partially completed: let person = { name: 'HTML', age: 27 }; let personName, personBar, personAge; try { // person.foo is undefined, so this will throw an error ({name: personName, foo: { bar: personBar }, age: personAge} = person); } catch(e) {} console.log(personName, personBar, personAge); // HTML, undefined, undefined

Object Create Prototype Pattern

Each function is created with a prototype property, which is an object containing properties and methods that should be available to instances of a particular reference type. Using the prototype makes all of its properties and methods share the object instances. Instead of assigning object information in the constructor, they can be assigned directly to the prototype, as in this example: function Person() {} Person.prototype.name = "CSS"; Person.prototype.age = 29; Person.prototype.job = "Style"; Person.prototype.sayName = function() { console.log(this.name); }; let person1 = new Person(); person1.sayName(); // "CSS" let person2 = new Person(); person2.sayName(); // "CSS" console.log(person1.sayName == person2.sayName); // true Using a function expression is also suitable: let Person = function() {}; Person.prototype.name = "CSS"; Person.prototype.age = 29; Person.prototype.job = "Style"; Person.prototype.sayName = function() { console.log(this.name); }; let person1 = new Person(); person1.sayName(); // "CSS" let person2 = new Person(); person2.sayName(); // "CSS" console.log(person1.sayName == person2.sayName); // true Here, the properties and the sayName() method are added directly to the prototype property of Person, leaving the constructor empty. However, it's still possible to call the constructor to create a new object and have the properties and methods present. Unlike the constructor pattern, the properties and methods are all shared among instances, so person1 and person2 are both accessing the same set of properties and the same sayName() function.

How Prototypes Work

Whenever a function is created, its prototype property is also created according to a specific set of rules. By default, all prototypes automatically get a property called constructor that points back to the function on which it is a property. For instance, from the following code, Person.prototype.constructor points to Person. let Person = function() {}; console.log(Person.prototype.constructor == Person);//true Then, depending on the constructor, other properties and methods may be added to the prototype. When defining a custom constructor, the prototype gets the constructor property only by default; all other methods are inherited from Object. Each time the constructor is called to create a new instance, that instance has an internal pointer to the constructor's prototype. In ECMA-262, this is called [[Prototype]]. There is no standard way to access [[Prototype]] from script, but Firefox, Safari, and Chrome all support a property on every object called __proto__. A direct link exists between the instance and the constructor's prototype but not between the instance and the constructor. You can refer to the following code as a sort of lookup table for overall prototype behavior: Constructor function can exist as function expression or function declaration, so both of these are suitable: function Person {} let Person = function() {} Upon declaration, the constructor function already has a prototype object associated with it: console.log(typeof Person.prototype); console.log(Person.prototype); Output: The constructor function has * a 'prototype' reference to the prototype object, and the prototype object has a 'constructor' reference to the constructor function. These references are cyclical: console.log(Person.prototype.constructor === Person); // true Any normal prototype chain will terminate at the Object prototype. The prototype of the Object prototype is null. console.log(Person.prototype.__proto__ === Object.prototype); // true console.log(Person.prototype.__proto__.constructor === Object); // true console.log(Person.prototype.__proto__.__proto__ === null); // true console.log(Person.prototype.__proto__); // { // constructor: f Object(), // toString: ... // hasOwnProperty: ... // isPrototypeOf: ... // ... // } The constructor, the prototype object, and an instance are three completely distinct objects: let person1 = new Person(), person2 = new Person(); console.log(person1 !== Person); // true console.log(person1 !== Person.prototype); // true console.log(Person.prototype !== person); // true An instance is linked to the prototype through __proto__, which is the literal manifestation of the [[Prototype]] hidden property. A constructor is linked to the prototype through the constructor property. An instance has no direct link to the constructor, only through the prototype. console.log(person1.__proto__ === Person.prototype); // true conosle.log(person1.__proto__.constructor === Person); // true Two instances created from the same constructor function will share a prototype object: console.log(person1.__proto__ === person2.__proto__); // true instanceof will check the instance's prototype chain against the prototype property of a constructor function: console.log(person1 instanceof Person); // true console.log(person1 instanceof Object); // true console.log(Person.prototype instanceof Object); // true The Person's prototype, and the two instances of Person that exist. Note that Person.prototype points to the prototype object but Person.prototype.constructor points back to Person. The prototype contains the constructor property and the other properties that were added. Each instance of Person, person1, and person2 has internal properties that point back to Person.prototype only; each has no direct relationship with the constructor. Even though neither of these instances have properties or methods, person1.sayName() works because of the lookup procedure for object properties. Even though [[Prototype]] is not accessible in all implementations, the isPrototypeOf() method can be used to determine if this relationship exists between objects. Essentially, isPrototypeOf() returns true if [[Prototype]] points to the prototype on which the method is being called, as shown here: console.log(Person.prototype.isPrototypeOf(person1)); // true console.log(Person.prototype.isPrototypeOf(person2)); // true In this code, the prototype's isPrototypeOf() method is called on both person1 and person2. Because both instances have a link to Person.prototype, it returns true. The ECMAScript Object type has a method called Object.getPrototypeOf(), which returns the value of [[Prototype]]. For example: console.log(Object.getPrototypeOf(person1) == Person.prototype); // true console.log(Object.getPrototypeOf(person1).name); // "CSS" The first line of this code confirms that the object returned from Object.getPrototypeOf() is actually the prototype of the object. The second line retrieves the value of the name property on the prototype, which is "CSS". Using Object.getPrototypeOf(), you can retrieve an object's prototype, which is important to implement inheritance using the prototype. The Object type has a setPrototypeOf() method, which writes a new value into the [[Prototype]] of the instance. This allows you to overwrite the prototype hierarchy of an already-instantiated object: let animal = { numLegs: 2 }; let person = { name: 'HTML' }; Object.setPrototypeOf(person, animal); console.log(person.name); // HTML console.log(person.numLegs); // 2 console.log(Object.getPrototypeOf(person) === animal); // true let animal = { numLegs: 2 }; let person = Object.create(animal); person.name = 'HTML'; console.log(person.name); // HTML console.log(person.numLegs); // 2 console.log(Object.getPrototypeOf(person) === animal); // true

Understanding the Object Prototype Hierarchy

Whenever a property is accessed for reading on an object, a search is started to find a property with that name. The search begins on the object instance itself. If a property with the given name is found on the instance, then that value is returned; if the property is not found, then the search continues up the pointer to the prototype, and the prototype is searched for a property with the same name. If the property is found on the prototype, then that value is returned. Suppose we have function Person() {} Person.prototype.name = "CSS"; Person.prototype.age = 29; Person.prototype.job = "Style"; Person.prototype.sayName = function() { console.log(this.name); }; let person1 = new Person(); person1.sayName(); // "CSS" let person2 = new Person(); person2.sayName(); // "CSS" So, when person1.sayName() is called, a two-step process happens. First, the JavaScript engine asks, "Does the instance person1 have a property called sayName?" The answer is no, so it continues the search and asks, "Does the person1 prototype have a property called sayName?" The answer is yes, so the function stored on the prototype is accessed. When person2.sayName() is called, the same search executes, ending with the same result. This is how prototypes are used to share properties and methods among multiple object instances. The constructor property exists only on the prototype and so is accessible from object instances.

Mask

Although it's possible to read values on the prototype from object instances, it is not possible to overwrite them. If you add a property to an instance that has the same name as a property on the prototype, you create the property on the instance, which then masks the property on the prototype. Here's an example: function Person() {} Person.prototype.name = "CSS"; Person.prototype.age = 29; Person.prototype.job = "Style"; Person.prototype.sayName = function() { console.log(this.name); }; let person1 = new Person(); let person2 = new Person(); person1.name = "HTML"; console.log(person1.name); // "HTML" - from instance console.log(person2.name); // "CSS" - from prototype In this example, the name property of person1 is shadowed by a new value. Both person1.name and person2.name still function appropriately, returning "HTML" (from the object instance) and "CSS" (from the prototype), respectively. When person1.name was accessed in the console.log(), its value was read, so the search began for a property called name on the instance. Because the property exists, it is used without searching the prototype. When person2.name is accessed the same way, the search doesn't find the property on the instance, so it continues to search on the prototype where the name property is found. Once a property is added to the object instance, it shadows any properties of the same name on the prototype, which means that it blocks access to the property on the prototype without altering it. Even setting the property to null only sets the property on the instance and doesn't restore the link to the prototype. The delete operator, however, completely removes the instance property and allows the prototype property to be accessed again as follows: function Person() {} Person.prototype.name = "CSS"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function() { console.log(this.name); }; let person1 = new Person(); let person2 = new Person(); person1.name = "HTML"; console.log(person1.name); // "HTML" - from instance console.log(person2.name); // "CSS" - from prototype delete person1.name; console.log(person1.name); // "CSS" - from the prototype In this modified example, delete is called on person1.name, which previously had been shadowed with the value "HTML". This restores the link to the prototype's name property, so the next time person1.name is accessed, it's the prototype property's value that is returned. The hasOwnProperty() method determines if a property exists on the instance or on the prototype. This method, which is inherited from Object, returns true only if a property of the given name exists on the object instance, as in this example: function Person() {} Person.prototype.name = "CSS"; Person.prototype.age = 29;/*w ww . d e m o 2 s. c o m*/ Person.prototype.job = "Style"; Person.prototype.sayName = function() { console.log(this.name); }; let person1 = new Person(); let person2 = new Person(); console.log(person1.hasOwnProperty("name")); // false person1.name = "HTML"; console.log(person1.name); // "HTML" - from instance console.log(person1.hasOwnProperty("name")); // true console.log(person2.name); // "CSS" - from prototype console.log(person2.hasOwnProperty("name")); // false delete person1.name; console.log(person1.name); // "CSS" - from the prototype console.log(person1.hasOwnProperty("name")); // false By injecting calls to hasOwnProperty() in this example, it becomes clear when the instance's property is being accessed and when the prototype's property is being accessed. Calling person1.hasOwnProperty("name") returns true only after name has been overwritten on person1, indicating that it now has an instance property instead of a prototype property. The ECMAScript Object.getOwnPropertyDescriptor() method works only on instance properties; to retrieve the descriptor of a prototype property, you must call Object.getOwnPropertyDescriptor() on the prototype object directly.

Prototypes and the "in" Operator

There are two ways to use the in operator: on its own or as a for-in loop. When used on its own, the in operator returns true when a property of the given name is accessible by the object. The property may exist on the instance or on the prototype. Consider the following example: function Person() {} Person.prototype.name = "CSS"; Person.prototype.age = 19;// w ww . d e m o 2 s . c o m Person.prototype.job = "Style"; Person.prototype.sayName = function() { console.log(this.name); }; let person1 = new Person(); let person2 = new Person(); console.log(person1.hasOwnProperty("name")); // false console.log("name" in person1); // true person1.name = "HTML"; console.log(person1.name); // "HTML" - from instance console.log(person1.hasOwnProperty("name")); // true console.log("name" in person1); // true console.log(person2.name); // "CSS" - from prototype console.log(person2.hasOwnProperty("name")); // false console.log("name" in person2); // true delete person1.name; console.log(person1.name); // "CSS" - from the prototype console.log(person1.hasOwnProperty("name")); // false console.log("name" in person1); // true The property name is available on each object either directly or from the prototype. Therefore, calling "name" in person1 always returns true, regardless of whether the property exists on the instance.

Check if property exists on the prototype

We can check if the property exists on the prototype by combining a call to hasOwnProperty() with the in operator like this: function hasPrototypeProperty(object, name){ return !object.hasOwnProperty(name) && (name in object); } Because the in operator always returns true as long as the property is accessible by the object, and hasOwnProperty() returns true only if the property exists on the instance, a prototype property can be determined if the in operator returns true but hasOwnProperty() returns false. Consider the following example: function Person() {} Person.prototype.name = "CSS"; Person.prototype.age = 19; Person.prototype.job = "Style"; Person.prototype.sayName = function() { console.log(this.name); }; let person = new Person(); console.log(hasPrototypeProperty(person, "name")); // true person.name = "HTML"; console.log(hasPrototypeProperty(person, "name")); // false In this code, the name property first exists on the prototype, so hasPrototypeProperty() returns true. Once the name property is overwritten, it exists on the instance, so hasPrototypeProperty() returns false. Even though the name property still exists on the prototype, it is no longer used because the instance property now exists.

Get all enumerable instance properties on an object via Object.keys()

When using a for-in loop, all properties that are accessible by the object and can be enumerated will be returned, which includes properties both on the instance and on the prototype. Instance properties that shadow a non-enumerable prototype property, where a property that has [[Enumerable]] set to false, will be returned in the for-in loop because all developer-defined properties are enumerable by default. To retrieve a list of all enumerable instance properties on an object, you can use the Object.keys() method, which accepts an object as its argument and returns an array of strings containing the names of all enumerable properties. For example: function Person() {} Person.prototype.name = "CSS"; Person.prototype.age = 19; Person.prototype.job = "Style"; Person.prototype.sayName = function() { console.log(this.name); }; let keys = Object.keys(Person.prototype); console.log(keys); // "name,age,job,sayName" let p1 = new Person(); p1.name = "Rob"; p1.age = 31; let p1keys = Object.keys(p1); console.log(p1keys); // "name,age" Here, the keys variable is filled with an array containing "name", "age", "job", and sayName. This is the order in which they would normally appear using for-in. When called on an instance of Person, Object.keys() returns an array of name and age, the only two instance properties. If you'd like a list of all instance properties, whether enumerable or not, you can use Object.getOwnPropertyNames() in the same way: let keys = Object.getOwnPropertyNames(Person.prototype); console.log(keys); // "constructor,name,age,job,sayName" It contains the non-enumerable constructor property in the list of results. Both Object.keys() and Object.getOwnPropertyNames() may be suitable replacements for using for-in. With the introduction of symbols in ECMAScript 6, Object.getOwnPropertySymbols() was introduced, which offers the same behavior as Object.getOwnPropertyNames() but with respect to symbols: let k1 = Symbol('k1'), k2 = Symbol('k2'); let o = { [k1]: 'k1', [k2]: 'k2' }; console.log(Object.getOwnPropertySymbols(o)); // [Symbol(k1), Symbol(k2)]

Object Property Enumeration Order

Javascript for-in loops, Object.keys(), Object.getOwnPropertyNames/Symbols(), and Object.assign() have different property enumeration order. for-in loops and Object.keys() do not have a deterministic order of enumeration-these are determined by the JavaScript engine and may vary by browser. Object.getOwnPropertyNames(), Object.getOwnPropertySymbols(), and Object.assign() have a deterministic enumeration order. Number keys will first be enumerated in ascending order, then string and symbol keys enumerated in insertion order. Keys defined inline in an object literal will be inserted in their comma delimited order. let k1 = Symbol('k1'), k2 = Symbol('k2'); let o = {/* w w w. d e m o 2 s . c o m*/ 1: 1, first: 'first', [k1]: 'sym2', second: 'second', 0: 0 }; o[k2] = 'sym2'; o[3] = 3; o.third = 'third'; 0[2] = 2; console.log(Object.getOwnPropertyNames(o)); // ["0", "1", "3", "first", "second", "third"] console.log(Object.getOwnPropertySymbols(o)); // [Symbol(k1), Symbol(k2)]

Object Iteration

The ECMAScript 2017 introduced two static methods, Object.values() and Object.entries(), for converting an object's contents into a serialized and more iterable-format. Object.values() and Object.entries() accept an object and return its contents in an array. Object.values() returns an array of the object's values, and Object.entries() returns an array of array pairs, each representing a [key, value] pair in the object. For example const o = { foo: 'bar', baz: 1, qux: {} }; console.log(Object.values(o)); // ["bar", 1, {}] console.log(Object.entries((o))); // [["foo", "bar"], ["baz", 1], ["qux", {}]] The non-string properties are converted to strings in the output array. Furthermore, the method performs a shallow copy of the object: const o = { qux: {} }; console.log(Object.values(o)[0] === o.qux); // true console.log(Object.entries(o)[0][1] === o.qux); // true Symbol-keyed properties are ignored: const sym = Symbol(); const o = { [sym]: 'foo' }; console.log(Object.values(o)); // [] console.log(Object.entries((o))); // []

Object Definition Alternate Prototype Syntax

We can overwrite the prototype with an object literal that contains all of the properties and methods, as in this example: function Person() {} Person.prototype = { name: "CSS", age: 19, job: "Style", sayName() { console.log(this.name); } }; In this example, the Person.prototype property is set equal to a new object created with an object literal. In this way the constructor property no longer points to Person. When a function is created, its prototype object is created and the constructor is automatically assigned. This syntax overwrites the default prototype object completely/ The constructor property is equal to that of a new Object constructor instead of the function itself. Although the instanceof operator still works reliably, you cannot rely on the constructor to indicate the type of object, as this example shows: let friend = new Person(); console.log(friend instanceof Object); // true console.log(friend instanceof Person); // true console.log(friend.constructor == Person); // false console.log(friend.constructor == Object); // true Here, instanceof still returns true for both Object and Person, but the constructor property is now equal to Object instead of Person. If the constructor's value is important, you can set it specifically back to the appropriate value, as shown here: function Person() { } Person.prototype = { constructor: Person, name: "CSS", age: 19, job: "Style", sayName() { console.log(this.name); } }; This code includes a constructor property and sets it equal to Person, ensuring that the property contains the appropriate value. Keep in mind that restoring the constructor in this manner creates a property with [[Enumerable]] set to true. Native constructor properties are not enumerable by default, so if you're using an ECMAScript-compliant JavaScript engine, you may wish to use Object.defineProperty() instead: function Person() {} Person.prototype = { name: "CSS", age: 19, job: "Style", sayName() { console.log(this.name); } }; // restore the constructor Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person });

Dynamic Nature of Prototypes

The changes made to the prototype are reflected on instances, even the instances that existed before the change was made. Here's an example: function Person() {} let friend = new Person(); Person.prototype.sayHi = function() { console.log("hi"); }; friend.sayHi(); // "hi" - works! In this code, an instance of Person is created and stored in friend. The next statement adds a method called sayHi() to Person.prototype. Even though the friend instance was created prior to this change, it still has access to the new method. When friend.sayHi() is called, the instance is first searched for a property named sayHi; when it's not found, the search continues to the prototype.

No Overwrite

Although properties and methods may be added to the prototype at any time, and they are reflected instantly by all object instances, you cannot overwrite the entire prototype. The [[Prototype]] pointer is assigned when the constructor is called, so changing the prototype to a different object breaks the link between the constructor and the original prototype. The instance has a pointer to only the prototype, not to the constructor. Consider the following: function Person() {} let friend = new Person(); Person.prototype = { constructor: Person, name: "CSS", age: 19, job: "Style", sayName() { console.log(this.name); } }; friend.sayName(); // error In this example, a new instance of Person is created before the prototype object is overwritten. When friend.sayName() is called, it causes an error, because the prototype that friend points to doesn't contain a property of that name. Overwriting the prototype on the constructor means that new instances will reference the new prototype while any previously existing object instances still reference the old prototype.

Native Object Prototypes

The prototype pattern is the pattern used to implement all of the native reference types in Javascript. Object, Array, String, and so on all have its methods defined on the constructor's prototype. For instance, the sort() method can be found on Array.prototype, and substring() can be found on String.prototype, as shown here: console.log(typeof Array.prototype.sort); // "function" console.log(typeof String.prototype.substring); // "function" Through native object prototypes, we can get references to all of the default methods and to define new methods. Native object prototypes can be modified just like custom object prototypes, so methods can be added at any time. For example, the following code adds a method called startsWith() to the String primitive wrapper: String.prototype.startsWith = function (text) { return this.indexOf(text) === 0; }; let msg = "Hello world!"; console.log(msg.startsWith("Hello")); // true The startsWith() method in this example returns true if some given text occurs at the beginning of a string. The method is assigned to String.prototype, making it available to all strings in the environment. Since msg is a string, the String primitive wrapper is created behind the scenes, making startsWith() accessible. We should not modify native object prototypes in a production environment. We should create a custom class that inherits from the native type.

Problems with Prototypes

The prototype pattern negates the ability to pass initialization arguments into the constructor. All instances get the same property values by default. All properties on the prototype are shared among instances, which is ideal for functions. Properties that contain primitive values also work well. We can hide the prototype property by assigning a property of the same name to the instance. The real problem occurs when a property contains a reference value. Consider the following example: function Person() {} Person.prototype = {// w w w . d em o 2 s . c o m constructor: Person, name: "CSS", age: 19, job: "Style", friends: ["HTML", "Javascript"], sayName() { console.log(this.name); } }; let person1 = new Person(); let person2 = new Person(); person1.friends.push("Browser"); console.log(person1.friends);//[ 'HTML', 'Javascript', 'Browser' ] console.log(person2.friends);//[ 'HTML', 'Javascript', 'Browser' ] console.log(person1.friends === person2.friends); // true Here, the Person.prototype object has a property called friends that contains an array of strings. Two instances of Person are then created. The person1.friends array is altered by adding another string. Because the friends array exists on Person.prototype, not on person1, the changes made are also reflected on person2.friends which points to the same array. If the intention is to have an array shared by all instances, then this outcome is okay. Typically, instances want to have their own copies of all properties. This is why the prototype pattern is rarely used on its own.

Inheritance via Prototype Chaining

ECMA-262 describes prototype chaining as the primary method of inheritance in ECMAScript. It uses prototypes to inherit properties and methods between two reference types. Each constructor has a prototype object that points back to the constructor, and instances have an internal pointer to the prototype. If the prototype is an instance of another type, the prototype itself will have a pointer to a different prototype that, in turn, would have a pointer to another constructor. If that prototype were also an instance of another type, then the pattern would continue, forming a chain between instances and prototypes. This is the basic idea behind prototype chaining. Implementing prototype chaining involves the following code pattern: function SuperType() { this.property = true;/*w w w . d e m o 2 s . co m */ } SuperType.prototype.getSuperValue = function() { return this.property; }; function SubType() { this.subproperty = false; } // inherit from SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subproperty; }; let instance = new SubType(); console.log(instance.getSuperValue()); // true console.log(instance instanceof Object); // true console.log(instance instanceof SuperType); // true console.log(instance instanceof SubType); // true console.log(Object.prototype.isPrototypeOf(instance)); // true console.log(SuperType.prototype.isPrototypeOf(instance)); // true console.log(SubType.prototype.isPrototypeOf(instance)); // true This code defines two types: SuperType and SubType. Each type has a single property and a single method. SubType inherits from SuperType by creating a new instance of SuperType and assigning it to SubType.prototype. This overwrites the original prototype and replaces it with a new object, which means that all properties and methods that typically exist on an instance of SuperType now also exist on SubType.prototype. After the inheritance takes place, a method is assigned to SubType.prototype, adding a new method on top of what was inherited from SuperType. Instead of using the default prototype of SubType, a new prototype is assigned. The new prototype is an instance of SuperType, so it not only gets the properties and methods of a SuperType instance but also points back to the SuperType's prototype. So instance points to SubType.prototype and SubType.prototype points to SuperType.prototype. The getSuperValue() method remains on the SuperType.prototype object, but property ends up on SubType.prototype. That's because getSuperValue() is a prototype method, and property is an instance property. SubType.prototype is now an instance of SuperType so property is stored there. The instance.constructor points to SuperType because the constructor property on the SubType.prototype was overwritten. When a property is accessed in read mode on an instance, the property is first searched for on the instance. If the property is not found, then the search continues to the prototype. When inheritance has been implemented via prototype chaining, that search can continue up the prototype chain. A call to instance.getSuperValue() results in a three-step search: the instance, SubType.prototype, and SuperType.prototype The method is found in SuperType.prototype. The search for properties and methods always continues until the end of the prototype chain is reached.

Object prototype

All reference types inherit from Object by default, which is accomplished through prototype chaining. The default prototype for any function is an instance of Object, meaning that its internal prototype pointer points to Object.prototype. This is how custom types inherit all of the default methods such as toString() and valueOf(). So the previous example has an extra layer of inheritance. SubType inherits from SuperType, and SuperType inherits from Object. When instance.toString() is called, the method being called actually exists on Object.prototype.

Methods in Inheritance

During inheritance a subtype will need to either override a supertype method or introduce new methods that don't exist on the supertype. To accomplish this, the methods must be added to the prototype after the prototype has been assigned. Consider this example: function SuperType() { this.property = true;// w ww . d e m o 2 s . c o m } SuperType.prototype.getSuperValue = function() { return this.property; }; function SubType() { this.subproperty = false; } // inherit from SuperType SubType.prototype = new SuperType(); // new method SubType.prototype.getSubValue = function() { return this.subproperty; }; // override existing method SubType.prototype.getSuperValue = function() { return false; }; let instance = new SubType(); console.log(instance.getSuperValue()); // false The first is getSubValue(), which is a new method on the SubType. The second is getSuperValue(), which already exists in the prototype chain but is being shadowed here. When getSuperValue() is called on an instance of SubType, it will call this one, but instances of SuperType will still call the original. Both of the methods are defined after the prototype has been assigned as an instance of SuperType. The object literal approach to creating prototype methods cannot be used with prototype chaining because you end up overwriting the chain. Here's an example: function SuperType() { this.property = true;// ww w . d em o 2 s . c o m } SuperType.prototype.getSuperValue = function() { return this.property; }; function SubType() { this.subproperty = false; } // inherit from SuperType SubType.prototype = new SuperType(); // try to add new methods - this nullifies the previous line SubType.prototype = { getSubValue() { return this.subproperty; }, someOtherMethod() { return false; } }; let instance = new SubType(); console.log(instance.getSuperValue()); // error! In this code, the prototype is reassigned to be an object literal after it was already assigned to be an instance of SuperType. The prototype now contains a new instance of Object instead of an instance of SuperType, so the prototype chain has been broken.

Problems with Prototype Chaining

The prototype chaining for inheritance has major issue around prototypes that contain reference values. The prototype properties containing reference values are shared with all instances. So properties are typically defined within the constructor instead of on the prototype. When implementing inheritance using prototypes, the prototype actually becomes an instance of another type. What once were instance properties are now prototype properties. Here is an example: function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() {} // inherit from SuperType SubType.prototype = new SuperType(); let instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" let instance2 = new SubType(); console.log(instance2.colors); // "red,blue,green,black" In this example, the SuperType constructor defines a property, colors, that contains an array, which is a reference value. Each instance of SuperType has its own colors property containing its own array. When SubType inherits from SuperType via prototype chaining, SubType.prototype becomes an instance of SuperType and so it gets its own colors property. After that all instances of SubType share a colors property. This is indicated as the changes made to instance1.colors are reflected on instance2.colors. A second issue with prototype chaining is that you cannot pass arguments into the supertype constructor when the subtype instance is being created. The prototype chaining is rarely used alone.

Constructor Stealing

To solve the inheritance problem with reference values on prototypes, We use constructor stealing:call the supertype constructor from within the subtype constructor. The apply() and call() methods can be used to execute a constructor on the newly created object, as in this example: function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { // inherit from SuperType SuperType.call(this); } let instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" let instance2 = new SubType(); console.log(instance2.colors); // "red,blue,green" By using the call() method or alternately, apply(), the SuperType constructor is called in the context of the newly created instance of SubType. Doing this runs all of the object-initialization code in the SuperType() function on the new SubType object. The result is that each instance has its own copy of the colors property.

Passing Arguments via constructor stealing

The constructor stealing can pass arguments into the supertype constructor from within the subtype constructor. Consider the following: function SuperType(name){ this.name = name; } function SubType() { // inherit from SuperType passing in an argument SuperType.call(this, "CSS"); // instance property this.age = 19; } let instance = new SubType(); console.log(instance.name); // "CSS"; console.log(instance.age); // 19 In this code, the SuperType constructor accepts a single argument, name, which is simply assigned to a property. A value can be passed into the SuperType constructor when called from within the SubType constructor, effectively setting the name property for the SubType instance.

Combination Inheritance

Combination inheritance combines prototype chaining and constructor stealing to get the best of each approach. We use prototype chaining to inherit properties and methods on the prototype and to use constructor stealing to inherit instance properties. This allows function reuse by defining methods on the prototype and allows each instance to have its own properties. Consider the following: function SuperType(name){ this.name = name;/* w w w . de m o 2 s. c o m */ this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { console.log(this.name); }; function SubType(name, age){ // inherit properties SuperType.call(this, name); this.age = age; } // inherit methods SubType.prototype = new SuperType(); SubType.prototype.sayAge = function() { console.log(this.age); }; let instance1 = new SubType("CSS", 19); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" instance1.sayName(); // "CSS"; instance1.sayAge(); // 19 let instance2 = new SubType("HTML", 27); console.log(instance2.colors); // "red,blue,green" instance2.sayName(); // "HTML"; instance2.sayAge(); // 27 In this example, the SuperType constructor defines two properties, name and colors, and the Super- Type prototype has a single method called sayName(). The SubType constructor calls the SuperType constructor, passing in the name argument, and defines its own property, called age. Additionally, the SubType prototype is assigned to be an instance of SuperType, and then a new method, called sayAge(), is defined. With this code, we create two separate instances of SubType that have their own properties, including the colors property, but all use the same methods. Addressing the down sides of both prototype chaining and constructor stealing, combination inheritance is the most frequently used inheritance pattern in JavaScript. It also preserves the behavior of instanceof and isPrototypeOf() for identifying the composition of objects.

Parasitic Inheritance

The concept of parasitic inheritance is created by Crockford. It creates a function that does the inheritance, augments the object in some way, and then returns the object as if it did all the work. The basic parasitic inheritance pattern looks like this: function createAnother(original){ let clone = object(original); // create a new object by calling a function clone.sayHi = function() { // augment the object in some way console.log("hi"); }; return clone; // return the object } In this code, the createAnother() function accepts a single argument, which is the object to base a new object on. This object, original, is passed into the object() function, and the result is assigned to clone. Next, the clone object is changed to have a new method called sayHi(). The last step is to return the object. The createAnother() function can be used in the following way: let person = { name: "CSS", friends: ["HTML", "Javascript", "Browser"] }; let anotherPerson = createAnother(person); anotherPerson.sayHi(); // "hi" The code in this example returns a new object based on person. The anotherPerson object has all of the properties and methods of person but adds a new method called sayHi(). The object() method is not required for parasitic inheritance; any function that returns a new object fits the pattern.

Parasitic Combination Inheritance

In combination inheritance, the supertype constructor is always called twice: once to create the subtype's prototype, and once inside the subtype constructor. Consider the combination inheritance example: function SuperType(name) { this.name = name;/* w ww . d e m o 2 s . co m*/ this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { console.log(this.name); }; function SubType(name, age){ SuperType.call(this, name); // second call to SuperType() this.age = age; } SubType.prototype = new SuperType(); // first call to SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); }; When this code is executed, SubType.prototype ends up with two properties: name and colors. These are instance properties for SuperType, but they are now on the SubType's prototype. When the SubType constructor is called, the SuperType constructor is also called, which creates instance properties name and colors on the new object that mask the properties on the prototype. Parasitic combination inheritance uses constructor stealing to inherit properties but uses a hybrid form of prototype chaining to inherit methods. Instead of calling the supertype constructor to assign the subtype's prototype, all you need is a copy of the supertype's prototype. Use parasitic inheritance to inherit from the supertype's prototype and then assign the result to the subtype's prototype. The basic pattern for parasitic combination inheritance is as follows: function inheritPrototype(subType, superType) { let prototype = object(superType.prototype); // create object prototype.constructor = subType; // augment object subType.prototype = prototype; // assign object } The inheritPrototype() function implements very basic parasitic combination inheritance. This function accepts two arguments: the subtype constructor and the supertype constructor. A call to inheritPrototype() can replace the subtype prototype assignment: function inheritPrototype(subType, superType) { let prototype = new Object(superType.prototype); // create object prototype.constructor = subType; // augment object subType.prototype = prototype; // assign object } function SuperType(name) { this.name = name;/* ww w . d e m o 2 s . c o m*/ this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { console.log(this.name); }; function SubType(name, age) { SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function() { console.log(this.age); }; let instance1 = new SubType("CSS", 19); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" instance1.sayName(); // "CSS"; instance1.sayAge(); // 19 let instance2 = new SubType("HTML", 27); console.log(instance2.colors); // "red,blue,green" instance2.sayName(); // "HTML"; instance2.sayAge(); // 27 Output: In this example, the SuperType constructor is being called only one time, avoiding having unnecessary and unused properties on SubType.prototype. The prototype chain is kept intact, so both instanceof and isPrototypeOf() behave as they would normally. Parasitic combination inheritance is considered the most optimal inheritance paradigm for reference types.

Array Type Introduction

An ECMAScript array is very different from arrays in most other programming languages. ECMAScript arrays are ordered lists of data, and they can hold any type of data in each slot. We can create an array that has a string in the first position, a number in the second, an object in the third, and so on. ECMAScript arrays are also dynamically sized, automatically growing to accommodate any data that is added to them.

Creating Arrays

There are several ways to create arrays. This section introduced the following four ways: Array literals The ... spread operator on an iterable object The Array() constructor The Array.of() and Array.from() factory methods

Array Literals

We can create an array with an array literal, which is simply a comma-separated list of array elements within square brackets. For example: let empty = []; // An array with no elements let primes = [2, 3, 5, 7, 11]; // An array with 5 numeric elements let misc = [ 1.1, true, "a", ]; // 3 elements of various types + trailing comma The values in an array literal need not be constants; they may be arbitrary expressions: let base = 1; let table = [base, base+1, base+2, base+3]; Array literals can contain object literals or other array literals: let b = [[1, {x: 1, y: 2}], [2, {x: 3, y: 4}]]; If an array literal contains multiple commas in a row, with no value between, the array is sparse. Array elements for which values are omitted do not exist but appear to be undefined if you query them: let count = [1,,3]; // Elements at indexes 0 and 2. No element at index 1 let undefs = [,,]; // An array with no elements but a length of 2 Array literal syntax allows an optional trailing comma, so [,,] has a length of 2, not 3.

Spread Operator

In ES6 and later, you can use the "spread operator" to include the elements of one array within an array literal: let a = [1, 2, 3]; let b = [0, ...a, 4]; // b == [0, 1, 2, 3, 4] The three dots "spread" the array a so that its elements become elements within the array literal that is being created. The spread operator is a convenient way to create a shallow copy of an array: let original = [1,2,3]; let copy = [...original]; copy[0] = 0; // Modifying the copy does not change the original original[0] // => 1 The spread operator works on any iterable object. Strings are iterable, so you can use a spread operator to turn any string into an array of single-character strings: let digits = [..."0123456789ABCDEF"]; digits // => ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"] Set objects are iterable, we can remove duplicate elements from an array by converting the array to a set and then converting the set back to an array using the spread operator: let letters = [..."hello world"]; [...new Set(letters)] // => ["h","e","l","o"," ","w","r","d"]

Array() Constructor

Another way to create an array is with the Array() constructor. You can invoke this constructor in three distinct ways: 1.Call it with no arguments: This method creates an empty array with no elements and is equivalent to the array literal []. 2. Call it with a single numeric argument, which specifies a length: let a = new Array(10); This technique creates an array with the specified length. This form of the Array() constructor can be used to preallocate an array when you know in advance how many elements will be required. Note that no values are stored in the array. 3. Explicitly specify two or more array elements or a single non-numeric element for the array: let a = new Array(5, 4, 3, 2, 1, "testing, testing"); In this form, the constructor arguments become the elements of the new array.

Array.of()

When the Array() constructor function is invoked with one numeric argument, it uses that argument as an array length. But when invoked with more than one numeric argument, it treats those arguments as elements for the array to be created. This means that the Array() constructor cannot be used to create an array with a single numeric element. Array.of() function is a factory method that creates and returns a new array, using its argument values, regardless of how many of them there are, as the array elements: Array .of() // => []; returns empty array with no arguments Array .of(10) // => [10]; can create arrays with a single numeric argument Array .of(1,2,3) // => [1, 2, 3]

Array.from()

Array.from is another array factory method introduced in ES6. It expects an iterable or array-like object as its first argument and returns a new array that contains the elements of that object. With an iterable argument, Array.from(iterable) works like the spread operator [.iterable] does. It is also a simple way to make a copy of an array: let copy = Array .from(original); Array.from() defines a way to make a true-array copy of an array-like object. Array-like objects are non-array objects that have a numeric length property and have values stored with properties whose names happen to be integers. When working with client-side JavaScript, the return values of some web browser methods are array-like, and it can be easier to work with them if you first convert them to true arrays: let truearray = Array .from(arraylike); Array.from() also accepts an optional second argument. If you pass a function as the second argument, then as the new array is being built, each element from the source object will be passed to the function you specify, and the return value of the function will be stored in the array instead of the original value. This is very much like the array map() method, but it is more efficient to perform the mapping while the array is being built than it is to build the array and then map it to another new array.

Array Type and how to create Array Object

Arrays can be created in several basic ways. One is to use the Array constructor, as in this line: let colors = new Array(); If you know the number of items that will be in the array, you can pass the count into the constructor, and the length property will automatically be created with that value. For example, the following creates an array with an initial length value of 20: let colors = new Array(20); The Array constructor can also be passed items that should be included in the array. The following creates an array with three string values: let colors = new Array("red", "blue", "green"); An array can be created with a single value by passing it into the constructor. Important: Providing a single argument that is a number always creates an array with the given number of items, whereas an argument of any other type creates a one-item array that contains the specified value. Here's an example: let colors = new Array(3); // create an array with three items let names = new Array("HTML"); // create an array with // one item, the string "HTML" It's possible to omit the new operator when using the Array constructor. It has the same result, as you can see here: let colors = Array(3); // create an array with three items let names = Array("HTML"); // create an array with one item, // the string "HTML" A second way to create an array is by using array literal notation. An array literal is specified by using square brackets and placing a comma-separated list of items between them, as in this example: let colors = ["red", "blue", "green"]; // Creates an array with three strings let names = []; // Creates an empty array let values = [1,2,]; // Creates an array with 2 items In this code, the first line creates an array with three string values. The second line creates an empty array by using empty square brackets. The third line shows the effects of leaving a comma after the last value in an array literal: values is a two-item array containing the values 1 and 2. The Array constructor isn't called when an array is created using array literal notation.

create Array Object using from() and of() method

The Array constructor also has two additional static methods introduced in ES6 to create arrays: from() and of(). from() is used for converting array-like constructs into an array instance, whereas of() is used to convert a collection of arguments into an array instance. The first argument to Array.from() is an "array Like" object, which is anything that is iterable or has a property length and indexed elements. This type can be used in an abundance of different ways: Strings will be broken up into an array of single characters console.log(Array.from("Java")); // ["J", "a", "v", "a"] Sets and Maps can be converted into an new array instance using from() const m = new Map().set(1, 2) .set(3, 4); const s = new Set().add(1) .add(2) .add(3) .add(4); console.log(Array.from(m)); // [[1, 2], [3, 4]] console.log(Array.from(s)); // [1, 2, 3, 4] Array.from() performs a shallow copy of an existing array const a1 = [1, 2, 3, 4]; const a2 = Array.from(a1); console.log(a1); // [1, 2, 3, 4] console.log(a1 === a2); // false Any iterable object can be used const iter = { *[Symbol.iterator]() { yield 1; yield 2; yield 3; yield 4; } }; console.log(Array.from(iter)); // [1, 2, 3, 4] The arguments object can now easily be casted into an array: function getArgsArray() { return Array.from(arguments); } console.log(getArgsArray(1, 2, 3, 4)); // [1, 2, 3, 4] from() can use a custom object with required properties const arrayLikeObject = { 0: 1, 1: 2, 2: 3, 3: 4, length: 4 }; console.log(Array.from(arrayLikeObject)); // [1, 2, 3, 4] Array.from() also accepts a second optional map function argument. This allows you to augment the new array's values without creating an intermediate array first, which is the case if the same were performed with Array.from().map(). A third optional argument specifies the value of this inside the map function. The overridden this value is not applied inside an arrow function: const a1 = [1, 2, 3, 4]; const a2 = Array.from(a1, x => x**2); const a3 = Array.from(a1, function(x) {return x**this.exponent}, {exponent: 2}); console.log(a2); // [1, 4, 9, 16] console.log(a3); // [1, 4, 9, 16] Array.of() will convert the list of arguments into an array. This is to replace the common pre-ES6 method of converting the arguments object into an array using the Array.prototype.slice.call(arguments): console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4] console.log(Array.of(undefined)); // [undefined]

Array Holes

Initializing an array with an array literal allows you to create "holes" using sequential commas. ECMAScript will treat the value at the index between the commas as a hole. An array of holes might be created as follows: const options = [,,,,,]; // Creates an array with 5 items console.log(options.length); // 5 console.log(options); // [,,,,,] Methods and iterators introduced in ES6 behave differently than methods present in earlier ECMAScript editions. ES6 additions will universally treat the holes as an existing entry with a value of undefined: const options = [1,,,,5]; for (const option of options) { console.log(option === undefined); } Output: Holes created from from() method const a = Array.from([,,,]); // Array of 3 holes // created with ES6's Array.from() for (const val of a) { console.log(val === undefined); } Output: Holes from of() method console.log(Array.of(...[,,,])); // [undefined, undefined, undefined] const options = [1,,,,5]; for (const [index, value] of options.entries()) { console.log(value); } Output: Conversely, methods available before ES6 will tend to ignore the holes: const options = [1,,,,5]; // map() will skip the holes entirely console.log(options.map(() => 6)); // [6, undefined, undefined, undefined, 6] // join() treats holes as empty strings console.log(options.join('-')); // "1----5" Output: We should avoid using array holes in your code and use an explicit undefined in place of a hole.

Use index on Array elements

To get and set array values, you use square brackets and provide the zero-based numeric index of the value, as shown here: let colors = ["red", "blue", "green"]; // define an array of strings console.log(colors[0]); // display the first item colors[2] = "black"; // change the third item colors[3] = "brown"; // add a fourth item The index within the square brackets indicates the value being accessed. If the index is less than the number of items in the array, then it will return the value stored in the corresponding item, as colors[0] displays "red" in this example. Setting a value replacing the value in the designated position. If a value is set to an index that is past the end of the array, as with colors[3] in this example, the array length is automatically expanded to be that index plus 1.

Get and use Array Length

The number of items in an array is stored in the length property, which always returns 0 or more, as shown in the following example: let colors = ["red", "blue", "green"]; // creates an array with three strings let names = []; // creates an empty array console.log(colors.length); // 3 console.log(names.length); // 0 Javascript Array length is not read-only. By setting the length property, you can remove items from or add items to the end of the array. Consider this example: let colors = ["red", "blue", "green"]; // creates an array with three strings colors.length = 2; console.log(colors[2]); // undefined Here, the array colors starts out with three values. Setting the length to 2 removes the last item in position 2, making it no longer accessible using colors[2]. If the length were set to a number greater than the number of items in the array, the new items would each get filled with the value of undefined, as in this example: let colors = ["red", "blue", "green"]; // creates an array with three strings colors.length = 4; console.log(colors[3]); // undefined This code sets the length of the colors array to 4 even though it contains only three items. Position 3 does not exist in the array, so trying to access its value results in the special value undefined being returned.

Adding

The length property can also be helpful in adding items to the end of an array, as in this example: let colors = ["red", "blue", "green"]; // creates an array with three strings colors[colors.length] = "black"; // add a color (position 3) colors[colors.length] = "brown"; // add another color (position 4) The last item in an array is always at position length - 1, so the next available open slot is at position length. Each time an item is added after the last one in the array, the length property is automatically updated to reflect the change. That means colors[colors.length] assigns a value to position 3 in the second line of this example and to position 4 in the last line. The new length is automatically calculated when an item is placed into a position that's outside of the current array size, which is done by adding 1 to the position, as in this example: let colors = ["red", "blue", "green"]; // creates an array with three strings colors[99] = "black"; // add a color (position 99) console.log(colors.length); // 100 In this code, the colors array has a value inserted into position 99, resulting in a new length of 100. Each of the other items, positions 3 through 98, doesn't actually exist and so returns undefined when accessed. Arrays can contain a maximum of 4,294,967,295 items. If you try to add more than that number, an exception occurs.

Detecting Arrays

To determine whether a given object is an array, use ECMAScript Array.isArray() method. This method determines if a given value is an array regardless of the global execution context in which it was created. Consider the following example: const value = "a"; if (Array.isArray(value)){ console.log("value is an array"); }else{ console.log("value is NOT an array"); } const a = []; if (Array.isArray(a)){ console.log("a is an array"); }else{ console.log("a is NOT an array"); } Output:

Reading and Writing Array Elements

You access an element of an array using the [] operator. A reference to the array should appear to the left of the brackets. An arbitrary expression that has a non-negative integer value should be inside the brackets. You can use this syntax to both read and write the value of an element of an array. Thus, the following are all legal JavaScript statements: let a = ["world"]; // Start with a one-element array let value = a[0]; // Read element 0 a[1] = 3.14; // Write element 1 let i = 2; a[i] = 3; // Write element 2 a[i + 1] = "hello"; // Write element 3 a[a[i]] = a[0]; // Read elements 0 and 2, write element 3 Array maintains the value of the length property for you. In the preceding, for example, we created an array a with a single element. We then assigned values at indexes 1, 2, and 3. The length property of the array changed as we did, so:

Adding and Deleting Array Elements

We can add elements to an array: just assign values to new indexes: let a = []; // Start with an empty array. a[0] = "zero"; // And add elements to it. a[1] = "one"; You can also use the push() method to add one or more values to the end of an array: let a = []; // Start with an empty array a.push("zero"); // Add a value at the end. a = ["zero"] a.push("one", "two"); // Add two more values. a = ["zero", "one", "two"] Pushing a value onto an array a is the same as assigning the value to a[a.length]. You can use the unshift() method to insert a value at the beginning of an array, shifting the existing array elements to higher indexes. it removes the last element of the array and returns it, reducing the length of an array by 1. Similarly, the shift() method removes and returns the first element of the array, reducing the length by 1 and shifting all ele ments down to an index one lower than their current index. You can delete array elements with the delete operator, just as you can delete object properties: let a = [1,2,3]; delete a[2]; // a now has no element at index 2 2 in a // => false: no array index 2 is defined a.length // => 3: delete does not affect array length Note that using delete on an array element does not alter the length property and does not shift elements with higher indexes down to fill in the gap. If you delete an element from an array, the array becomes sparse. We can also remove elements from the end of an array simply by setting the length property to the new desired length. Finally, splice() is the general-purpose method for inserting, deleting, or replacing array elements. It alters the length property and shifts array elements to higher or lower indexes as needed.

Iterating Arrays

The easiest way to loop through each of the elements of an array or any iterable object is with the for/of loop: let letters = [..."Hello world"]; // An array of letters let string = ""; for(let letter of letters) { string += letter; } string // => "Hello world"; we reassembled the original text The built-in array iterator that the for/of loop uses returns the elements of an array in ascending order. It has no special behavior for sparse arrays and simply returns undefined for any array elements that do not exist. If you want to use a for/of loop for an array and need to know the index of each array element, use the entries() method of the array, along with destructuring assignment, like this: let letters = [..."Hello world"]; // An array of letters let everyother = ""; for(let [index, letter] of letters.entries()) { if (index % 2 === 0) everyother += letter; // letters at even indexes } everyother // => "Hlowrd" Another good way to iterate arrays is with forEach() method from Array. It offers a functional approach to array iteration. You pass a function to the forEach() method of an array, and forEach() invokes your function once on each element of the array: let letters = [..."Hello world"]; // An array of letters let uppercase = ""; letters.forEach(letter => { // Note arrow function syntax here uppercase += letter.toUpperCase(); }); uppercase // => "HELLO WORLD" forEach() iterates the array in order, and it actually passes the array index to your function as a second argument, which is occasionally useful. Unlike the for/of loop, the forEach() is aware of sparse arrays and does not invoke your function for elements that are not there. You can also loop through the elements of an array with a good old-fashioned for loop: let letters = [..."Hello world"]; // An array of letters let vowels = ""; for(let i = 0; i < letters.length; i++) { // For each index in the array let letter = letters[i]; // Get the element at that index if (/[aeiou]/.test(letter)) { // Use a regular expression test vowels += letter; // If it is a vowel, remember it } } vowels // => "eoo"

Array Iterator Methods

In ES6, three new methods are added on the Array prototype that allow you to inspect the contents of an array: keys(), values(), and entries(). keys() will return an iterator of the array's indices, values() will return an iterator of the array's elements, and entries() will return an iterator of index/value pairs: const a = ["foo", "bar", "baz", "qux"]; // Because these methods return iterators, // you can funnel their contents // into array instances using Array.from() const aKeys = Array.from(a.keys()); const aValues = Array.from(a.values()); const aEntries = Array.from(a.entries()); console.log(aKeys); // [0, 1, 2, 3] console.log(aValues); // ["foo", "bar", "baz", "qux"] console.log(aEntries); // [[0, "foo"], [1, "bar"], [2, "baz"], [3, "qux"]] ES6 destructuring means it is now very easy to split out the key/value pairs inside a loop: const a = ["foo", "bar", "baz", "qux"]; for (const [idx, element] of a.entries()) [ console.log(idx); console.log(element); } Output:

Array Fill Methods

ES6 added two methods, fill() and copyWithin(), to respectively allow for batch fill and copy inside an array. Both methods have a similar function signature. We can specify a range within an existing array instance using an inclusive start and exclusive end index. Arrays that use this method will never be resized. The fill() method allows you to insert the same value into all or part of an existing array. Specifying the optional start index instructs the fill to begin at that index, and the fill will continue to the end of the array unless an end index is provided. Negative indices are interpreted from the end of the array: const zeroes = [0, 0, 0, 0, 0];/* ww w . d e mo 2 s . c om */ // Fill the entire array with 5 zeroes.fill(5); console.log(zeroes); // [5, 5, 5, 5, 5] zeroes.fill(0); // reset // Fill all indices >=3 with 6 zeroes.fill(6, 3); console.log(zeroes); // [0, 0, 0, 6, 6] zeroes.fill(0); // reset // Fill all indices >= 1 and < 3 with 7 zeroes.fill(7, 1, 3); console.log(zeroes); // [0, 7, 7, 0, 0]; zeroes.fill(0); // reset // Fill all indices >=1 and < 4 with 8 // (-4 + zeroes.length = 1) // (-1 + zeroes.length = 4) zeroes.fill(8, -4, -1); console.log(zeroes); Output: fill() silently ignores ranges that exceed the boundaries of the array, are zero length, or go backwards: const zeroes = [0, 0, 0, 0, 0]; // Fill with too low indices is noop zeroes.fill(1, -10, -6); console.log(zeroes); Output: Output: Output: Output:

Array Copy Methods

copyWithin() performs an iterative shallow copy of some of the array and overwrites existing values beginning at the provided index. It uses the same conventions with respect to start and end indices. Copy the contents of ints beginning at index 0 to the values beginning at index 5. Stops when it reaches the end of the array either in the source indices or the destination indices. let ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; ints.copyWithin(5); console.log(ints); Output: Copy the contents of ints beginning at index 5 to the values beginning at index 0. let ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; ints.copyWithin(0, 5); console.log(ints); Output: Copy the contents of ints beginning at index 0 and ending at index 3 to values beginning at index 4. let ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; ints.copyWithin(4, 0, 3); console.log(ints); Output: The JS engine will perform a full copy of the range of values before inserting, so there is no danger of overwrite during the copy. let ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; ints.copyWithin(2, 0, 6); console.log(ints); Output: Support for negative indexing behaves identically to fill() in that negative indices are calculated relative to the end of the array let ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; ints.copyWithin(-4, -7, -3); console.log(ints); Output: copyWithin() silently ignores ranges that exceed the boundaries of the array, are zero length, or go backwards: Copy with too low indices is noop let ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; ints.copyWithin(1, -15, -12); console.log(ints); Output: Copy with too high indices is noop let ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; ints.copyWithin(1, 12, 15); console.log(ints); Output: Copy with reversed indices is noop let ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; ints.copyWithin(2, 4, 2); console.log(ints); Output: Copy with partial index overlap is best effort let ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; ints.copyWithin(4, 7, 10) console.log(ints); Output:

Convert Array to String

Array objects have toLocaleString(), toString(), and valueOf() methods. The toString() and valueOf() methods return the same value when called on an array. The result is a comma-separated string that contains the string equivalents of each value in the array, each item has its toString() method called to create the final string. Take a look at this example: let colors = ["red", "blue", "green"]; // creates an array with three strings console.log(colors.toString()); // red,blue,green console.log(colors.valueOf()); // red,blue,green console.log(colors); // red,blue,green Output: In this code, the toString() and valueOf() methods are first called explicitly to return the string representation of the array, which combines the strings, separating them by commas. The last line passes the array directly into console.log(). Because console.log() expects a string, it calls toString() behind the scenes to get the same result as when toString() is called directly. The toLocaleString() method may end up returning the same value as toString() and valueOf(), but not always. When toLocaleString() is called on an array, it creates a comma delimited string of the array values. toLocaleString() calls each item's toLocaleString() instead of toString() to get its string value. Consider the following example: let person1 = {// w w w . de m o 2 s . c o m toLocaleString() { return "Javascript"; }, toString() { return "CSS"; } }; let person2 = { toLocaleString() { return "SQL"; }, toString() { return "HTML"; } }; let people = [person1, person2]; console.log(people); // CSS,HTML console.log(people.toString()); // CSS,HTML console.log(people.toLocaleString()); // Javascript,SQL Output: Here, two objects are defined, person1 and person2. Each object defines both a toString() method and a toLocaleString() method that return different values. An array, people, is created to contain both objects. When passed into console.log(), the output is "CSS,HTML" because the toString() method is called on each item in the array (the same as when toString() is called explicitly on the next line). When toLocaleString() is called on the array, the result is "Javascript,SQL" because this calls toLocaleString() on each array item. The inherited methods toLocaleString(), toString(), and valueOf() each return the array items as a comma-separated string.

Convert Array to String by joining all element

We can construct a string with a different separator using the join() method. The join() method accepts one argument, which is the string separator to use, and returns a string containing all items. Consider this example: let colors = ["red", "green", "blue"]; console.log(colors.join(",")); // red,green,blue console.log(colors.join("||")); // red||green||blue Here, the join() method is used on the colors array to duplicate the output of toString(). By passing in a comma, the result is a comma-separated list of values. On the last line, double pipes are passed in, resulting in the string "red||green||blue". If no value or undefined is passed into the join() method, then a comma is used as the separator. If an item in the array is null or undefined, it is represented by an empty string in the result of join(), toLocaleString(), toString(), and valueOf().

Array Stack Methods

ECMAScript arrays provide a method to make an array behave like other data structures. An array object can act just like a stack, which is one of a group of data structures that restrict the insertion and removal of items. A stack is referred to as a last-in-first-out (LIFO) structure, meaning that the most recently added item is the first one removed. The insertion (called a push) and removal (called a pop) of items in a stack occur at only one point: the top of the stack. ECMAScript arrays provide push() and pop() specifically to allow stack-like behavior. The push() method accepts any number of arguments and adds them to the end of the array, returning the array's new length. The pop() method removes the last item in the array, decrements the array's length, and returns that item. Consider this example: let colors = new Array(); // create an array let count = colors.push("red", "green"); // push two items console.log(count); // 2 count = colors.push("black"); // push another item on console.log(count); // 3 let item = colors.pop(); // get the last item console.log(item); // "black" console.log(colors.length);// 2 In this code, an array is created for use as a stack. First, two strings are pushed onto the end of the array using push(), and the result is stored in the variable count. Then, another value is pushed on, and the result is once again stored in count. Because there are now three items in the array, push() returns 3. When pop() is called, it returns the last item in the array, which is the string "black". The array then has only two items left. The stack methods may be used in combination with all of the other array methods as well, as in this example: let colors = ["red", "blue"]; colors.push("brown"); // add another item colors[3] = "black"; // add an item console.log(colors.length); // 4 let item = colors.pop(); // get the last item console.log(item); // "black" Here, an array is initialized with two values. A third value is added via push(), and a fourth is added by direct assignment into position 3. When pop() is called, it returns the string "black", which was the last value added to the array.

Array Queue Methods

Javascript array can be used as a Queue data structure. The queues restrict access in a first-in-first-out (FIFO) data structure. A queue adds items to the end of a list and retrieves items from the front of the list. Because the push() method adds items to the end of an array, all that is needed to emulate a queue is a method to retrieve the first item in the array. The array method for this is called shift(), which removes the first item in the array and returns it, decrementing the length of the array by one. Using shift() in combination with push() allows arrays to be used as queues: let colors = new Array(); // create an array let count = colors.push("red", "green"); // push two items console.log(count); // 2 count = colors.push("black"); // push another item on console.log(count); // 3 let item = colors.shift(); // get the first item console.log(item); // "red" console.log(colors.length); // 2 This example creates an array of three colors using the push() method. The shift() method is used to retrieve the first item in the array, which is "red". With that item removed, "green" is moved into the first position and "black" is moved into the second, leaving the array with two items. ECMAScript also provides an unshift() method for arrays. it adds any number of items to the front of an array and returns the new array length. By using unshift() in combination with pop(), it's possible to emulate a queue in the opposite direction, where new values are added to the front of the array and values are retrieved off the back, as in this example: let colors = new Array(); // create an array let count = colors.unshift("red", "green"); // push two items console.log(count); // 2 count = colors.unshift("black"); // push another item on console.log(count); // 3 let item = colors.pop(); // get the first item console.log(item); // "green" console.log(colors.length);// 2 In this code, an array is created and then populated by using unshift(). First "red" and "green" are added to the array, and then "black" is added, resulting in an order of "black", "red", "green". When pop() is called, it removes the last item, "green", and returns it.

Array Reordering Methods via sort() and reverse()

Two methods deal directly with the reordering of items already in the array: reverse() and sort().

reverse()

The reverse() method reverses the order of items in an array. Consider the following example: let values = [1, 2, 3, 4, 5]; values.reverse(); console.log(values); // 5,4,3,2,1 Here, the array's values are initially set to 1, 2, 3, 4, and 5, in that order. Calling reverse() on the array reverses the order to 5, 4, 3, 2, 1.

sort()

By default, the sort() method puts the items in ascending order-with the smallest value first and the largest value last. To do this, the sort() method calls the String() casting function on every item and then compares the strings to determine the correct order. This occurs even if all items in an array are numbers, as in this example: let values = [0, 1, 5, 10, 15]; values.sort(); console.log(values); // 0,1,10,15,5 The sort() method changes that order based on their string equivalents. So even though 5 is less than 10, the string "10" comes before "5" when doing a string comparison, so the array is updated accordingly. The sort() method allows you to pass in a comparison function that indicates which value should come before which. A comparison function accepts two arguments and returns a negative number if the first argument should come before the second, a zero if the arguments are equal, or a positive number if the first argument should come after the second. Here's an example of a simple comparison function: function compare(value1, value2) { if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } This comparison function works for most data types and can be used by passing it as an argument to the sort() method, as in the following example: let values = [0, 1, 5, 10, 15]; values.sort(compare); console.log(values); // 0,1,5,10,15 When the comparison function is passed to the sort() method, the numbers remain in the correct order. Of course, the comparison function could produce results in descending order if you simply switch the return values like this: function compare(value1, value2) { if (value1 < value2) { return 1; } else if (value1 > value2) { return -1; } else { return 0; } } let values = [0, 1, 5, 10, 15]; values.sort(compare); console.log(values); // 15,10,5,1,0 Alternately, the compare function can be shortened and defined as an inline arrow function: let values = [0, 1, 5, 10, 15]; values.sort((a, b) => a < b ? a > b ? -1 : 0); console.log(values); // 15,10,5,1,0 In this modified example, the comparison function returns 1 if the first value should come after the second and -1 if the first value should come before the second. Swapping these means the larger value will come first and the array will be sorted in descending order. Both reverse() and sort() return a reference to the array on which they were applied. We can code the comparison function with numeric types and objects whose valueOf() method returns numeric values, such as the Date object. In either case, you can simply subtract the second value from the first as shown here: function compare(value1, value2){ return value2 - value1; } Because comparison functions work by returning a number less than zero, zero, or a number greater than zero, the subtraction operation handles all of the cases appropriately.

Append two Arrays together using concat() method

The concat() method creates a new array based on all of the items in the current array. This method begins by creating a copy of the array and then appending the method arguments to the end and returning the newly constructed array. When no arguments are passed in, concat() simply clones the array and returns it. If one or more arrays are passed in, concat() appends each item in these arrays to the end of the result. If the values are not arrays, they are simply appended to the end of the resulting array. Consider this example: let colors = ["red", "green", "blue"]; let colors2 = colors.concat("yellow", ["black", "brown"]); console.log(colors); // ["red", "green","blue"] console.log(colors2); // ["red", "green", "blue", // "yellow", "black", "brown"] This code begins with the colors array containing three values. The concat() method is called on colors, passing in the string "yellow" and an array containing "black" and "brown". The result, stored in colors2, contains "red", "green", "blue", "yellow", "black", and "brown". The original array, colors, remains unchanged. You can override this force-flattening default behavior by specifying a special symbol on the argument array instance, Symbol.isConcatSpreadable. This will prevent the concat() method from flattening the result. Conversely, setting the value to true will force array-like objects to be flattened: let colors = ["red", "green", "blue"]; let newColors = ["black", "brown"]; let moreNewColors = { [Symbol.isConcatSpreadable]: true,/*w w w . d e m o 2 s . c om */ length: 2, 0: "pink", 1: "cyan" }; newColors[Symbol.isConcatSpreadable] = false; // Force the array to not be flattened let colors2 = colors.concat("yellow", newColors); // Force the array-like object to be flattened let colors3 = colors.concat(moreNewColors); console.log(colors); // ["red", "green","blue"] console.log(colors2); // ["red", "green", "blue", // "yellow", ["black", "brown"]] console.log(colors3); // ["red", "green", "blue", "pink, "cyan"] Output:

Iterator and Spread operator Introduction

ECMAScript 6 introduced iterators and the spread operator, which are especially useful for collection reference types. These new tools allow for easy interoperability, cloning, and modification of collection types. Four native collection reference types define a default iterator: Array All typed arrays Map Set All support ordered iteration and can be passed to a for...of loop: let iterableThings = [ Array.of(1, 2), typedArr = Int16Array.of(3, 4), new Map([[5, 6], [7, 8]]), new Set([9, 10]) ]; for (const iterableThing of iterableThings) { for (const x of iterableThing) { console.log(x); } } Output: All these types are compatible with the spread operator. The spread operator is especially useful as it performs a shallow copy on the iterable object. This allows you to easily clone entire objects with a succinct syntax: let arr1 = [1, 2, 3]; let arr2 = [...arr1]; console.log(arr1); // [1, 2, 3] console.log(arr2); // [1, 2, 3] console.log(arr1 === arr2); // false Constructors which expect an iterable object can just be passed the iterable instance to be cloned: let map1 = new Map([[1, 2], [3, 4]]); let map2 = new Map(map1); console.log(map1); // Map {1 => 2, 3 => 4} console.log(map2); // Map {1 => 2, 3 => 4} It also allows for partial array construction: let arr1 = [1, 2, 3]; let arr2 = [0, ...arr1, 4, 5]; console.log(arr2); // [0, 1, 2, 3, 4, 5] The shallow copy mechanism means that only object references are copied: let arr1 = [{}]; let arr2 = [...arr1]; arr1[0].foo = 'bar'; console.log(arr2[0]); // { foo: 'bar' } Each of these collection types support multiple methods of construction, such as the Array.of() and Array.from() static methods. When combined with the spread operator, this makes for easy interoperability: let arr1 = [1, 2, 3]; // Copy array into typed array let typedArr1 = Int16Array.of(...arr1); let typedArr2 = Int16Array.from(arr1); console.log(typedArr1); // Int16Array [1, 2, 3] console.log(typedArr2); // Int16Array [1, 2, 3] // Copy array into map let map = new Map(arr1.map((x) => [x, 'val' + x])); console.log(map); // Map {1 => 'val 1', 2 => 'val 2', 3 => 'val 3'} // Copy array in to set let set = new Set(typedArr2); console.log(set); // Set {1, 2, 3} // Copy set back into array let arr2 = [...set]; console.log(arr2); // [1, 2, 3]

Iterator Protocol

An iterator is a single-use object that will iterate through whatever iterable it is associated with. The Iterator API uses a next() method to advance through the iterable. Each successive time next() is invoked, it will return an IteratorResult object containing the next value in the iterator. The current position the iterator is at cannot be known without invoking the next() method. The next() method returns an object with two properties: done, which is a Boolean indicating if next() can be invoked again to retrieve more values, and value, which will contain the next value in the iterable or undefined if done is true. The done:true state is termed "exhaustion." This can be demonstrated with a simple array: // Iterable object let arr = ['foo', 'bar']; // Iterator factory console.log(arr[Symbol.iterator]); // f values() { [native code] } // Iterator let iter = arr[Symbol.iterator](); console.log(iter); // ArrayIterator {} // Performing iteration console.log(iter.next()); // { done: false, value: 'foo' } console.log(iter.next()); // { done: false, value: 'bar' } console.log(iter.next()); // { done: true, value: undefined } Arrays are iterated in order by creating an iterator and invoking next() until it ceases to produce new values. The iterator does not know how to retrieve the next values inside the iterable, and it does not know how large the iterable is. Once the iterator reaches the done:true state, invoking next() returns undefined: let arr = ['foo']; let iter = arr[Symbol.iterator](); console.log(iter.next()); // { done: false, value: 'foo' } console.log(iter.next()); // { done: true, value: undefined } console.log(iter.next()); // { done: true, value: undefined } console.log(iter.next()); // { done: true, value: undefined } Each iterator represents a one-time ordered traversal of the iterable. Different instances are not aware of each other and will independently traverse the iterable: let arr = ['foo', 'bar']; let iter1 = arr[Symbol.iterator](); let iter2 = arr[Symbol.iterator](); console.log(iter1.next()); // { done: false, value: 'foo' } console.log(iter2.next()); // { done: false, value: 'foo' } console.log(iter2.next()); // { done: false, value: 'bar' } console.log(iter1.next()); // { done: false, value: 'bar' } An iterator is not bound to a snapshot of the iterable; it merely uses a cursor to track its progress through the iterable. If the iterable is mutated during iteration, the iterator will incorporate the changes: let arr = ['foo', 'baz']; let iter = arr[Symbol.iterator](); console.log(iter.next()); // { done: false, value: 'foo' } // Insert value in the middle of array arr.splice(1, 0, 'bar'); console.log(iter.next()); // { done: false, value: 'bar' } console.log(iter.next()); // { done: false, value: 'baz' } console.log(iter.next()); // { done: true, value: undefined } An iterator maintains a reference to the iterable object, so be aware that the iterator's existence will prevent garbage collection of the iterable object. The following example compares an explicit iterator implementation and a native iterator implementation: Foo class implements the Iterable interface. Invoking the default iterator factory will return an iterator object that implements the Iterator interface. class Foo { [Symbol.iterator]() { return { next() { return { done: false, value: 'foo' }; } } } } let f = new Foo(); // Logs an object which implements the Iterator interface console.log(f[Symbol.iterator]()); // { next: f() {} } The Array type implements the Iterable interface. Invoking the default iterator of an Array type will create an instance of ArrayIterator. let a = new Array(); // Logs an instance of ArrayIterator console.log(a[Symbol.iterator]()); // Array Iterator {}

Iterable Protocol Introduction

Many built-in types implement the Iterable interface: Strings Arrays Maps Sets The arguments object DOM collection types like NodeList Checking for the existence of this default iterator property will expose the factory function: let num = 1;/* w w w. d e m o 2 s .c o m */ let obj = {}; // These types do not have iterator factories console.log(num[Symbol.iterator]); // undefined console.log(obj[Symbol.iterator]); // undefined let str = 'abc'; let arr = ['a', 'b', 'c']; let map = new Map().set('a', 1).set('b', 2).set('c', 3); let set = new Set().add('a').add('b').add('c'); let els = document.querySelectorAll('div'); // These types all have iterator factories console.log(str[Symbol.iterator]); // f values() { [native code] } console.log(arr[Symbol.iterator]); // f values() { [native code] } console.log(map[Symbol.iterator]); // f values() { [native code] } console.log(set[Symbol.iterator]); // f values() { [native code] } console.log(els[Symbol.iterator]); // f values() { [native code] } // Invoking the factory function produces an Iterator console.log(str[Symbol.iterator]()); // StringIterator {} console.log(arr[Symbol.iterator]()); // ArrayIterator {} console.log(map[Symbol.iterator]()); // MapIterator {} console.log(set[Symbol.iterator]()); // SetIterator {} console.log(els[Symbol.iterator]()); // ArrayIterator {} Value that implements this protocol is compatible with any language features that accept an iterable. These native language constructs include: for...of loop Array destructuring The spread operator Array.from() Set construction Map construction Promise.all(), which expects an iterable of promises Promise.race(), which expects an iterable of promises yield* operator in generators Behind the scenes, these native language constructs are invoking the factory function of the provided iterable to create an iterator: let arr = ['foo', 'bar', 'baz']; // for...of loops for (let el of arr) { console.log(el);// w w w . d e mo 2 s .c o m } // foo // bar // baz // Array destructuring let [a, b, c] = arr; console.log(a, b, c); // foo, bar, baz // Spread operator let arr2 = [...arr]; console.log(arr2); // ['foo', 'bar', 'baz'] // Array.from() let arr3 = Array.from(arr); console.log(arr3); // ['foo', 'bar', 'baz'] // Set constructor let set = new Set(arr); console.log(set); // Set(3) {'foo', 'bar', 'baz'} // Map constructor let pairs = arr.map((x, i) => [x, i]); console.log(pairs); // [['foo', 0], ['bar', 1], ['baz', 2]] let map = new Map(pairs); console.log(map); // Map(3) { 'foo'=>0, 'bar'=>1, 'baz'=>2 } An object still implements the Iterable interface if a parent class up the prototype chain implements the interface: class FooArray extends Array {} let fooArr = new FooArray('foo', 'bar', 'baz'); for (let el of fooArr) { console.log(el); } Output:

Custom Iterator Definition

Any object that implements the Iterator interface can be used as an iterator. Consider the following example where a Counter class is defined to iterate a specific number of times: class Counter {// w w w . d e m o 2 s . c om // Counter instance should iterate <limit> times constructor(limit) { this.count = 1; this.limit = limit; } next() { if (this.count <= this.limit) { return { done: false, value: this.count++ }; } else { return { done: true, value: undefined }; } } [Symbol.iterator]() { return this; } } let counter = new Counter(3); for (let i of counter) { console.log(i); } Output: This satisfies the Iterator interface, but this implementation isn't optimal because each class instance can be iterated only once: for (let i of counter) { console.log(i); } // 1 // 2 // 3 for (let i of counter) { console.log(i); } // (nothing logged) In order to allow for creating multiple iterators from a single iterable, the counter must be created on a per-iterator basis. To address this, you can return an iterator object with the counter variables available through a closure: class Counter {// w w w . d e m o 2 s. co m constructor(limit) { this.limit = limit; } [Symbol.iterator]() { let count = 1, limit = this.limit; return { next() { if (count <= limit) { return { done: false, value: count++ }; } else { return { done: true, value: undefined }; } } }; } } let counter = new Counter(3); for (let i of counter) { console.log(i); } // 1 // 2 // 3 for (let i of counter) { console.log(i); } // 1 // 2 // 3 Every iterator created in this way also implements the Iterable interface. The Symbol.iterator property refers to a factory that will return the same iterator: let arr = ['foo', 'bar', 'baz']; let iter1 = arr[Symbol.iterator](); console.log(iter1[Symbol.iterator]); // f values() { [native code] } let iter2 = iter1[Symbol.iterator](); console.log(iter1 === iter2); // true Because every iterator also implements the Iterable interface, they can be used everywhere an iterable is expected, such as a for-of loop: let arr = [3, 1, 4]; let iter = arr[Symbol.iterator](); for (let item of arr) { console.log(item); } // 3 // 1 // 4 for (let item of iter) { console.log(item); } // 3 // 1 // 4

Early Termination of Iterators

The optional return() method will execute only if the iterator is closed prematurely. Closing an iterator occurs when the construct performing the iteration wishes to indicate to the iterator that it does not intend to finish traversing until exhaustion. Scenarios where this might happen include the following: A for...of loop exits early via break, continue, return, or throw. A destructuring operation does not consume all values. The return() method must return a valid IteratorResult object. A simple iterator implementation should just return { done: true }, as the return value is only used in the context of generators. As shown in the code that follows, a built-in language construct will automatically invoke the return() method once it identifies that there are further values that need to be iterated over that will not be consumed. class Counter {// w w w . d e mo 2 s . c o m constructor(limit) { this.limit = limit; } [Symbol.iterator]() { let count = 1, limit = this.limit; return { next() { if (count <= limit) { return { done: false, value: count++ }; } else { return { done: true }; } }, return() { console.log('Exiting early'); return { done: true }; } }; } } let counter1 = new Counter(5); for (let i of counter1) { if (i > 2) { break; } console.log(i); } // 1 // 2 // Exiting early let counter2 = new Counter(5); try { for (let i of counter2 { if (i > 2) { throw 'err'; } console.log(i); } } catch(e) {} // 1 // 2 // Exiting early let counter3 = new Counter(5); let [a, b] = counter3; // Exiting early If an iterator is not closed, then you are able to pick up iteration where you left off. For example, Array Iterators are not closable: let a = [1, 2, 3, 4, 5]; let iter = a[Symbol.iterator](); for (let i of iter) { console.log(i); if (i > 2) { break } } // 1 // 2 // 3 for (let i of iter) { console.log(i); } // 4 // 5 Because the return() method is optional, not all iterators are closable. Whether or not an iterator is closable can be ascertained by testing if the return property on the iterator instance is a function object. However, merely adding the method to a non-closable iterator will not make it become closable, as invoking return() does not force the iterator into a closed state. The return() method will, however, still be invoked: let a = [1, 2, 3, 4, 5]; let iter = a[Symbol.iterator](); iter.return = function() { console.log('Exiting early'); return { done: true }; }; for (let i of iter) { console.log(i);// w w w. de m o 2 s . c o m if (i > 2) { break } } // 1 // 2 // 3 // Exiting early for (let i of iter) { console.log(i); } // 4 // 5

Generator Introduction

ECMAScript 6 Generators can pause and resume code execution inside a single function block. We can use it to define custom iterators and implement coroutines.

Generator Basics

Generators take the form of a function with an asterisk. Anywhere a function definition is valid, a generator function definition is also valid. Generator function declaration: function* generatorFn() {} Generator function expression let generatorFn = function* () {} Object literal method generator function let foo = { * generatorFn() {} } Class instance method generator function class Foo { * generatorFn() {} } Class static method generator function class Bar { static * generatorFn() {} } Note: Arrow functions cannot be used as generator functions. The function will be considered a generator irrespective of the whitespace surrounding the asterisk: // Equivalent generator functions: function* generatorFnA() {} function *generatorFnB() {} function * generatorFnC() {} // Equivalent generator methods: class Foo { *generatorFnD() {} * generatorFnE() {} }

Generator execution

When invoked, generator functions produce a generator object. Generator objects begin in a state of suspended execution. The generator objects implement the Iterator interface and therefore has a next() method, which, when invoked, instructs the generator to begin or resume execution. function* generatorFn() {} const g = generatorFn(); console.log(g); // generatorFn {<suspended>} console.log(g.next); // f next() { [native code] } The return value of this next() method matches that of an iterator, with a done and value property. A generator function with an empty function body will act as a pass through; invoking next() a single time will result in the generator reaching the done:true state. function* generatorFn() {} let generatorObject = generatorFn(); console.log(generatorObject); // generatorFn {<suspended>} console.log(generatorObject.next()); // { done: true, value: undefined } The value property is the return value of the generator function, which defaults to undefined and can be specified via the generator function's return value. function* generatorFn() { return 'foo'; } let generatorObject = generatorFn(); console.log(generatorObject); // generatorFn {<suspended>} console.log(generatorObject.next()); // { done: true, value: 'foo' } Generator function execution will only begin upon the initial next() invocation, as shown here: function* generatorFn() { console.log('foobar'); } // Nothing is logged yet when the generator function is initially invoked let generatorObject = generatorFn(); generatorObject.next(); // foobar Generator objects implement the Iterable interface, and their default iterator is self-referential: function* generatorFn() {} console.log(generatorFn); // f* generatorFn() {} console.log(generatorFn()[Symbol.iterator]); // f [Symbol.iterator]() {native code} console.log(generatorFn()); // generatorFn {<suspended>} console.log(generatorFn()[Symbol.iterator]()); // generatorFn {<suspended} const g = generatorFn(); console.log(g === g[Symbol.iterator]()); // true

Interrupting Generator Execution with yield

The yield keyword allows generators to stop and start execution, and it is what makes generators truly useful. Generator functions will proceed with normal execution until they encounter a yield keyword. Upon encountering the yield keyword, execution will be halted and the scope state of the function will be preserved. Execution will only resume when the next() method is invoked on the generator object: function* generatorFn() { yield; } let generatorObject = generatorFn(); console.log(generatorObject.next()); // { done: false, value: undefined } console.log(generatorObject.next()); // { done: true, value: undefined } The yield keyword behaves as an intermediate function return, and the yielded value is available inside the object returned by the next() method. A generator function exiting via the yield keyword will have a done value of false; a generator function exiting via the return keyword will have a done value of true: function* generatorFn() { yield 'foo'; yield 'bar'; return 'baz'; } let generatorObject = generatorFn(); console.log(generatorObject.next()); // { done: false, value: 'foo' } console.log(generatorObject.next()); // { done: false, value: 'bar' } console.log(generatorObject.next()); // { done: true, value: 'baz' } Execution progress within a generator function is scoped to each generator object instance. Invoking next() on one generator object does not affect any other: function* generatorFn() { yield 'foo'; yield 'bar'; return 'baz'; } let generatorObject1 = generatorFn(); let generatorObject2 = generatorFn(); console.log(generatorObject1.next()); // { done: false, value: 'foo' } console.log(generatorObject2.next()); // { done: false, value: 'foo' } console.log(generatorObject2.next()); // { done: false, value: 'bar' } console.log(generatorObject1.next()); // { done: false, value: 'bar' } The yield keyword can only be used inside a generator function; anywhere else will throw an error. Nesting further inside a non-generator function will throw a syntax error: // valid/* w w w . d e m o 2 s . c o m */ function* validGeneratorFn() { yield; } // invalid function* invalidGeneratorFnA() { function a() { yield; } } // invalid function* invalidGeneratorFnB() { const b = () => { yield; } } // invalid function* invalidGeneratorFnC() { (() => { yield; })(); }

Using a Generator Object as an Iterable

Javascript generators are much more useful when consumed as an iterable, as shown here: function* generatorFn() { yield 1; yield 2; yield 3; } for (const x of generatorFn()) { console.log(x); } Output: This can be useful to define custom iterable. For example, it is often useful to define an iterable, which will produce an iterator that executes a specific number of times. With a generator, this can be accomplished simply with a loop: function* nTimes(n) { while(n--) { yield; } } for (let _ of nTimes(3)) { console.log('foo'); } Output: The single generator function parameter controls the number of loop iterations. When n reaches 0, the while condition will become falsy, the loop will exit, and the generator function will return.

Using yield for Input and Output for Generator

The yield keyword also behaves as an intermediate function parameter. The value provided to the first next() invocation is not used, as this next() is used to begin the generator function execution: function* generatorFn(initial) { console.log(initial); console.log(yield); console.log(yield); } let generatorObject = generatorFn('foo'); generatorObject.next('bar'); // foo generatorObject.next('baz'); // baz generatorObject.next('qux'); // qux The yield keyword can be simultaneously used as both an input and an output, as is shown in the following example: function* generatorFn() { return yield 'foo'; } let generatorObject = generatorFn(); console.log(generatorObject.next()); // { done: false, value: 'foo' } console.log(generatorObject.next('bar')); // { done: true, value: 'bar' } Because the function must evaluate the entire expression to determine the value to return, it will pause execution when encountering the yield keyword and evaluate the value to yield, foo. The subsequent next() invocation provides the bar value as the value for that same yield, and this in turn is evaluated as the generator function return value. The yield keyword is not limited to a one-time use. An infinite counting generator function can be defined as follows: function* generatorFn() { for (let i = 0;;++i) { yield i; } } let generatorObject = generatorFn(); console.log(generatorObject.next().value); // 0 console.log(generatorObject.next().value); // 1 console.log(generatorObject.next().value); // 2 console.log(generatorObject.next().value); // 3 console.log(generatorObject.next().value); // 4 console.log(generatorObject.next().value); // 5 ... Suppose you wanted to define a generator function that would iterate a configurable number of times and produce the index of iteration. This can be accomplished by instantiating a new array, but the same behavior can be accomplished without the array: function* nTimes(n) { for (let i = 0; i < n; ++i) { yield i; } } for (let x of nTimes(3)) { console.log(x); } // 0 // 1 // 2 Alternately, the following has a slightly less verbose while loop implementation: function* nTimes(n) { let i = 0; while(n--) { yield i++; } } for (let x of nTimes(3)) { console.log(x); } Output: Using generators in this way provides a useful way of implementing ranges or populating arrays: function* range(start, end) { let i = start; while(end > start) { yield start++; } } for (const x of range(4, 7)) { console.log(x); } Output: function* zeroes(n) { while(n--) { yield 0; } } console.log(Array.from(zeroes(8))); Output:

Generator Yield an Iterable

We can use yield to iterate through an iterable and yield its contents one at a time. This can be done using an asterisk, as shown here: // generatorFn is equivalent to: // function* generatorFn() { // for (const x of [1, 2, 3]) { // yield x; // } // } function* generatorFn() { yield* [1, 2, 3]; } let generatorObject = generatorFn(); for (const x of generatorFn()) { console.log(x); } Output: Like the generator function asterisk, whitespace around the yield asterisk will not alter its behavior: function* generatorFn() { yield* [1, 2]; yield *[3, 4]; yield * [5, 6]; } for (const x of generatorFn()) { console.log(x); } Output: Because yield* is effectively serializing an iterable into sequential yielded values, using it isn't any different than placing yield inside a loop. These two generator functions are equivalent in behavior: function* generatorFnA() { for (const x of [1, 2, 3]) { yield x;/* w w w. d e m o2 s . c o m */ } } for (const x of generatorFnA()) { console.log(x); } // 1 // 2 // 3 function* generatorFnB() { yield* [1, 2, 3]; } for (const x of generatorFnB()) { console.log(x); } // 1 // 2 // 3 The value of yield* is the value property accompanying done:true of the associated iterator. For vanilla iterators, this value will be undefined: function* generatorFn() { console.log('iter value:', yield* [1, 2, 3]); } for (const x of generatorFn()) { console.log('value:', x); } Output: For iterators produced from a generator function, this value will take the form of whatever value is returned from the generator function: function* innerGeneratorFn() { yield 'foo'; return 'bar'; } function* outerGeneratorFn(genObj) { console.log('iter value:', yield* innerGeneratorFn()); } for (const x of outerGeneratorFn()) { console.log('value:', x); } Output:

Recursive Algorithms Using yield*

yield* is useful when used in a recursive operation, where the generator can yield itself. Consider the following example: function* nTimes(n) { if (n > 0) { yield* nTimes(n - 1); yield n - 1; } } for (const x of nTimes(3)) { console.log(x); } Output: In this example, each generator is first yielding each value from a newly created generator object, and then yielding a single integer. The generator function will recursively decrement the counter value and instantiate another generator object, which at the top level will have the effect of creating a single iterable that returns incremental integers. Using recursive generator structure and yield* allows for elegantly expressing recursive algorithms.

Using a Generator as the Default Iterator

Because generator objects implement the Iterable interface, and because both generator functions and the default iterator are invoked to produce an iterator, generators are suited to be used as default iterators. The following is a simple example where the default iterator can yield the class's contents in a single line: class Foo { constructor() { this.values = [1, 2, 3]; } *[Symbol.iterator]() { yield* this.values; } } const f = new Foo(); for (const x of f) { console.log(x); } Output: Here, the for...of loop invokes the default iterator-which happens to be a generator function and produces a generator object. The generator object is an iterable and therefore compatible for use in iteration.

Early Termination of Generators

Javascript generators also support the concept of being "closable." For an object to implement the Iterator interface, it must have a next() and, optionally, a return() method for when the iterator is terminated early. A generator object has both of these methods and an additional third method, throw(). function* generatorFn() {} const g = generatorFn(); console.log(g); // generatorFn {<suspended>} console.log(g.next); // f next() { [native code] } console.log(g.return); // f return() { [native code] } console.log(g.throw); // f throw() { [native code] } The return() and throw() methods are two methods that can be used to coerce the generator into a closed state.

Generator return() method

The return() method will force the generator into a closed state, and the value provided to return() will be the value provided in the terminal iterator object: function* generatorFn() { for (const x of [1, 2, 3]) { yield x; } } const g = generatorFn(); console.log(g); // generatorFn {<suspended>} console.log(g.return(4)); // { done: true, value: 4 } console.log(g); // generatorFn {<closed>} Unlike iterators, all generator objects have a return() method that forces it into a closed state that it cannot exit once reached. Subsequent invoking of next() will disclose the done:true state, but any provided return value is not stored or propagated: function* generatorFn() { for (const x of [1, 2, 3]) { yield x; } } const g = generatorFn(); console.log(g.next()); // { done: false, value: 1 } console.log(g.return(4)); // { done: true, value: 4 } console.log(g.next()); // { done: true, value: undefined } console.log(g.next()); // { done: true, value: undefined } console.log(g.next()); // { done: true, value: undefined } Built-in language constructs such as the for...of loop will sensibly ignore any values returned inside the done:true IteratorObject. function* generatorFn() { for (const x of [1, 2, 3]) { yield x; } } const g = generatorFn(); for (const x of g) { if (x > 1) { g.return(4); } console.log(x); } Output:

Generator throw() Method

The throw() method will inject a provided error into the generator object at the point it is suspended. If the error is unhandled, the generator will close: function* generatorFn() { for (const x of [1, 2, 3]) { yield x; } } const g = generatorFn(); console.log(g); // generatorFn {<suspended>} try { g.throw('foo'); } catch (e) { console.log(e); // foo } console.log(g); // generatorFn {<closed>} If, however, the error is handled inside the generator function, then it will not close and can resume execution. The error handling will skip over that yield, so in this example you will see it skip a value. Consider the following example: function* generatorFn() { for (const x of [1, 2, 3]) { try { yield x; } catch(e) {} } } const g = generatorFn(); console.log(g.next()); // { done: false, value: 1} g.throw('foo'); console.log(g.next()); // { done: false, value: 3} In this example, the generator suspends execution at a yield keyword inside a try/catch block. While it is suspended, throw() injects the foo error, which is thrown by the yield keyword. Because this error is thrown inside the generator's try/catch block, it is subsequently caught while still inside the generator. However, because yield threw that error, that value of 2 will not be produced by the generator. Instead, the generator function continues execution, proceeding on to the next loop iteration where it encounters the yield keyword yet again-this time, yielding the value 3.

Class Introduction

ECMAScript 6 can formally define classes using the class keyword. ECMAScript 6 classes still use prototype and constructor concepts under the hood.

Class Definition Basics

Similar to the function type, there are two primary ways of defining a class: class declarations and class expressions. Both use the class keyword and curly braces: // class declaration class Person {} // class expression const Animal = class {}; Like function expressions, class expressions cannot be referenced until they are evaluated in execution. Javascript function declarations are hoisted, class declarations are not: console.log(FunctionExpression); // undefined var FunctionExpression = function() {}; console.log(FunctionExpression); // function() {} console.log(FunctionDeclaration); // FunctionDeclaration() {} function FunctionDeclaration() {} console.log(FunctionDeclaration); // FunctionDeclaration() {} console.log(ClassExpression); // undefined var ClassExpression = class {}; console.log(ClassExpression); // class {} console.log(ClassDeclaration);//ReferenceError:ClassDeclaration is not defined class ClassDeclaration {} console.log(ClassDeclaration); // class ClassDeclaration {} function declarations are function scoped, while class declarations are block scoped: { function FunctionDeclaration() {} class ClassDeclaration {} } console.log(FunctionDeclaration);// FunctionDeclaration() {} console.log(ClassDeclaration);//ReferenceError:ClassDeclaration is not defined

Class Member Composition

A class can be composed of the class's constructor method, instance methods, getters, setters, and static class methods. None of these are explicitly required. By default, everything inside a class definition executes in strict mode. We should capitalize the class name to distinguish it from instances that are created from it. For example, class Foo {} might create an instance foo. Valid empty class definition Valid class definition with constructor class Bar { constructor() {} } Valid class definition with getter class Baz { get myBaz() {} } Valid class definition with static method class Qux { static myQux() {} }

Class expressions

Class expressions may be optionally named. When the expression is assigned to a variable, the name property may be used to retrieve the class expression name string, but the identifier itself is not accessible outside the class expression scope. let Person = class PersonName { identify() { console.log(Person.name, PersonName.name); } } let p = new Person(); p.identify(); // PersonName, PersonName console.log(Person.name); // PersonName console.log(PersonName); // ReferenceError: PersonName is not defined

Class Constructor

The constructor keyword is used inside the class definition block to define the class's constructor function. The interpreter will invoke constructor to create a fresh instance using the new operator. Definition of the constructor is optional. class Animal {}/* ww w . de m o 2 s . c o m */ class Person { constructor() { console.log('person ctor'); } } class MyClass { constructor() { this.color = 'orange'; } } let a = new Animal(); let p = new Person(); // person constructor let v = new MyClass(); console.log(v.color); // orange

Parameters

Parameters provided when instantiating the class are used as parameters to the constructor function. If you do not require the use of parameters, empty parentheses following the class name are optional: class Person { constructor(name) { console.log(arguments.length); this.name = name || null; } } let p1 = new Person; // 0 console.log(p1.name); // null let p2 = new Person(); // 0 console.log(p2.name); // null let p3 = new Person('CSS'); // 1 console.log(p3.name); // CSS By default, the constructor will return the this object after execution. If a different object is returned, the returned object will not be associated with the class via instanceof because its prototype pointer was never modified. class Person {// ww w . de m o 2 s. c o m constructor(override) { this.foo = 'foo'; if (override) { return { bar: 'bar' }; } } } let p1 = new Person(), p2 = new Person(true); console.log(p1); // Person{ foo: 'foo' } console.log(p1 instanceof Person); // true console.log(p2); // { bar: 'bar' } console.log(p2 instanceof Person); // false The use of the new operator with class constructors is mandatory. function Person() {} class Animal {} // Constructs instance using window as 'this' let p = Person(); let a = Animal(); // TypeError: class constructor Animal cannot be invoked without 'new'

Class Instance Members

The class definition syntax allows you to neatly define members that should exist on an object instance, members that should exist on the object prototype, and members that should exist on the class itself.

Instance Members

Each time new <classname> is invoked, the constructor function will execute. Inside this function, you are able to populate the instance with properties. Each instance is assigned unique member objects, meaning nothing is shared on the prototype: class Person {// w w w . d e m o 2 s . c o m constructor() { // define a string with object wrapper // as to check object equality between instances below this.name = new String('CSS'); this.sayName = () => console.log(this.name); this.nicknames = ['CSS2', 'CSS3'] } } let p1 = new Person(), p2 = new Person(); p1.sayName(); //[String: 'CSS'] p2.sayName(); //[String: 'CSS'] console.log(p1.name === p2.name); // false console.log(p1.sayName === p2.sayName); // false console.log(p1.nicknames === p2.nicknames); // false p1.name = p1.nicknames[0]; p2.name = p2.nicknames[1]; p1.sayName(); p2.sayName(); Output:

Class Prototype Methods and Accessors

To allow for sharing of methods between instances, the class definition syntax allows for definition of methods on the prototype object inside the class body. class Person { constructor() { // Everything added to 'this' will // exist on each individual instance this.locate = () => console.log('instance'); } // Everything defined in the class body is // defined on the class prototype object locate() { console.log('prototype'); } } let p = new Person(); p.locate(); // instance Person.prototype.locate(); // prototype Methods can be defined in either location, but member data such as primitives and objects cannot be added to the prototype inside the class body: class Person { name: 'CSS' } // Uncaught SyntaxError: Unexpected token : Class methods behave identically to object properties, meaning they can be keyed with strings, symbols, or computed values: const symbolKey = Symbol('symbolKey'); class Person {// w w w . d e m o 2 s . c o m stringKey() { console.log('invoked stringKey'); } [symbolKey]() { console.log('invoked symbolKey'); } ['computed' + 'Key']() { console.log('invoked computedKey'); } } let p = new Person(); p.stringKey(); // invoked stringKey p[symbolKey](); // invoked symbolKey p.computedKey(); // invoked computedKey

Class Getter and Setter

Class definitions also support getters and setter accessors. The syntax and behavior is identical to that of regular objects: class Person { set name(newName) { this.name_ = newName; } get name() { return this.name_; } } let p = new Person(); p.name = 'CSS'; console.log(p.name); // CSS

Static Class Methods and Accessors

Using class keyword we can define methods on the class itself in Javascript. A static class member is designated using the static keyword as a prefix inside the class definition. Inside static members, this refers to the class itself. All other conventions are identical to prototype members: class Person {// w w w . d e m o 2 s . c o m constructor() { // Everything added to 'this' will // exist on each individual instance this.locate = () => console.log('instance', this); } // Defined on the class prototype object locate() { console.log('prototype', this); } // Defined on the class static locate() { console.log('class', this); } } let p = new Person(); p.locate(); // instance, Person {} Person.prototype.locate(); // prototype, {constructor:...} Person.locate(); // class, class Person {} The static class methods can be used as instance factories: class Person { constructor(age) { this.age_ = age; } sayAge() { console.log(this.age_); } static create() { // Create and return a person instance with a random age return new Person(Math.floor(Math.random()*100)); } } console.log(Person.create()); // Person { age_:... }

Iterator and Generator Methods

The class definition syntax allows for definition of generator methods on both the prototype and the class itself: class Person {// w w w .d e m o 2 s . c om // define generator on prototype *createNicknameIterator() { yield 'CSS1'; yield 'CSS2'; yield 'CSS3'; } // define generator on class static *createJobIterator() { yield 'Style'; yield 'Color'; yield 'Layout'; } } let jobIter = Person.createJobIterator(); console.log(jobIter.next().value); console.log(jobIter.next().value); console.log(jobIter.next().value); let p = new Person(); let nicknameIter = p.createNicknameIterator(); console.log(nicknameIter.next().value); console.log(nicknameIter.next().value); console.log(nicknameIter.next().value); Output: Because generator methods are supported, we can make a class instance iterable by adding a default iterator: class Person { constructor() { this.nicknames = ['CSS1', 'CSS2', 'CSS3']; } *[Symbol.iterator]() { yield *this.nicknames.entries(); } } let p = new Person(); for (let [idx, nickname] of p) { console.log(nickname); } Output: Alternately, to merely return the iterator instance: class Person { constructor() { this.nicknames = ['CSS1', 'CSS2', 'CSS3']; } [Symbol.iterator]() { return this.nicknames.entries(); } } let p = new Person(); for (let [idx, nickname] of p) { console.log(nickname); } Output:

class Inheritance

The ECMAScript 6 has native support for a class inheritance. Although it makes use of a new syntax, class inheritance still uses the prototype chain under the hood.

Inheritance Basics

ES6 classes support a single inheritance format. Using the extends keyword, you are able to inherit from anything that has a [[Construct]] property and a prototype. For the most part, this means inheriting from another class, but this also allows for backwards compatibility with function constructors: Inherit from class class Vehicle {} class Bus extends Vehicle {} let b = new Bus(); console.log(b instanceof Bus); // true console.log(b instanceof Vehicle); // true Inherit from function constructor function Person() {} class Engineer extends Person {} let e = new Engineer(); console.log(e instanceof Engineer); // true console.log(e instanceof Person); // true Both class and prototype methods are carried down to the derived class. The value of this reflects the class or instance that is invoking the method: class Vehicle {/* w w w. d e m o2 s . c o m*/ identifyPrototype(id) { console.log(id, this); } static identifyClass(id) { console.log(id, this); } } class Bus extends Vehicle {} let v = new Vehicle(); let b = new Bus(); b.identifyPrototype('bus'); // bus, Bus {} v.identifyPrototype('vehicle'); // vehicle, Vehicle {} Bus.identifyClass('bus'); // bus, class Bus {} Vehicle.identifyClass('vehicle'); // vehicle, class Vehicle {} The extends keyword is valid inside class expressions, so let Bar = class extends Foo {} is perfectly valid syntax.

Call constructor from super class via super()

Derived class methods have a reference to their prototype via the super keyword. This is only available for derived classes, and only available inside the constructor or inside static methods.

super()

super is used inside the constructor to control when to invoke the parent class constructor. class Vehicle { constructor() { this.hasEngine = true; } } class Bus extends Vehicle { constructor() { // Cannot reference 'this' before super(), will throw ReferenceError super(); // same as super.constructor() console.log(this instanceof Vehicle); // true console.log(this); // Bus { hasEngine: true } } } new Bus();

super.static

super can be used inside static methods to invoke static methods defined on the inherited class: class Vehicle { static identify() { console.log('vehicle'); } } class Bus extends Vehicle { static identify() { super.identify(); } } Bus.identify(); // vehicle ES6 gives the constructor and static methods a reference to the internal [[HomeObject]], which points to the object where the method is defined. This pointer is assigned automatically and is only accessible internally inside the JavaScript engine. super will always be defined as the prototype of [[HomeObject]].

super limitation in class inheritance

Limitation 1

super can only be used in a derived class constructor or static method. class Vehicle { constructor() { super(); // SyntaxError: 'super' keyword unexpected } }

Limitation 2

The super keyword cannot be referenced by itself; it must be either invoked as a constructor or used to reference a static method. class Vehicle {} class Bus extends Vehicle { constructor() { console.log(super); // SyntaxError: 'super' keyword unexpected here } }

Limitation 3

Calling super() will invoke the parent class constructor and assign the resulting instance to this. class Vehicle {} class Bus extends Vehicle { constructor() { super(); console.log(this instanceof Vehicle); } } new Bus(); // true

Limitation 4

super() behaves like a constructor function; you must manually pass arguments to it to pass them to the parent constructor. class Vehicle { constructor(engineModel) { this.engineModel = engineModel; } } class Bus extends Vehicle { constructor(engineModel) { super(engineModel); } } console.log(new Bus('X3')); // Bus { engineModel: 'X3' } If you decline to define a constructor function, super() will be invoked and all arguments passed to the derived class constructor. class Vehicle { constructor(engineModel) { this.engineModel = engineModel; } } class Bus extends Vehicle {} console.log(new Bus('1234')); // Bus { engineModel: '1234' }

Limitation 5

You cannot reference this inside the constructor before invoking super(). class Vehicle {} class Bus extends Vehicle { constructor() { console.log(this); } } new Bus(); // ReferenceError: Must call super constructor in derived class // before accessing 'this' or returning from derived constructor

Note

If a class is derived from a parent class and you explicitly define a constructor, you must either invoke super() or return an object from the constructor. class Vehicle {} class Car extends Vehicle {} class Bus extends Vehicle { constructor() { super(); } } class Van extends Vehicle { constructor() { return {}; } } console.log(new Car()); // Car {} console.log(new Bus()); // Bus {} console.log(new Van()); // {}

Abstract Base Classes

You may find a need to define a class that should be inherited from but not directly instantiated. Although ECMAScript does not explicitly support this, we can implement using new.target. You can prevent direct instantiation by checking that new.target is never the abstract base class: // Abstract base class class Vehicle { constructor() { console.log(new.target); if (new.target === Vehicle) { throw new Error('Vehicle cannot be directly instantiated'); } } } // Derived class class Bus extends Vehicle {} new Bus(); // class Bus {} new Vehicle(); // class Vehicle {} // Error: Vehicle cannot be directly instantiated We can require that a method be defined on a derived class by checking for it in the abstract base class constructor. Because prototype methods exist before the constructor is invoked, you can check for them on the this keyword: // Abstract base class class Vehicle {// w w w . d e m o2 s . c o m constructor() { if (new.target === Vehicle) { throw new Error('Vehicle cannot be directly instantiated'); } if (!this.foo) { throw new Error('Inheriting class must define foo()'); } console.log('success!'); } } // Derived class class Bus extends Vehicle { foo() {} } // Derived class class Van extends Vehicle {} new Bus(); // success! new Van(); // Error: Inheriting class must define foo()

Inheriting from Built-in Types

ES6 classes offer interoperability with existing built-in reference types, allowing you to extend them: class SuperArray extends Array { printAll() { for (let i = this.length - 1; i > 0; i--) { console.log(this[i]); } } } let a = new SuperArray(1, 2, 3, 4, 5); console.log(a instanceof Array); // true console.log(a instanceof SuperArray); // true console.log(a); // [1, 2, 3, 4, 5] a.printAll(); Output: Some built-in types have methods defined in which a new object instance is returned. By default, the type of this object instance will match the type of the original instance: class SuperArray extends Array {} let a1 = new SuperArray(1, 2, 3, 4, 5); let a2 = a1.filter(x => !!(x%2)) console.log(a1); // [1, 2, 3, 4, 5] console.log(a2); // [1, 3, 5] console.log(a1 instanceof SuperArray); // true console.log(a2 instanceof SuperArray); // true If you wish to override this, you can override the Symbol.species accessor, which is used to determine the class to be used to create the returned instance: class SuperArray extends Array { static get [Symbol.species]() { return Array; } } let a1 = new SuperArray(1, 2, 3, 4, 5); let a2 = a1.filter(x => !!(x%2)) console.log(a1); // [1, 2, 3, 4, 5] console.log(a2); // [1, 3, 5] console.log(a1 instanceof SuperArray); // true console.log(a2 instanceof SuperArray); // false

Example for Class

Here are some examples for how to define class in Javascript: Create iterable class Create a class that subclasses or inherits from-another class Create A Complex Number Class Add Static Methods to Class

Map Type

ECMAScript 6 Map is a new collection type that introduces true key/value behavior into the language. An empty Map is instantiated with the new keyword: const m = new Map(); To populate the Map when it is initialized, the constructor optionally accepts an iterable object, expecting it to contain key/value pair arrays. Each pair in the iterable argument will be inserted into the newly created Map in the order in which they are iterated: // Initialize map with nested arrays const m1 = new Map([/* w w w . d e m o 2 s . c o m */ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"] ]); console.log(m1.size); // 3 // Initialize map with custom-defined iterator const m2 = new Map({ [Symbol.iterator]: function*() { yield ["key1", "val1"]; yield ["key2", "val2"]; yield ["key3", "val3"]; } }); console.log(m2.size); // 3 // Map expects values to be key/value whether they are provided or not const m3 = new Map([[]]); console.log(m3.has(undefined)); // true console.log(m3.get(undefined)); // undefined Key/value pairs can be added after initialization with set(), queried with get() and has(), counted with the size property, and removed with delete()and clear(): const m = new Map();/*w ww . d e m o2 s. c o m */ console.log(m.has("firstName")); // false console.log(m.get("firstName ")); // undefined console.log(m.size); // 0 m.set("firstName", "HTML") .set("lastName", "Frisbie"); console.log(m.has("firstName")); // true console.log(m.get("firstName")); // HTML console.log(m.size); // 2 m.delete("firstName"); // deletes only this key/value pair console.log(m.has("firstName")); // false console.log(m.has("lastName")); // true console.log(m.size); // 1 m.clear(); // destroys all key/value pairs in this Map instance console.log(m.has("firstName")); // false console.log(m.has("lastName")); // false console.log(m.size); // 0 The set() method returns the Map instance, so it is possible to chain multiple set operations together, including on the initial declaration: const m = new Map().set("key1", "val1"); m.set("key2", "val2") .set("key3", "val3"); console.log(m.size); // 3 A Map can use any JavaScript data type as a key. It uses the SameValueZero comparison operation and is comparable to using strict object equivalence to check for a key match. There is no restriction on what is contained in the value. const m = new Map(); const functionKey = function() {}; const symbolKey = Symbol(); const objectKey = new Object(); m.set(functionKey, "functionValue"); m.set(symbolKey, "symbolValue"); m.set(objectKey, "objectValue"); console.log(m.get(functionKey)); // functionValue console.log(m.get(symbolKey)); // symbolValue console.log(m.get(objectKey)); // objectValue // SameValueZero checks mean separate instances will not collide console.log(m.get(function() {})); // undefined The objects and other collection types used for keys and values remain unchanged inside a Map when their contents or properties are altered: const m = new Map(); const objKey = {}, objVal = {}, arrKey = [], arrVal = []; m.set(objKey, objVal); m.set(arrKey, arrVal); objKey.foo = "foo"; objVal.bar = "bar"; arrKey.push("foo"); arrVal.push("bar"); console.log(m.get(objKey)); // {bar: "bar"} console.log(m.get(arrKey)); // ["bar"] The use of the SameValueZero operation may introduce unexpected collisions: const m = new Map(); const a = 0/"", // NaN b = 0/"", // NaN pz = +0, nz = -0; console.log(a === b); // false console.log(pz === nz); // true m.set(a, "foo"); m.set(pz, "bar"); console.log(m.get(b)); // foo console.log(m.get(nz)); // bar

Map Order and Iteration

Map instances maintain the order of key/value insertion and allow you to perform iterative operations following insertion order. A Map instance can provide an Iterator that contains array pairs in the form of [key, value] in insertion order. This iterator can be retrieved using entries(), or the Symbol.iterator property, which references entries(): const m = new Map([// w w w . d e m o 2 s . c om ["key1", "val1"], ["key2", "val2"], ["key3", "val3"] ]); console.log(m.entries === m[Symbol.iterator]); // true for (let pair of m.entries()) { console.log(pair); } // [key1,val1] // [key2,val2] // [key3,val3] for (let pair of m[Symbol.iterator]()) { console.log(pair); } // [key1,val1] // [key2,val2] // [key3,val3] Because entries() is the default iterator, the spread operator can be used to concisely convert a Map into an array: const m = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"] ]); console.log([...m]); Output: To use a callback convention instead of an iterator, forEach(callback, opt_this_Arg) invokes the callback for each key/value pair. It optionally accepts a second argument, which will override the value of this inside each callback invocation. const m = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"] ]); m.forEach((val, key) => console.log(`${key} -> ${val}`)); Output: keys() and values() return an iterator that contains all keys or all values in the Map in insertion order: const m = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"] ]); for (let key of m.keys()) { console.log(key); } // key1 // key2 // key3 for (let key of m.values()) { console.log(key); } // value1 // value2 // value3 Keys and values exposed inside an iterator are mutable, but the references inside the Map cannot be altered. However, this does not restrict changing properties inside a key or value object. Doing so will not alter their identity with respect to the Map instance: const m1 = new Map([// ww w . de m o 2 s . c o m ["key1", "val1"] ]); // String primitive as key is unaltered for (let key of m.keys()) { key = "newKey"; console.log(key); // newKey console.log(m.get("key1")); // val1 } const keyObj = {id: 1}; const m = new Map([ [keyObj, "val1"] ]); // Key object property is altered, but the object still refers // to the same value inside the map for (let key of m.keys()) { key.id = "newKey"; console.log(key); // {id: "newKey"} console.log(m.get(keyObj)); // val1 } console.log(keyObj); // {id: "newKey"}

WeakMap Type

An empty WeakMap is instantiated with the new keyword: const wm = new WeakMap(); Keys in a WeakMap can only be of type or inherit from Object-all other attempts to set a key with a non-object will throw a TypeError. There are no restrictions on the type of the value. To populate the WeakMap when it is initialized, the constructor optionally accepts an iterable object, expecting it to contain valid key/value pair arrays. Each pair in the iterable argument will be inserted into the newly created WeakMap in the order in which they are iterated: const key1 = {id: 1},/* w w w . d e mo 2 s . c o m */ key2 = {id: 2}, key3 = {id: 3}; // Initialize WeakMap with nested arrays const wm1 = new WeakMap([ [key1, "val1"], [key2, "val2"], [key3, "val3"] ]); console.log(wm.get(key1)); // val2 console.log(wm.get(key2)); // val2 console.log(wm.get(key3)); // val3 // Initialization is all-or-nothing, one bad key will // throw an error and abort the initialization const wm2 = new WeakMap([ [key1, "val1"], ["BADKEY", "val2"], [key3, "val3"] ]); // TypeError: Invalid value used as WeakMap key typeof wm2; // ReferenceError: wm2 is not defined // Primitives can still be used with an object wrapper const stringKey = new String("key1"); const wm3 = new WeakMap([ stringKey, "val1" ]); console.log(wm3.get(stringKey)); // "val1" Key/value pairs can be added after initialization with set(), queried with get() and has(), and removed with delete(): const wm = new WeakMap(); const key1 = {id: 1}, key2 = {id: 2}; console.log(wm.has(key1)); // false console.log(wm.get(key1)); // undefined wm.set(key1, "HTML") .set(key2, "Frisbie"); console.log(wm.has(key1)); // true console.log(wm.get(key1)); // HTML wm.delete(key1); // deletes only this key/value pair console.log(wm.has(key1)); // false console.log(wm.has(key2)); // true The set() method returns the WeakMap instance, so it is possible to chain multiple set operations together, including on the initial declaration: const key1 = {id: 1}, key2 = {id: 2}, key3 = {id: 3}; const wm = new WeakMap().set(key1, "val1"); wm.set(key2, "val2") .set(key3, "val3"); console.log(wm.get(key1)); // val1 console.log(wm.get(key2)); // val2 console.log(wm.get(key3)); // val3

WeakMap Weak Keys

The keys in a WeakMap are "weakly held," meaning they are not counted as formal references that would otherwise prevent garbage collection. The WeakMap value reference is not weakly held. As long as the key exists, the key/value pair will remain in the map and count as a reference to the value-thereby preventing it from being garbage collected. Consider the following example: const wm = new WeakMap(); wm.set({}, "val"); Inside set(), a fresh object is initialized and used as a key to a dummy string. Because there are no other references to this object, as soon as this line is finished executing, the object key will be free for garbage collection. When this occurs, the key/value pair will disappear from the WeakMap, and it will be empty. In this example, because there are no other references to the value, this key/value destruction will also mean that the value is eligible for garbage collection. Consider a slightly different example: const wm = new WeakMap(); const container = { key: {} }; wm.set(container.key, "val"); function removeReference() container.key = null; } Here, the container object maintains a reference to the key in the WeakMap, so the object is ineligible for garbage collection. However, as soon as removeReference() is invoked, the last strong reference to the key object will be destroyed, and garbage collection will eventually wipe out the key/value pair.

Non-Iterable Keys

Because key/value pairs in a WeakMap can be destroyed at any time, it does not make sense to offer the ability to iterate through the key/value pairs. clear() method is not part of the WeakMap API. Because iteration is not possible, it is also not possible to retrieve a value from a WeakMap instance unless you have a reference to the key object. Even if code has access to the WeakMap instance, there is no way to inspect its contents. The reason that WeakMap instances restrict keys to only objects is to preserve the convention that values can only be retrieved from a WeakMap with a reference to the key object.

Map Examples

Examples for Map Calculate Character Frequency Histograms Examples for WeakMap Use WeakMap to create Private Variables Javascript Map a dictionary by value Javascript Map Getting key of sorted properties Javascript Map manipulation get and set

Set Type Introduction

ECMAScript 6 Set is a new collection type that introduces set behavior into the language. An empty Set is instantiated with the new keyword: const m = new Set(); If you wish to populate the set when it is initialized, the constructor optionally accepts an iterable object containing elements to be added into the newly created Set instance. // Initialize set with array const s1 = new Set(["val1", "val2", "val3"]); console.log(s1.size); // 3 // Initialize set with custom-defined iterator const s2 = new Set({ [Symbol.iterator]: function*() { yield "val1"; yield "val2"; yield "val3"; } }); console.log(s2.size); // 3 Values can be added after initialization with add(), queried with has(), counted with the size property, and removed with delete()and clear(): const s = new Set();/*w w w . d e m o 2 s . c om */ console.log(s.has("HTML")); // false console.log(s.size); // 0 s.add("HTML") .add("CSS"); console.log(s.has("HTML")); // true console.log(s.size); // 2 s.delete("HTML"); console.log(s.has("HTML")); // false console.log(s.has("CSS")); // true console.log(s.size); // 1 s.clear(); // destroys all values in this Set instance console.log(s.has("HTML")); // false console.log(s.has("CSS")); // false console.log(s.size); // 0 The add() method returns the Set instance, so it is possible to chain multiple operations together, including on the initial declaration: const s = new Set().add("val1"); s.set("val2") .set("val3"); console.log(s.size); // 3 A Set can contain any JavaScript data type as a value. It uses the SameValueZero comparison operation and is comparable to using strict object equivalence to check for a value match. There is no restriction on what is contained in the value. const s = new Set(); const functionVal = function() {}; const symbolVal = Symbol(); const objectVal = new Object(); s.add(functionVal); s.add(symbolVal); s.add(objectVal); console.log(s.has(functionVal)); // true console.log(s.has(symbolVal)); // true console.log(s.has(objectVal)); // true // SameValueZero checks mean separate instances will not collide console.log(s.has(function() {})); // false The objects and other "collection" types used for values remain unchanged inside a set when their contents or properties are altered: const s = new Set(); const objVal = {}, arrVal = []; s.add(objVal); .add(arrVal); objVal.bar = "bar"; arrVal.push("bar"); console.log(s.has(objVal)); // true console.log(s.has(arrVal)); // true The add() and delete() operations are idempotent. delete() returns a Boolean indicating if that value was present in the set. const s = new Set(); s.add('foo'); console.log(s.size); // 1 s.add('foo'); console.log(s.size); // 1 // Value was present in the set console.log(s.delete('foo')); // true // Value was not present in the set console.log(s.delete('foo')); // false

Set Order and Iteration

Sets maintain the order of value insertion and allow you to perform iterative operations following insertion order. A Set instance can provide an Iterator that contains the set contents in insertion order. This iterator can be retrieved using values(), its alias keys(), or the Symbol. const s = new Set(["val1", "val2", "val3"]); console.log(s.values === s[Symbol.iterator]); // true console.log(s.keys === s[Symbol.iterator]); // true for (let value of s.values()) { console.log(value); } // val1 // val2 // val3 for (let value of s[Symbol.iterator]()) { console.log(value); } // val1 // val2 // val3 Because values() is the default iterator, the spread operator can be used to concisely convert a set into an array: const s = new Set(["val1", "val2", "val3"]); console.log([...s]); // [val1,val2,val3] entries() returns an iterator that contains a two-element array containing a duplicate of all values in the Set in insertion order: const s = new Set(["val1", "val2", "val3"]); for (let pair of s.entries()) { console.log(pair); } Output: To use a callback convention instead of an iterator, forEach(callback, opt_this_Arg) invokes the callback for each value. It optionally accepts a second argument, which will override the value of this inside each callback invocation. const s = new Set(["val1", "val2", "val3"]); s.forEach((val, dupVal) => console.log(`${val} -> ${dupVal}`)); Output: Changing properties of values in a Set does not alter the value's identity with respect to the Set instance: const s1 = new Set(["val1"]); // String primitive as value is unaltered for (let value of m.values()) { value = "newVal"; console.log(value); // newVal console.log(s.has("val1")); // true } const valObj = {id: 1};/* w w w . de m o2 s . c o m */ const s2 = new Set([valObj]); // Value object property is altered, but the object still exists // inside the set for (let value of s.values()) { value.id = "newVal"; console.log(value); // {id: "newVal"} console.log(s.has(valObj)); // true } console.log(valObj); // {id: "newKey"}

Defining Formal Set Operations

The following code extends Javascript Set to support set operations on Set. It supports the following operations: Union Intersect Difference Symmetric Difference Cartesian Product class XSet extends Set {/*w w w . d e m o 2s . c o m*/ union(...sets) { return XSet.union(this, ...sets) } intersection(...sets) { return XSet.intersection(this, ...sets); } difference(set) { return XSet.difference(this, set); } symmetricDifference(set) { return XSet.symmetricDifference(this, set); } cartesianProduct(set) { return XSet.cartesianProduct(this, set); } powerSet() { return XSet.powerSet(this); } // Returns union of two or more sets. static union(a, ...bSets) { const unionSet = new XSet(a); for (const b of bSets) { for (const bValue of b) { unionSet.add(bValue); } } return unionSet; } // Returns intersection of two or more sets. static intersection(a, ...bSets) { const intersectionSet = new XSet(a); for (const aValue of intersectionSet) { for (const b of bSets) { if (!b.has(aValue)) { intersectionSet.delete(aValue); } } } return intersectionSet; } // Returns difference of exactly two sets. static difference(a, b) { const differenceSet = new XSet(a); for (const bValue of b) { if (a.has(bValue)) { differenceSet.delete(bValue); } } return differenceSet; } // Returns symmetric difference of exactly two sets. static symmetricDifference(a, b) { // By definition, the symmetric difference can be expressed as // (a union b) - (a intersection b) return a.union(b).difference(a.intersection(b)); } // Returns cartesian product (as array pairs) of exactly two sets. // Must return set of arrays since cartesian product may contain // pairs of identical values. static cartesianProduct(a, b) { const cartesianProductSet = new XSet(); for (const aValue of a) { for (const bValue of b) { cartesianProductSet.add([aValue, bValue]); } } return cartesianProductSet; } // Returns power set of exactly one set. static powerSet(a) { const powerSet = new XSet().add(new XSet()); for (const aValue of a) { for (const set of new XSet(powerSet)) { powerSet.add(new XSet(set).add(aValue)); } } return powerSet; } }

WeakSet Type

ECMAScript 6 WeakSet is a new collection type that introduces set behavior into the language. WeakSet type API is a strict subset of Set. The "weak" designation describes how JavaScript's garbage collector treats values in a weak set. An empty WeakSet instance is instantiated with the new keyword: const ws = new WeakSet(); Values in a WeakSet can only be of type or inherit from Object-all other attempts to set a value with a non-object will throw a TypeError. If you wish to populate the WeakSet when it is initialized, the constructor optionally accepts an iterable object, expecting it to contain valid values. Each value in the iterable argument will be inserted into the newly created WeakSet in the order in which they are iterated: const val1 = {id: 1}, val2 = {id: 2}, val3 = {id: 3}; // Initialize WeakSet with nested arrays const ws1 = new WeakSet([val1, val2, val3]); console.log(ws1.has(val1)); // true console.log(ws1.has(val2)); // true console.log(ws1.has(val3)); // true Initialization is all-or-nothing, a single bad value will throw an error and abort the initialization const val1 = {id: 1}, val2 = {id: 2}, val3 = {id: 3}; const ws2 = new WeakSet([val1, "BADVAL", val3]); // TypeError: Invalid value used in WeakSet typeof ws2; // ReferenceError: ws2 is not defined // Primitives can still be used with an object wrapper const stringVal = new String("val1"); const ws3 = new WeakSet([stringVal]); console.log(ws3.has(stringVal)); // true Values can be added after initialization with add(), queried with has(), and removed with delete(): const ws = new WeakSet(); const val1 = {id: 1}, val2 = {id: 2}; console.log(ws.has(val1)); // false ws.add(val1) .add(val2); console.log(ws.has(val1)); // true console.log(ws.has(val2)); // true ws.delete(val1); // deletes only this value console.log(ws.has(val1)); // false console.log(ws.has(val2)); // true The add() method returns the WeakSet instance, so it is possible to chain multiple add operations together, including on the initial declaration: const val1 = {id: 1}, val2 = {id: 2}, val3 = {id: 3}; const ws = new WeakSet().add(val1); ws.add(val2) .add(val3); console.log(ws.has(val1)); // true console.log(ws.has(val2)); // true console.log(ws.has(val3)); // true

WeakSet Weak Values

The values in a WeakSet are "weakly held," meaning they are not counted as formal references, which would otherwise prevent garbage collection. Consider the following example: const ws = new WeakSet(); ws.add({}); Inside add(), a fresh object is initialized and used as a value. Because there are no other references to this object, as soon as this line is finished executing, the object value will be free for garbage collection. When this occurs, the value will disappear from the WeakSet, and it will be empty. Consider a slightly different example: const ws = new WeakSet(); const container = { val: {} }; ws.add(container.val); function removeReference() container.val = null; } Here, the container object maintains a reference to the value in the WeakSet instance, so the object is ineligible for garbage collection. However, as soon as removeReference() is invoked, the last strong reference to the value object will be destroyed, and garbage collection will eventually wipe out the value.

WeakSet Non-Iterable Values

Because values in a WeakSet can be destroyed at any time, we cannot iterate through the values. clear() method is not part of the WeakSet API. We cannot retrieve a value from a WeakSet instance unless you have a reference to the value object. Even if code has access to the WeakSet instance, there is no way to inspect its contents. WeakSet instances restrict values to only objects in order to preserve the convention that values can only be retrieved from a WeakSet with a reference to the value object.

Typed Array Introduction

ECMAScript 6 typed array is designed for efficiently passing binary data to native libraries. Typed Array type refers to a collection of specialized arrays that contain numeric types.

ArrayBuffers

The ArrayBuffer is the fundamental unit referred to by all typed arrays and views. For example, Float32Array is one type of "view" that allows the JavaScript runtime to access a block of allocated memory called an ArrayBuffer. The TypedArrayBuffer is a variant of the ArrayBuffer that can be passed between execution contexts without performing a copy. ArrayBuffer is a normal JavaScript constructor that can be used to allocate a specific number of bytes in memory. const buf = new ArrayBuffer(16); // Allocates 16 bytes of memory console.log(buf.byteLength); // 16 An ArrayBuffer can never be resized once it is created. However, you are able to copy all or part of an existing ArrayBuffer into a new instance using slice(): const buf1 = new ArrayBuffer(16); const buf2 = buf1.slice(4, 12); console.log(buf2.byteLength); // 8

Note

ArrayBuffer is similar to the C++ malloc(). If ArrayBuffer allocation fails, it throws an error. ArrayBuffer allocation cannot exceed Number.MAX_SAFE_INTEGER (2 ^ 53) bytes. Declaring an ArrayBuffer initializes all the bits to 0s. Heap memory allocated by ArrayBuffer is garbage collected. The contents of an ArrayBuffer cannot be read or written with only a reference to the buffer instance. To read or write data inside, you must do so with a view. There are different types of views, but they all refer to binary data stored in an ArrayBuffer.

Typed Array DataView

The first type of view that allows you to read and write an ArrayBuffer is the DataView. This view is designed for file I/O and network I/O; the API allows for a high degree of control when manipulating buffer data, but it offers reduced performance compared to different view types as a result. A DataView does not assume anything about the buffer contents and is not iterable. A DataView must be created to read from and write to an ArrayBuffer that already exists. It can use the whole buffer or only part of it, and it maintains a reference to the buffer instance and where in the buffer the view begins. const buf = new ArrayBuffer(16); // DataView default to use the entire ArrayBuffer const fullDataView = new DataView(buf); console.log(fullDataView.byteOffset); // 0 console.log(fullDataView.byteLength); // 16 console.log(fullDataView.buffer === buf); // true Constructor takes an optional byte offset and byte length. byteOffset=0 begins the view at the start of the buffer and byteLength=8 restricts the view to the first 8 bytes. const firstHalfDataView = new DataView(buf, 0, 8); console.log(firstHalfDataView.byteOffset); // 0 console.log(firstHalfDataView.byteLength); // 8 console.log(firstHalfDataView.buffer === buf); // true DataView will use the remainder of the buffer unless specified. byteOffset=8 begins the view at the 9th byte of the buffer and byteLength default is the remainder of the buffer. const secondHalfDataView = new DataView(buf, 8); console.log(secondHalfDataView.byteOffset); // 8 console.log(secondHalfDataView.byteLength); // 8 console.log(secondHalfDataView.buffer === buf); // true To read from and write to the buffer through a DataView, you will require the use of several components: The byte offset at which you wish to read or write. This can be thought of as a sort of address within the DataView. The ElementType the DataView should use for conversion between the Number type in the JavaScript runtime and the binary format in the buffer. The endianness of the value in memory defaults to big-endian.

DataView Element Type

The DataView makes no assumptions about what data type is stored inside the buffer. We need to specify an ElementType when reading or writing, and the DataView will perform the conversion to execute that read or write. ECMAScript 6 supports eight different ElementTypes:
Elementtype Bytes Description C Equivalent Range Of Values
Int8 1 8-bit signed integer signed char -128 to 127
Uint8 1 8-bit
unsigned integer
unsigned char 0 to 255
Int16 2 16-bit signed integer short -32768 to 32767
Uint16 2 16-bit
unsigned integer
unsigned short 0 to 65535
Int32 4 32-bit signed integer int -2,147,483,648 to
2,147,483,647
Uint32 4 32-bit
unsigned integer
unsigned int 0 to 4,294,967,295
Float32 4 32-bit IEEE-754
floating point
float -3.4E+38 to +3.4E+38
Float64 8 64-bit IEEE-754
floating point
double -1.7E+308
to +1.7E+308
The DataView exposes get and set methods for each of these types, which use a byteOffset to address into the buffer for reading and writing values. Types can be used interchangeably, as demonstrated here: // Allocate two bytes of memory and declare a DataView const buf = new ArrayBuffer(2);/*w w w . d e m o 2 s. c o m */ const view = new DataView(buf); // the entire buffer is indeed all zeroes // Check the first and second byte console.log(view.getInt8(0)); // 0 console.log(view.getInt8(1)); // 0 // Check the entire buffer console.log(view.getInt16(0)); // 0 // Set the entire buffer to ones // 255 in binary is 11111111 (2^8 - 1) view.setUint8(0, 255); // DataView will automatically cast values // to the designated ElementType // 255 in hex is 0xFF view.setUint8(1, 0xFF); // The buffer is now all ones, which when read as a // two's complement signed integer should be -1 console.log(view.getInt16(0)); // -1

Big-Endian and Little-Endian

"Endianness" refers to the convention of byte ordering maintained by a computing system. In Javascript DataViews, there are only two conventions supported: big-endian and little-endian. Big-endian, also referred to as "network byte order," means that the most significant byte is held in the first byte, and the least significant byte is held in the last byte. Little-endian means the least significant byte is held in the first byte, and the most significant byte is held in the last byte. The native endianness of the system executing the JavaScript runtime will determine how it reads and writes bytes, but a DataView does not obey this convention. A DataView will follow the endianness you specify. All DataView API methods default to the big-endian convention but accept an optional final Boolean argument that allows you to enable the little-endian convention by setting it to true. // Allocate two bytes of memory and declare a DataView const buf = new ArrayBuffer(2);/* w w w. d e m o 2 s . c o m */ const view = new DataView(buf); // Fill the buffer so that the first bit and last bit are 1 view.setUint8(0, 0x80); // Sets leftmost bit to 1 view.setUint8(1, 0x01); // Sets rightmost bit to 1 // Buffer contents, spaced for readability: // 0x8 0x0 0x0 0x1 // 1000 0000 0000 0001 // Read a big-endian Uint16 // 0x80 is the high byte, 0x01 is the low byte // 0x8001 = 2^15 + 2^0 = 32768 + 1 = 32769 console.log(view.getUint16(0)); // 32769 // Read a little-endian Uint16 // 0x01 is the high byte, 0x80 is the low byte // 0x0180 = 2^8 + 2^7 = 256 + 128 = 384 console.log(view.getUint16(0, true)); // 384 // Write a big-endian Uint16 view.setUint16(0, 0x0004); // Buffer contents, spaced for readability: // 0x0 0x0 0x0 0x4 // 0000 0000 0000 0100 console.log(view.getUint8(0)); // 0 console.log(view.getUint8(1)); // 4 // Write a little-endian Uint16 view.setUint16(0, 0x0002, true); // Buffer contents // 0x0 0x2 0x0 0x0 // 0000 0010 0000 0000 console.log(view.getUint8(0)); // 2 console.log(view.getUint8(1)); // 0

Typed Arrays

Typed arrays are another form of an ArrayBuffer view. A typed array enforces a single ElementType and obeys the system's native endianness. In exchange, it offers a much broader API and better performance. Typed arrays are designed for efficiently exchanging binary data with native libraries like WebGL. Typed arrays can be created to read from an existing buffer, initialized with their own buffer, filled with an iterable, or filled from an existing typed array of any type. They can also be created using <ElementType>.from() and <ElementType>.of(): // Creates a buffer of 12 bytes const buf = new ArrayBuffer(12); // Creates an Int32Array that references this buffer const ints = new Int32Array(buf); // The typed array recognizes it needs 4 bytes per element, // and therefore will have a length of 3 console.log(ints.length); // 3 Creates an Int32Array of length 6 const ints2 = new Int32Array(6); // Each number uses 4 bytes, so the ArrayBuffer is 24 bytes console.log(ints2.length); // 6 // Like DataView, typed arrays have a reference to their associated buffer console.log(ints2.buffer.byteLength); // 24 Creates an Int32Array containing [2, 4, 6, 8] const ints3 = new Int32Array([2, 4, 6, 8]); console.log(ints3.length); // 4 console.log(ints3.buffer.byteLength); // 16 console.log(ints3[2]); // 6 Creates an Int16Array with values copies from ints3 const ints4 = new Int16Array(ints3); // The new typed array allocates its own buffer, and each value // is converted to its new representation at the same index console.log(ints4.length); // 4 console.log(ints4.buffer.byteLength); // 8 console.log(ints4[2]); // 6 Creates an Int16Array from a normal array const ints5 = Int16Array.from([3, 5, 7, 9]); console.log(ints5.length); // 4 console.log(ints5.buffer.byteLength); // 8 console.log(ints5[2]); // 7 Creates a Float32Array from arguments const floats = Float32Array.of(3.14, 2.718, 1.618); console.log(floats.length); // 3 console.log(floats.buffer.byteLength); // 12 console.log(floats[2]); // 1.6180000305175781 Both the constructor and instances expose a BYTES_PER_ELEMENT property that returns the size of each element in that type of array: console.log(Int16Array.BYTES_PER_ELEMENT); // 2 console.log(Int32Array.BYTES_PER_ELEMENT); // 4 const ints = new Int32Array(1), floats = new Float64Array(1); console.log(ints.BYTES_PER_ELEMENT); // 4 console.log(floats.BYTES_PER_ELEMENT); // 8 Unless a typed array is initialized with values, its associated buffer is filled with zeroes: const ints = new Int32Array(4); console.log(ints[0]); // 0 console.log(ints[1]); // 0 console.log(ints[2]); // 0 console.log(ints[3]); // 0

Typed Array Methods

Javascript typed arrays behave like their regular array counterparts would. Typed arrays support the following operators, methods, and properties: [] copyWithin() entries() every() fill() filter() find() findIndex() forEach() indexOf() join() keys() lastIndexOf() length map() reduce() reduceRight() reverse() slice() some() sort() toLocaleString() toString() values() Methods that return a new array will return a new typed array with the same element type: const ints = new Int16Array([1, 2, 3]); const doubleints = ints.map(x => 2*x); console.log(doubleints instanceof Int16Array); // true Typed arrays have a Symbol.iterator defined, meaning that for..of loops and spread operators can also be used: const ints = new Int16Array([1, 2, 3]); for (const int of ints) { console.log(int); } Output:

Merging, Copying, and Changing Typed Arrays

Typed arrays still use array buffers as their storage, and array buffers cannot be resized. Therefore, the following methods are not supported by typed arrays: concat() pop() push() shift() splice() unshift() Javascript typed arrays do offer two new methods that allow you to copy values in and out of arrays quickly: set() and subarray(). set() copies the values from a provided array or typed array into the current typed array at the specified index: // Create an int16 array of length 8 const container = new Int16Array(8); // Copy in typed array into first four values // Offset default to an index of 0 container.set(Int8Array.of(1, 2, 3, 4)); console.log(container); // [1,2,3,4,0,0,0,0] // Copy in normal array into last four values // Offset of 4 means begin inserting at the index 4 container.set([5,6,7,8], 4); console.log(container); // [1,2,3,4,5,6,7,8] // An overflow will throw an error container.set([5,6,7,8], 7); // RangeError subarray() performs the opposite operation of set(), returning a new typed array with values copied out of the original. Providing the start and end indices is optional: const source = Int16Array.of(2, 4, 6, 8); // Copies the entire array into a new array of the same type const fullCopy = source.subarray(); console.log(fullCopy); // [2, 4, 6, 8] // Copy the array from index 2 on const halfCopy = source.subarray(2); console.log(halfCopy); // [6, 8] // Copy the array from index 1 up until 3 const partialCopy = source.subarray(1, 3); console.log(partialCopy); // [4, 6]

Concatenate

Typed arrays don't have a native ability to concatenate. The following code creates a function to append two typed arrays together: // First argument is the type of array that should be returned // Remaining arguments are all the typed arrays that should be concatenated function typedArrayConcat(typedArrayConstructor, ...typedArrays) { // Count the total elements in all arrays const numElements = typedArrays.reduce((x,y) => (x.length || x) + y.length); // Create an array of the provided type with space for all elements const resultArray = new typedArrayConstructor(numElements); // Perform the successive array transfer let currentOffset = 0; typedArrays.map(x => {// w w w . d e mo 2 s. co m resultArray.set(x, currentOffset); currentOffset += x.length; }); return resultArray; } const concatArray = typedArrayConcat(Int32Array, Int8Array.of(1, 2, 3), Int16Array.of(4, 5, 6), Float32Array.of(7, 8, 9)); console.log(concatArray); // [1, 2, 3, 4, 5, 6, 7, 8, 9] console.log(concatArray instanceof Int32Array); // true

Typed Array Underflow and Overflow

Typed arrays will accept only the relevant bits that each index in the array can hold, irrespective of the effect it will have on the actual numerical value. The following demonstrates how underflow and overflow are handled. It creates signed ints array of length 2. Each index holds a 2's complement signed integer which can range from -128 (-1 * 2^7) to 127 (2^7 - 1) const ints = new Int8Array(2); Then we create Unsigned ints array of length 2. Each index holds an unsigned integer which can range from 0 to 255 (2^7 - 1) Overflow bits will not spill into adjacent indices. The index only takes the least significant 8 bits unsignedInts[1] = 256; // 0x100 console.log(unsignedInts); // [0, 0] unsignedInts[1] = 511; // 0x1FF console.log(unsignedInts); // [0, 255] Underflow bits will be converted to their unsigned equivalent. 0xFF is -1 as a 2's complement int (truncated to 8 bits), but is 255 as an unsigned int unsignedInts[1] = -1 // 0xFF (truncated to 8 bits) console.log(unsignedInts); // [0, 255] Overflow in 2's complement occurs transparently. 0x80 is 128 in unsigned int but -128 in 2's complement int ints[1] = 128; // 0x80 console.log(ints); // [0, -128] Underflow in 2's complement occurs transparently. 0xFF is 255 in unsigned int but -1 in 2's complement int ints[1] = 255; // 0xFF console.log(ints); // [0, -1]

Float64Array Introduction

A Float64Array object describes an array-like view of an underlying binary data buffer.

Constructor

This object cannot be instantiated directly. new Float64Array(); new Float64Array(length); new Float64Array(typed_Array); new Float64Array(object); new Float64Array(buffer [, byteOffset [, length]]);

Parameters

Item Description
length When called with a length argument, an internal array buffer is created in memory, of size length multiplied by BYTES_PER_ELEMENT bytes, containing zeros.
typed_Array When called with a typed_Array argument, which can be an object of any of the typed array types (such as Float64Array), the typed_Array gets copied into a new typed array.
object When called with an object argument, a new typed array is created as if by the Float64Array.from() method.
buffer, byteOffset, length When called with a buffer, and optionally a byteOffset and a length argument, a new typed array view is created that views the specified ArrayBuffer.

Static properties

Item
Float64Array.BYTES_PER_ELEMENT
Returns a number value of the element size for the different Float64Array objects.
Float64Array.name
Returns the string value of the constructor name (e.g, "Float64Array").
Float64Array[@@species]
The constructor function used to create derived objects.
Float64Array.prototype
Prototype for Float64Array objects.

Static methods

Item Description
Float64Array.from()
Creates a new Float64Array from an array-like or iterable object. See also Array.from().
Float64Array.of()
Creates a new Float64Array with a variable number of arguments. See also Array.of().

Instance properties

Item
Float64Array.prototype.buffer
Returns the ArrayBuffer referenced by the typed array.
Float64Array.prototype.byteLength
Returns the length (in bytes) of the typed array.
Float64Array.prototype.byteOffset
Returns the offset (in bytes) of the typed array from the start of its ArrayBuffer.
Float64Array.prototype.length
Returns the number of elements held in the typed array.

Instance methods

Float64Array.prototype.copyWithin()
Copies a sequence of array elements within the array.
Float64Array.prototype.entries()
Returns a new Array Iterator object that contains the key/value pairs for each index in the array.
Float64Array.prototype.every()
Tests whether all elements in the array pass the test provided by a function.
Float64Array.prototype.fill()
Fills all the elements of an array from a start index to an end index with a static value.
Float64Array.prototype.filter()
Creates a new array with all of the elements of this array for which the provided filtering function returns true.
Float64Array.prototype.find()
Returns the found value in the array, if an element in the array satisfies the provided testing function, or undefined if not found.
Float64Array.prototype.findIndex()
Returns the found index in the array, if an element in the array satisfies the provided testing function or -1 if not found.
Float64Array.prototype.forEach()
Calls a function for each element in the array.
Float64Array.prototype.includes()
Determines whether a typed array includes a certain element, returning true or false as appropriate.
Float64Array.prototype.indexOf()
Returns the first (least) index of an element within the array equal to the specified value, or -1 if none is found.
Float64Array.prototype.join()
Joins all elements of an array into a string.
Float64Array.prototype.keys()
Returns a new Array Iterator that contains the keys for each index in the array.
Float64Array.prototype.lastIndexOf()
Returns the last (greatest) index of an element within the array equal to the specified value, or -1 if none is found.
Float64Array.prototype.map()
Creates a new array with the results of calling a provided function on every element in this array.
Float64Array.prototype.reduce()
Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value.
Float64Array.prototype.reduceRight()
Apply a function against an accumulator and each value of the array (from right-to-left) as to reduce it to a single value.
Float64Array.prototype.reverse()
Reverses the order of the elements of an array - the first becomes the last, and the last becomes the first.
Float64Array.prototype.set()
Stores multiple values in the typed array, reading input values from a specified array.
Float64Array.prototype.slice()
Extracts a section of an array and returns a new array.
Float64Array.prototype.some()
Returns true if at least one element in this array satisfies the provided testing function.
Float64Array.prototype.sort()
Sorts the elements of an array in place and returns the array.
Float64Array.prototype.subarray()
Returns a new Float64Array from the given start and end element index.
Float64Array.prototype.values()
Returns a new Array Iterator object that contains the values for each index in the array.
Float64Array.prototype.toLocaleString()
Returns a localized string representing the array and its elements.
Float64Array.prototype.toString()
Returns a string representing the array and its elements.
Float64Array.prototype[@@iterator]()
Returns a new Array Iterator object that contains the values for each index in the array.
Calling a Float64Array constructor as a function without new will throw a TypeError. var dv = new Float64Array([1, 2, 3]); Property access // Setting and getting using standard array syntax var int16 = new Float64Array(2); int16[0] = 42; console.log(int16[0]); // 42 // Indexed properties on prototypes are not consulted (Fx 25) Float64Array.prototype[20] = 'foo'; (new Float64Array(32))[20]; // 0 // even when out of bound Float64Array.prototype[20] = 'foo'; (new Float64Array(8))[20]; // undefined // or with negative integers Float64Array.prototype[-1] = 'foo'; (new Float64Array(8))[-1]; // undefined // Named properties are allowed, though (Fx 30) Float64Array.prototype.foo = 'bar'; (new Float64Array(32)).foo; // "bar" Result

Float64Array Reference

Javascript Float64Array buffer Javascript Float64Array byteLength Javascript Float64Array byteOffset Javascript Float64Array BYTES_PER_ELEMENT Javascript Float64Array copyWithin Javascript Float64Array entries Javascript Float64Array every Javascript Float64Array fill Javascript Float64Array filter Javascript Float64Array find Javascript Float64Array findIndex Javascript Float64Array.prototype[@@iterator]() Javascript Float64Array join Javascript Float64Array keys Javascript Float64Array lastIndexOf Javascript Float64Array length Javascript Float64Array forEach Javascript Float64Array.from Javascript Float64Array includes Javascript Float64Array indexOf Javascript Float64Array map Javascript Float64Array.name Javascript Float64Array.of Javascript Float64Array reduce Javascript Float64Array reduceRight Javascript Float64Array reverse Javascript Float64Array set Javascript Float64Array slice Javascript Float64Array some Javascript Float64Array sort Javascript Float64Array subarray Javascript Float64Array toLocaleString Javascript Float64Array toString Javascript Float64Array values

Proxies Introduction

ECMAScript 6 proxies and reflection can intercept and add additional behavior into common operations. You can define a proxy object associated with a target object. Then the proxy object can be used as a target object from which you can control what happens when operations are performed before they reach the target object. By default, all operations performed on a proxy object will be transparently propagated through to the target object. We can use a proxy object in the same ways as use the target object. A proxy is created using the Proxy constructor. It requires you to provide both a target object and a handler object, without which a TypeError is thrown.

Pass through proxy Creation

For a simple pass through proxy, using a simple object literal for the handler object will allow all operations to reach the target object. In the following example, all operations performed on the proxy will be applied to the target object instead. const target = { id: 'target' }; const handler = {}; const proxy = new Proxy(target, handler); The 'id' property will access the same value console.log(target.id); // target console.log(proxy.id); // target Assignment to a target property changes both since both are accessing the same value. target.id = 'foo'; console.log(target.id); // foo console.log(proxy.id); // foo Assignment to a proxy property changes both since this assignment is conferred to the target object. proxy.id = 'bar'; console.log(target.id); // bar console.log(proxy.id); // bar The hasOwnProperty() method is effectively applied to the target in both cases. console.log(target.hasOwnProperty('id')); // false console.log(proxy.hasOwnProperty('id')); // false The instanceof operator is effectively applied to the target in both cases. console.log(target instanceof Proxy); // false console.log(proxy instanceof Proxy); // false Strict object equality can still be used to differentiate proxy from target. console.log(target === proxy); // false

Defining Traps using Proxy

We can use a proxy to define traps, which acts as operation interceptors inside the handler object. Each handler object is made up of zero, one, or many traps. Each trap corresponds to an operation that can be directly or indirectly called on the proxy. When these operations are called on the proxy object, before being invoked on the target object, the proxy will invoke the trap function instead, allowing you to intercept and modify its behavior.

Example

For example, we can define a get() trap that is triggered each time any ECMAScript operation performs a get() in one form or another. Such a trap can be defined as follows: const target = { foo: 'bar' }; const handler = { // Traps are keyed by method name // inside the handler object get() { return 'handler override'; } }; const proxy = new Proxy(target, handler); When a get() operation is called on this proxy object, the trap function that is defined for get() will be invoked instead. get() is not a usable method on ECMAScript objects. Operations of the form proxy[property], proxy.property, or Object.create(proxy)[property] will all use the operation get() to retrieve the property, All of these operations will invoke the trap function instead when they are used on the proxy. Only the proxy will use the trap handler functions; these operations will behave normally when used with the target object. const target = {/* w w w. d e m o2 s . c o m*/ foo: 'bar' }; const handler = { // Traps are keyed by method name inside the handler object get() { return 'got you'; } }; const proxy = new Proxy(target, handler); console.log(target.foo); // bar console.log(proxy.foo); // handler override console.log(target['foo']); // bar console.log(proxy['foo']); // handler override console.log(Object.create(target)['foo']); // bar console.log(Object.create(proxy)['foo']); // handler override Output:

Trap Parameters using Proxy

All proxy traps can access parameters and we can recreate the behavior of the trapped method. For example, the get() method receives a reference to the target object, the property being looked up, and a reference to the proxy object. const target = { foo: 'bar' }; const handler = { get(trapTarget, property, receiver) { console.log(trapTarget === target); console.log(property); console.log(receiver === proxy); } }; const proxy = new Proxy(target, handler); proxy.foo; Output: Thus, we can define a trap handler that recreates the behavior of the method being trapped: const target = { foo: 'bar' }; const handler = { get(trapTarget, property, receiver) { return trapTarget[property]; } }; const proxy = new Proxy(target, handler); console.log(proxy.foo); // bar console.log(target.foo); // bar Output: Such a tactic can be implemented for all traps.

Reflect API Introduction

The original behavior of the trapped method is wrapped in a method with the same name on the global Reflect object. Each trapped method inside a handler object has a corresponding Reflect API method. This method has an identical name and function signature, and performs the exact behavior. Therefore, We can define a pass through proxy using only the Reflect API: const target = { foo: 'bar' }; const handler = { get() { return Reflect.get(...arguments); } }; const proxy = new Proxy(target, handler); console.log(proxy.foo); // bar console.log(target.foo); // bar Output: Alternately, in a more succinct format: const target = { foo: 'bar' }; const handler = { get: Reflect.get }; const proxy = new Proxy(target, handler); console.log(proxy.foo); // bar console.log(target.foo); // bar Output: We can create a pass through proxy that traps every available method and forwards each one to its corresponding Reflect API function as follows: const target = { foo: 'bar' }; const proxy = new Proxy(target, Reflect); console.log(proxy.foo); // bar console.log(target.foo); // bar Output:

Use Reflect API to Modify trapped method

The Reflect API allows you to modify the trapped method with minimal code. For example, the following decorates the return value whenever a certain property is accessed: const target = {/* w w w . d e mo 2 s . c o m*/ foo: 'CSS', baz: 'HTML' }; const handler = { get(trapTarget, property, receiver) { let decoration = ''; if (property === 'foo') { decoration = '!!!'; } return Reflect.get(...arguments) + decoration; } }; const proxy = new Proxy(target, handler); console.log(proxy.foo); console.log(target.foo); console.log(proxy.baz); console.log(target.baz); Output:

Proxy Trap Limitation

The behavior of the trap handler function must obey the "trap invariants" as specified in the ECMAScript specification. Trap invariants vary by method, and they all prevent the trap from running any unexpected behaviors. For example, if a target object has a non-configurable and non-writable data property, a TypeError will be thrown if you attempt to return a value from the trap that is different from the target object's property: const target = {}; Object.defineProperty(target, 'foo', { configurable: false, writable: false, value: 'bar' }); const handler = { get() { return 'qux'; } }; const proxy = new Proxy(target, handler); console.log(proxy.foo); Output:

Revocable Proxies

We can disable the association between the proxy object and the target object. Proxy revocable() method can disassociate the proxy object from the target object. Revoking the proxy is irreversible. The revoke function have no effect if called multiple times. Any method called on a proxy after it is revoked will throw a TypeError. The revoke function can be captured upon proxy instantiation: const target = { foo: 'bar' }; const handler = { get() { return 'intercepted'; } }; const { proxy, revoke } = Proxy.revocable(target, handler); console.log(proxy.foo); // intercepted console.log(target.foo); // bar revoke(); console.log(proxy.foo); // TypeError

Use the Status Flags from Reflect API

Javascript Reflect API is not limited to trap handler Most Reflect API methods have an analogue on the Object type. The Reflect methods are for fine-tuned object control and manipulation. Many Reflect methods return a Boolean, indicating if the operation they intend to perform will be successful or not. For example, you can use the Reflect API to perform the following refactor: // Initial code const object1 = {}; if (Object.defineProperty(object1, 'property1', { value: 42 })) { console.log('property1 created!'); // expected output: "property1 created!" } else { console.log('problem creating property1'); } console.log(object1.property1); // expected output: 42 Output: In the event of a problem with defining the new property, Reflect.defineProperty() will return false instead of throwing an error, enabling you to do the following: // Refactored code const object1 = {}; if (Reflect.defineProperty(object1, 'property1', { value: 42 })) { console.log('property1 created!'); // expected output: "property1 created!" } else { console.log('problem creating property1'); } console.log(object1.property1); // expected output: 42 Output: The following Reflect methods provide you with status flags: Reflect.defineProperty Reflect.preventExtensions Reflect.setPrototypeOf Reflect.set Reflect.deleteProperty

Proxying a Proxy

Proxies are capable of intercepting Reflect API operations, and this means that we can create a proxy of a proxy. With proxy on proxy we can build multiple layers of indirection on top of a singular target object: const target = { foo: 'bar' }; const firstProxy = new Proxy(target, { get() { console.log('first proxy'); return Reflect.get(...arguments); } }); const secondProxy = new Proxy(firstProxy, { get() { console.log('second proxy'); return Reflect.get(...arguments); } }); console.log(secondProxy.foo); Output:

Proxy Traps And Reflect Methods get()

The get() trap is called inside operations that retrieve a property value. Its corresponding Reflect API method is Reflect.get().

Example

const myTarget = {}; const proxy = new Proxy(myTarget, { get(target, property, receiver) { console.log('get()'); return Reflect.get(...arguments) } }); proxy.foo; Output:

Return value

The return value is unrestricted.

Intercepted operations

proxy.property proxy[property] Object.create(proxy)[property] Reflect.get(proxy, property, receiver)

Trap handler parameters

target - Target object property - String key property being referenced on target object receiver - Proxy object or object that inherits from proxy object

Trap Limitation

If target.property is non-writable and non-configurable, the handler return value must match target.property. If target.property is non-configurable and has undefined as its [[Get]] attribute, the handler return value must also be undefined.

Proxy Traps And Reflect Methods set()

The set() trap is called inside operations that assign a property value. Its corresponding Reflect API method is Reflect.set().

Example

const myTarget = {}; const proxy = new Proxy(myTarget, { set(target, property, value, receiver) { console.log('set()'); return Reflect.set(...arguments) } }); proxy.foo = 'bar'; Output:

Return value

A return value of true indicates success; a return value of false indicates failure, and in strict mode will throw a TypeError.

Intercepted operations

proxy.property = value proxy[property] = value Object.create(proxy)[property] = value Reflect.set(proxy, property, value, receiver)

Trap handler parameters

target - Target object property - String key property being referenced on target object value - The value being assigned to property receiver - The original assignment recipient object

Trap Limitation

If target.property is non-writable and non-configurable, the target property value cannot be altered. If target.property is non-configurable and has undefined as its [[Set]] attribute, the target property value cannot be altered. Returning false from the handler will throw a TypeError in strict mode.

Proxy Traps And Reflect Methods has()

The has() trap is called inside the in operator. Its corresponding Reflect API method is Reflect.has().

Example

const myTarget = {}; const proxy = new Proxy(myTarget, { has(target, property) { console.log('has()'); return Reflect.has(...arguments) } }); 'foo' in proxy; Output:

Return value

has() must return a Boolean indicating if the property is present or not. Non-Boolean return values will be coerced into a Boolean.

Intercepted operations

property in proxy property in Object.create(proxy) with(proxy) {(property);} Reflect.has(proxy, property)

Trap handler parameters

target - Target object property - String key property being referenced on target object

Trap invariants

If an own target.property exists and is non-configurable, the handler must return true. If an own target.property exists and the target object is non-extensible, the handler must return true.

Proxy Traps And Reflect Methods defineProperty()

The defineProperty() trap is called inside Object.defineProperty(). Its corresponding Reflect API method is Reflect.defineProperty().

Example

const myTarget = {}; const proxy = new Proxy(myTarget, { defineProperty(target, property, descriptor) { console.log('defineProperty()'); return Reflect.defineProperty(...arguments) } }); Object.defineProperty(proxy, 'foo', { value: 'bar' }); // defineProperty()

Return value

defineProperty() must return a Boolean indicating if the property was successfully defined or not. Non-Boolean return values will be coerced into a Boolean.

Intercepted operations

Object.defineProperty(proxy, property, descriptor) Reflect.defineProperty(proxy, property, descriptor)

Trap handler parameters

target - Target object property - String key property being referenced on target object descriptor - Object containing optional definitions for enumerable, configurable, writable, value, get, or set

Trap invariants

If the target object is non-extensible, properties cannot be added. If the target object has a configurable property, a non-configurable property of the same key can- not be added. If the target object has a non-configurable property, a configurable property of the same key can- not be added.

Proxy Traps And Reflect Methods getOwnPropertyDescriptor()

The getOwnPropertyDescriptor() trap is called inside Object.getOwnPropertyDescriptor(). Its corresponding Reflect API method is Reflect.getOwnPropertyDescriptor().

Example

const myTarget = {}; const proxy = new Proxy(myTarget, { getOwnPropertyDescriptor(target, property) { console.log('getOwnPropertyDescriptor()'); return Reflect.getOwnPropertyDescriptor(...arguments) } }); Object.getOwnPropertyDescriptor(proxy, 'foo'); Output:

Return value

getOwnPropertyDescriptor() must return an object, or undefined if the property does not exist.

Intercepted operations

property in proxy property in Object.create(proxy) with(proxy) {(property);} Reflect.has(proxy, property)

Trap handler parameters

target - Target object property - String key property being referenced on target object

Trap invariants

If an own target.property exists and is non-configurable, the handler must return an object to indicate that the property exists. If an own target.property exists and is configurable, the handler cannot return an object indicating that the property is configurable. If an own target.property exists and target is non-extensible, the handler must return an object to indicate that the property exists. If target.property does not exist and target is non-extensible, the handler must return undefined to indicate that the property does not exist. If target.property does not exist, the handler cannot return an object indicating that the property is configurable.