JS uses two kinds of types: primitive and reference. Primitive types are stored as simple data types. Reference types are stored as objects, which are really just references to locations in memory.
- Boolean true or false
- Number Any integer or floating-point numeric value
- Null A primitive type that has only one value, null
The best way to identify primitive types is with the typeof operator, which works on any variable and returns a string indicating the type of data. The typeof operator works well with strings, numbers, Booleans, and undefined. The tricky part involves null. When you run typeof null, the result is "object". The best way to determine if a value is null is to compare it against null directly. Despite the fact that they’re primitive types, strings, numbers, and Booleans actually have methods. (The null and undefined types have no methods.) Strings, in particular, have numerous methods to help you work with them.
- Pointer means that if you assign one variable to another, each variable gets a copy of the pointer, and both still reference the same object in memory.
Adding or Removing Properties
The built-in types are:
- Literal Forms
Primitive wrapper Types
There are three primitive wrapper types (String, Number, and Boolean). The primitive wrapper types are reference types that are automatically created behind the scenes whenever strings, numbers, or Booleans are read (this process is called autoboxing). An object is always considered true inside a conditional statement.
There are actually two literal forms of functions. The first is a function declaration, which begins with the function keyword, The second form is a function expression, which doesn’t require a name after function. These functions are considered anonymous because the function object itself has no name. Instead, function expressions are typically referenced via a variable or property. Function declarations are hoisted to the top of the context. For instance, you can pass a function into another function as an argument. You can pass any number of parameters to any function without causing an error. The number of arguments a function expects is stored on the function’s length property. The length property indicates the function’s arity, or the number of parameters it expects.
The syntax for a data property and a method is exactly the same
The this Object
The if condition evaluates to true if the value is truthy (an object, a nonempty string, a nonzero number, or true) and evaluates to false if the value is falsy (null, undefined, 0, false, NaN, or an empty string).
The in operator looks for a property with a given name in a specific object and returns true if it finds it. In effect, the in operator checks to see if the given key exists in the hash table. The in operator checks for both own properties and prototype properties. The hasOwnProperty() method, which is present on all objects and returns true only if the given property exists and is an own property.
The delete operator works on a single object property removing a key/value pair from a hash table. When the delete operator is successful, it returns true.
By default, all properties that you add to an object are enumerable, which means that you can iterate over them using a for-in loop. ate on an array of property names and for-in when you don’t need an array. There is a difference between the enumerable properties returned in a for-in loop and the ones returned by Object.keys(). The for-in loop also enumerates prototype properties, while Object.keys() returns only own (instance) properties. You can check whether a property is enumerable by using the propertyIsEnumerable() method.
Types of Properties
There are two different types of properties: data properties and accessor properties. Data properties contain a value. Accessor properties don’t contain a value but instead define a function to call when the property is read (called a getter), and a function to call when the property is written to (called a setter). Accessor properties are most useful when you want the assignment of a value to trigger some sort of behavior, or when reading a value requires the calculation of the desired return value. You don’t need to define both a getter and a setter; you can choose one or both. If you define only a getter, then the property becomes read-only, and attempts to write to it will fail silently in nonstrict mode and throw an error in strict mode. If you define only a setter, then the property becomes write-only, and attempts to read the value will fail silently in both strict and nonstrict modes.
There are two property attributes shared between data and accessor properties. One is [[Enumerable]], which determines whether you can iterate over the property. The other is [[Configurable]], which determines whether the property can be changed. You can remove a configurable property using delete and can change its attributes at any time. By default, all properties you declare on an object are both enumerable and configurable. If you want to change property attributes, you can use the Object .defineProperty() method. This method accepts three arguments: the object that owns the property, the property name, and a property descriptor object containing the attributes to set. When you are defining a new property with Object.defineProperty(), it’s important to specify all of the attributes because Boolean attributes automatically default to false otherwise. If you need to fetch property attributes, you can do so in Java Script by using Object.getOwnPropertyDescriptor(). If the property exists, you should receive a descriptor object with four properties: configurable, enumerable, and the two others appropriate for the type of property.
Preventing object modification
Objects, just like properties, have internal attributes that govern their behavior. All objects you create are extensible by default, meaning new properties can be added to the object at any time. you can prevent new properties from being added to an object. There are three different ways to accomplish this.
One way to create a nonextensible object is with Object.preventExtensions(). You can check the value of [[Extensible]] by using Object.isExtensible()
The second way to create a nonextensible object is to seal the object. A sealed object is nonextensible, and all of its properties are nonconfigurable. If an object is sealed, you can only read from and write to its properties.
The last way to create a nonextensible object is to freeze it. If an object is frozen, you can’t add or remove properties, you can’t change properties’ types, and you can’t write to any data properties. In essence, a frozen object is a sealed object where data properties are also read-only.
Constructors and prototypes
A constructor is simply a function that is used with new to create an object. Because a constructor is just a function, you define it in the same way. The only difference is that constructor names should begin with a capital letter, to distinguish them from other functions. Even though this relationship exists between an instance and its constructor, you are still advised to use instanceof to check the type of an instance. This is because the constructor property can be overwritten and therefore may not be completely accurate. You could also use Object.defineProperty() inside of a constructor to help initialize the instance Make sure to always call constructors with new; otherwise, you risk to change the global object instead of the newly created object. Constructors allow you to configure object instances with the same properties, but constructors alone don’t eliminate code redundancy, that means if you have 100 instances of an object, then there are 100 copies of a function that do the exact same thing, just with different data. It would be much more efficient if all of the instances shared one method. This is where prototypes come in.
Built-in Object Prototypes
The object instances inherit properties from the prototype.Because the prototype is also an object, it has its own prototype and inherits properties from that. This is the prototype chain. All objects inherit from Object.prototype.
Methods Inherited from Object.prototype
- hasOwnProperty() Determines whether an own property with the given name exists
- propertyIsEnumerable() Determines whether an own property is enumerable
- isPrototypeOf() Determines whether the object is the prototype of another
- valueOf() Returns the value representation of the object
- toString() Returns a string representation of the object
The valueOf() method gets called whenever an operator is used on an object. By default, valueOf() simply returns the object instance. The primitive wrapper types override valueOf() so that it returns a string for String, a Boolean for Boolean, and a number for Number. Likewise, the Date object’s valueOf() method returns the epoch time in milliseconds.
All objects inherit from Object.prototype by default, so changes to Object.prototype affect all objects. The Object.create() method accepts two arguments. The first argument is the object to use for [[Prototype]] in the new object. The optional second argument is an object of property descriptors in the same format used by Object.defineProperties() You can also create objects with a null [[Prototype]] via Object.create(). Because the prototype property is writable, you can change the prototype chain by overwriting it. Always make sure that you overwrite the prototype before adding properties to it, or you will lose the added methods when the overwrite happens. YourConstructor is a subtype of Object, and Object is a supertype of YourConstructor.
Call the supertype constructor from the subtype constructor using either call() or apply() to pass in the newly created object. In effect, you’re stealing the supertype constructor for your own object, This two-step process is useful when you need to accomplish inheritance between custom types. You’ll always need to modify a constructor’s prototype, and you may also need to call the supertype constructor from within the subtype constructor. Generally, you’ll modify the prototype for method inheritance and use constructor stealing for properties. This approach is typically referred to as pseudoclassical inheritance because it mimics classical inheritance from class-based languages. You can directly access the method on the supertype’s prototype and use either call() or apply() to execute the method on the subtype object.
Private and privileged members
The Module Pattern
The basic approach is to use an immediately invoked function expression (IIFE) that returns an object. An IIFE is a function expression that is defined and then called immediately to produce a result. That function expression can contain any number of local variables that aren’t accessible from outside that function. Methods that access private data in this way are called privileged methods. The module pattern allows you to use regular variables as de facto object properties that aren’t exposed publicly. You accomplish this by creating closure functions as object methods. Closures are simply functions that access data outside their own scope. There is a variation of the module pattern called the revealing module pattern, which arranges all variables and methods at the top of the IIFE and simply assigns them to the returned object.
Private members for constructors
You can use a pattern that’s similar to the module pattern inside the constructor to create instance-specific private data. Placing methods on an object instance is less efficient than doing so on the prototype, but this is the only approach possible when you want private, instance-specific data.