This & Object prototypes (3): Objects
June 30, 2018
⏳ 9 min read
Types and objects
Types in JS
- string
- number
- boolean
- null
- undefined
- object
Built-in Objects
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
String
and string
, and other primitives
var strPrimitive = "I am a string";
typeof strPrimitive; // "string"
strPrimitive instanceof String; // false
var strObject = new String( "I am a string" );
typeof strObject; // "object"
strObject instanceof String; // true
// inspect the object sub-type
Object.prototype.toString.call( strObject ); // [object String]
Things we can know:
1. `strPrimitive` is not created by `String` constructor, so it logs `false` on `instanceof String` test.
2. `strObject` on the other hand is created by `String` constructor.
3. `"I am a string"` is actually **not** an object but a primitive literal and immutable value. If you want to do something like `"I am a string"[0]"` or `"I am a string".substring(2,4)`, you are going to need a temporary `String` object.
The same logic applies for other objects with corresponding primitives, such as: 1
and new Number(1)
.
Others
null
andundefined
have no object wrappers.Object
,Array
,Function
, andRegExp
are ALL OBJECTS regardless of whether the literal or constructed form is used (ex.new Array()
and[]
).- Simpler literal form is preferred. Only use constructed form if you need extra options.
Contents
An object contains property names only that act as pointers (references) to where actual values are stored.
Two ways of accessing
testObject = { a: 1 }
testObject.a
testObject["a"]
.
and [ ]
operator function the same. But their usage may vary:
.
operator requires anIdentifier
compatible property name.[".."]
operator can take any UTF-8 compatible string as the property name. (Ex.["Hey++^]"
)- You can also extend the logic to make a property name where necessary (make a string first, and then use that as a property name)
- In objects, property names are always strings. If you use any other value besides a string (primitive) as the property, it will first be converted to a string.
var myObject = { };
myObject[true] = "foo";
myObject[3] = "bar";
myObject[myObject] = "baz";
myObject["true"]; // "foo"
myObject["3"]; // "bar"
myObject["[object Object]"]; // "baz"
Notice that for the last property myObject
, toString
was called to fill the prop name.
You can also do this with [".."]
This is called “Computed property names”.
var prefix = "foo";
var myObject = {
[prefix + "bar"]: "hello",
[prefix + "baz"]: "world"
};
myObject["foobar"]; // hello
myObject["foobaz"]; // world
Property vs Method
If a function belongs to an object (a class), it is referred to as a ‘method’ ion other languages.
But technically, as we have seen, functions never belong to objects.
If you access a property on an object, it is a property access but a method access.
Perhaps one could argue that a function becomes a method, not at definition time, but during run-time just for that invocation, depending on how it’s called at its call-site.
The safest conclusion is probably that “function” and “method” are interchangeable in JavaScript.
Arrays
Arrays are objects, so they can have a property like
let test = [2,3]
test.a = "hey"
test.length // 2
test.a
does not add to the length of the array. It is just a property.
But if you use a property name that looks like a number, it will be understood as a numeric index.
let test = []
test["0"] = "zero index"
test["1"] = "first index"
test.length // 2
Object.assign(...)
(also ({ ...objectName })
)
Object.assign
iterates over all the enumerable, owned keys on the source objects and copies them to the target object (on the LHS)
class Dog{
constructor(name){
this.name = "name"
}
}
let testObj = { person: "Joel" }
let additionalObj = {
say: () => console.log("hello"),
friends: ["B", "E", "Q"],
pet: new Dog("Matt")
}
let newObj = Object.assign(
{}, // <<--- the target (newly created) object
additionalObj,
testObj
)
console.log(newObj)
/*
{say: ƒ, friends: Array(3), pet: Dog, person: "Joel"}
*/
console.log(`${newObj.say === additionalObj.say},
${newObj.friends === additionalObj.friends},
${newObj.pet === additionalObj.pet},
${newObj.person === testObj.person}
`)
/* it shows that properties are copied via `=` assignment only.
true,
true,
true,
true
*/
An alternative to Object.assign(...)
is { ...objectName, ..}
syntax:
class Dog{
constructor(name){
this.name = "name"
}
}
let testObj = { person: "Joel" }
let additionalObj = {
say: () => console.log("hello"),
friends: ["B", "E", "Q"],
pet: new Dog("Matt")
}
let newObj = {
...testObj,
...additionalObj
// you can override properties here
// like, pet: new Dog("Sally")
// Notice how simpler and clearer the syntax is!
}
console.log(newObj)
/*
{say: ƒ, friends: Array(3), pet: Dog, person: "Joel"}
*/
console.log(`${newObj.say === additionalObj.say},
${newObj.friends === additionalObj.friends},
${newObj.pet === additionalObj.pet},
${newObj.person === testObj.person}
`)
/* it shows that properties are copied via `=` assignment only.
true,
true,
true,
true
*/
Property descriptors
From ES5 onwards, all properties are described in terms of a property descriptor.
Object.getOwnPropertyDescriptor()
Following on from the last example:
Object.getOwnPropertyDescriptor( newObj, "friends" );
// See you have three characteristics: writable, enumerable and configurable.
// {value: Array(3), writable: true, enumerable: true, configurable: true}
Object.defineProperty()
var test = {};
Object.defineProperty( test, "myProp", {
value: "hey",
writable: true,
configurable: true,
enumerable: true
} );
test.myProp; // hey
It’s just a difficult way of doing var test = { myProp: "hey" }
. Just know that there’s a function called Object.defineProperty()
.
Writable
You cannot chance a value of a property if you set writable
as false
.
var test = {};
Object.defineProperty( test, "myProp", {
value: "hey",
writable: false,
configurable: true,
enumerable: true
} );
test.myProp = "it cannot change";
test.myProp // hey
Yeap. It does not change.
If you use strict
, you get an error by attempting to modify the value:
'use strict'
var test = {}
Object.defineProperty( test, "myProp", {
value: "hey",
writable: false,
configurable: true,
enumerable: true
} );
test.myProp = "it cannot change"
/*
logs:
Uncaught TypeError: Cannot assign to read only property 'myProp' of object '#<Object>'
*/
Configurable
If it’s configurable, you can modify its descriptor definition using defineProperty()
function.
var test = {}
Object.defineProperty( test, "myProp", {
value: "hey",
writable: true,
configurable: false,
enumerable: true
} );
Object.defineProperty( test, "myProp", {
value: "hey",
writable: true,
configurable: true,
enumerable: true
} );
delete test.myProp //fail
test.myProp // "hey". still here. not deleted.
/*
Uncaught TypeError: Cannot redefine property: myProp
at Function.defineProperty (<anonymous>)
*/
As you might have noticed, it is just a one-way action, so it cannot be undone.
Immutability
Object constant
var myObject = {};
Object.defineProperty( myObject, "NOT_CHANGING", {
value: "whatsup",
writable: false,
configurable: false
} );
Effect: cannot be changed, redefined or deleted.
Prevent extensions
var myObject = {
a: 2
};
Object.preventExtensions( myObject );
myObject.b = "something";
myObject.b; // undefined
Effect: prevent the object from being added to new properties.
Doing the above two things at once: Object.seal(..)
It’s literally just that. It seals the object by calling preventExtensions()
and making configurable
property as false
.
Doing one thing more: Object.freeze(..)
Things it does:
Object.seal(..)
- sets
writable
asfalse
[[Get]]
myObject.myProp
works through [[Get]]
operation.
The default built-in
[[Get]]
operation for an object first inspects the object for a property of the requested name, and if it finds it, it will return the value accordingly.
Otherwise it returns undefined
for the requested property that is not found.
[[Put]]
Simple assignment like let something = 1
would not just call [[Put]]
.
If a property is already present in an object:
- Is the property an accessor descriptor (see “Getters & Setters” section below)? If so, call the setter, if any.
- Is the property a data descriptor with
writable
offalse
? If so, silently fail innon-strict mode
, or throwTypeError
instrict mode
.- Otherwise, set the value to the existing property as normal.
If a property is not present, it is more complex, which will be covered later.
Getters & Setters
You can override getters and setters like:
const myObj = {
get a(){
return this._a
},
set a(val){
this._a = val - 1
}
}
> myObj.a
undefined
> myObj.a = 4
4
> myObj.a
3
Existence
Distinguishing between undefined
as a property and that as nothing.
const myObj = {
test: undefined
}
//both would log undefined
myObj.test // undefined
myObj.sofeiewagionweiogw // undefined
"test" in myObj // true
"sofeiewagionweiogw" in myObj // false
myObj.hasOwnProperty("test") // true
myObj.hasOwnProperty("sofeiewagionweiogw") // false
in
vs hasownProperty(..)
in
: checks the object itself and the prototype chain to see if the property is present in these by looking at the existence of the property name supplied.
hasOwnProperty(..)
checks only the target object itself. It will NOT go over the prototype chain.
Enumeration
var myObject = { };
Object.defineProperty(
myObject,
"a",
// make `a` enumerable, as normal
{ enumerable: true, value: 2 }
);
Object.defineProperty(
myObject,
"b",
// make `b` NON-enumerable
{ enumerable: false, value: 3 }
);
myObject.b; // 3
("b" in myObject); // true
myObject.hasOwnProperty( "b" ); // true
// .......
for (var k in myObject) {
console.log( k, myObject[k] );
}
// "a" 2
It’s just that b
exists, but it does not get referred to inside for..in
loop.
- You can check with
propertyIsEnumerable(..)
too see if the property name exists directly on the object and is enumerable. Object.keys(..)
returns an array of all enumerable properties.Object.getOwnPropertyNames(..)
returns an array of all properties regardless. Bothkeys(..)
andgetOwnPropertyNames(..)
would inspect only the direct object.
Iteration
for..in
iterates over enumerable properties on an object. You are getting the values directly.
Normal (let i = 0; i < arr.length; i++)
loop
iterates over indices to get values.
forEach(..)
iterates over all values in an array, ignoring any callback return values.
const arr = [3,4,5,6]
let test = arr.forEach((elem)=>{console.log(elem); return 3})
test // undefined.
every(..)
iterates until the end or the callback returns a falsy value.
const arr = [3,4,5,6]
arr.every((elem,index)=> { console.log(elem); return index == 2 ? false : true})
/*
logs:
3
4
5
*/
some(..)
reverse of every(..)
. Iterates until the end or the callback returns a truthy value.
arr.some((elem,index)=> { console.log(elem); return index == 2 ? false : true})
/*
logs:
3
*/
Index order is not guaranteed
The order of iteration over an object’s properties is not guaranteed and may vary between different JS engines.