javascript - object oriented programming
TRANSCRIPT
Javascript - Object-Oriented Programming
@farhan-faruque
Overview!!ECMAScript has no concept of classes, and therefore objects are different than in class-based languages.ECMA-262 defines an object as an “unordered collection of properties each of which contains a primitive value, object, or function.”Each object is created based on a reference type, either one of the native types or a developer-defined type.
UNDERSTANDING OBJECTSThe simplest way to create a custom object is to create a new instance of Object and add properties and methods to it
var person = new Object();person.name = “Nicholas”;person.sayName = function(){ alert(this.name);};
can be rewritten using object literal:var person = {
name: “Nicholas”,sayName: function(){
alert(this.name);}
};
Types of PropertiesThere are two types of properties: data properties and accessor properties.Data Properties➢ [[Configurable]] - 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]] - property will be returned in a for-in loop. By default, this is true
➢ [[Writable]] - property’s value can be changed. By default, this is true for all ➢ [[Value]] - Contains the actual data value for the property.The default value
for this attribute is undefined.
change default propertiesTo change any of the default property attributes, we must use the ECMAScript 5 Object.defineProperty() method.
var person = {};Object.defineProperty(person, “name”, {
writable: false,value: “Nicholas”
})alert(person.name); //”Nicholas”person.name = “Greg”;alert(person.name); //”Nicholas”
name “Nicholas”
Configurable propertyvar person = {};Object.defineProperty(person, “name”, {
configurable: false,value: “Nicholas”
});alert(person.name);//”Nicholas”delete person.name;alert(person.name);//”Nicholas”
setting configurable to false means that the property cannot be removed from the object.
Change Propertiesonce 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.
Accessor PropertiesAccessor properties do not contain a data value.they contain a combination of a getter function and a setter function Accessor properties have four attributes:➢ [[Configurable]] - indicates if the property may be redefined➢ [[Enumerable]] - Indicates if the property will be returned in a
for-in loop.➢ [[Get]] - The function to call when the property is read from➢ [[Set]] - The function to call when the property is written to.
Examplevar book = {
_year: 2004,edition: 1
};
Object.defineProperty(book, “year”, {get: function(){
return this._year;},set: function(newValue){
this._year = newValue;}
});
book.year = 2005;alert(book.edition);//2005
Accessor PropertiesAssigning just a getter means that the property cannot be written to and attempts to do so will be ignored a property
with only a setter cannot be read and will return the value undefined in nonstrict mode, while doing so throws an error in strict mode.
Defining Multiple PropertiesECMAScript 5 provides the Object.defineProperties() method
var book = {};Object.defineProperties(book, {
_year: {value: 2004
},edition: {
value: 1}
});
Reading Property AttributesObject.getOwnPropertyDescriptor() - retrieve the property descriptor for a given propertyvar book = {};Object.defineProperties(book, {
_year: {value: 2004
},edition: {
value: 1}
});var descriptor = Object.getOwnPropertyDescriptor(book, “_year”);alert(descriptor.value);//2004alert(descriptor.configurable);//falsealert(typeof descriptor.get);//”undefined”
The Factory Patternfunction createPerson(name, age, job){
var o = new Object();o.name = name;o.age = age;o.job = job;o.sayName = function(){
alert(this.name);};return o;
}var person1 = createPerson(“Nicholas”, 29, “Software Engineer”);var person2 = createPerson(“Greg”, 27, “Doctor”);
The Constructor Patternfunction Person(name, age, job){
this.name = name;this.age = age;this.job = job;this.sayName = function(){
alert(this.name);};
}var person1 = new Person(“Nicholas”, 29, “Software Engineer”);var person2 = new Person(“Greg”, 27, “Doctor”);
Each of these objects has a constructor property that points back to Person
constructor propertyalert(person1.constructor == Person); //truealert(person2.constructor == Person); //true
The constructor property was originally intended for use in identifying the object type.
However, the instanceof operator is considered to be a safer way of determining type.
Problems with ConstructorsThe major downside to constructors is that methods are created once for each instance.functions are objects in ECMAScript, so every time a function is defined, it’s actually an object being instantiated. Logically, the constructor actually looks like this:function Person(name, age, job){
this.name = name;this.age = age;this.job = job;this.sayName = new Function(“alert(this.name)”); //logical equivalent
}
The Prototype PatternEach 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.
The benefit of using the prototype is that all of its properties and methods are shared among object instances.
Examplefunction Person(){
}Person.prototype.name = “Nicholas”;Person.prototype.age = 29;Person.prototype.job = “Software Engineer”;Person.prototype.sayName = function(){
alert(this.name);};
var person1 = new Person();person1.sayName();//”Nicholas”
var person2 = new Person();person1.sayName(); //”Nicholas”alert(person1.sayName == person2.sayName);//true
How Prototypes Work➔ Whenever a function is created, its prototype property is also
created.➔ All prototypes automatically get a property called constructor
that points back to the function on which it is a property.➔ Each time the constructor is called to create a new instance, that
instance has an internal pointer to the constructor’s prototype.
How Prototypes Work
Person
prototype
Person Prototype
constructor
name
sayName
“Farhan”
person2[[prototype]][[prototype]]
(function)
person1
How Prototypes Work
➔ Person.prototype points to the prototype object but Person.prototype.constructor points back to Person.
➔ 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.
isPrototypeOf() methodisPrototypeOf()
alert(Person.prototype.isPrototypeOf(person1));//truealert(Person.prototype.isPrototypeOf(person2));//true
Object.getPrototypeOf()Object.getPrototypeOf() - which returns the value of [[Prototype]] in all supporting implementations.
alert(Object.getPrototypeOf(person1) == Person.prototype);//truealert(Object.getPrototypeOf(person1).name); //”Nicholas”
find a property in Object➔ 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➔ The prototype is searched for a property with the same name. ➔ If the property is found on the prototype, then that value is
returned.
How Prototypes WorkAlthough it’s possible to read values on the prototype from object instances, it is not possible to overwrite them. function Person(){}Person.prototype.name = “Nicholas”;
var person1 = new Person();var person2 = new Person();person1.name = “Greg”;alert(person1.name); //”Greg” - from instancealert(person2.name); //”Nicholas” - from prototype
add a property to an instance that has the same name as a property on the prototype.
How Prototypes Work➔ Once a 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.
➔ 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
How Prototypes Work - Examplefunction Person(){}Person.prototype.name = “Nicholas”;
var person1 = new Person();var person2 = new Person();person1.name = “Greg”;alert(person1.name); //”Greg” - from instancealert(person2.name); //”Nicholas” - from prototypedelete person1.name;alert(person1.name); //”Nicholas” - from the prototype
hasOwnPropertyhasOwnProperty() - determines if a property exists on the instance or on the prototype.function Person(){}Person.prototype.name = “Nicholas”;var person1 = new Person();var person2 = new Person();alert(person1.hasOwnProperty(“name”)); //falseperson1.name = “Greg”;alert(person1.hasOwnProperty(“name”)); //truealert(person2.hasOwnProperty(“name”)); //false
Prototypes and the in Operatorin true
function Person(){}Person.prototype.name = “Nicholas”;alert(person1.hasOwnProperty(“name”));//falsealert(“name” in person1); //trueperson1.name = “Greg”;alert(person2.hasOwnProperty(“name”)); //falsealert(“name” in person2); //true
Object.keys()Object.keys() method - accepts an object as its argument and returns an array of strings containing the names of all enumerable properties.
function Person(){}Person.prototype.name = “Nicholas”;Person.prototype.age = 29;Person.prototype.job = “Software Engineer”;var keys = Object.keys(Person.prototype);alert(keys); //”name,age,job,sayName”
Alternate Prototype Syntaxfunction Person(){}Person.prototype = {
name : “Nicholas”,age : 29,job : “Software Engineer”,sayName : function () {
alert(this.name);}
};
The end result is the same, with one exception: the constructor property no longer points to Person .its overwrites the default prototype object completely, meaning that the constructor property is equal to that of a completely new object (the Object constructor) instead of the function itself.
Alternate Prototype Syntaxfunction Person(){}Person.prototype = {
constructor: Person,name : “Nicholas”,age : 29,job : “Software Engineer”,sayName : function () {
alert(this.name);}
};
constructor Person
Dynamic Nature of Prototypeschanges made to the prototype at any point are immediately reflected on instances, even the instances that existed before the change was made.
var friend= new Person();Person.prototype.sayHi = function(){
alert(“hi”);};friend.sayHi(); //”hi” - works!
Dynamic Nature of PrototypesWe cannot overwrite the entire prototype and expect the same behavior.The [[Prototype]] pointer is assigned when the constructor is called, so changing the prototype to a different object severs the tie between the constructor and the original prototype.function Person(){}var friend = new Person();Person.prototype = {
constructor: Person,sayName : function () {
alert(this.name);}
};friend.sayName(); //error
Native Object PrototypesNative object prototypes can be modified just like custom object prototypes,so methods can be added at any time.
String.prototype.startsWith = function (text) {return this.indexOf(text) == 0;};var msg = “Hello world!”;alert(msg.startsWith(“Hello”)); //true
The method is assigned to String.prototype, making it available to all strings in the environment.
Problems with PrototypesAll properties on the prototype are shared among instances, which is ideal for functions.
function Person(){}Person.prototype = {
constructor: Person,age : 29,friends : [“Shelby”, “Court”]
};
var person1 = new Person();var person2 = new Person();
person1.friends.push(“Van”);alert(person1.friends);//”Shelby,Court,Van”alert(person2.friends);//”Shelby,Court,Van”alert(person1.friends === person2.friends); //true
Combination Constructor/Prototype Pattern
The constructor pattern defines instance properties, whereas the prototype pattern defines methods and shared properties.
function Person(name){this.name = name;
this.friends = [“Shelby”];}
Person.prototype = {constructor: Person,sayName : function () {
alert(this.name); }};
var person1 = new Person(“Nicholas”);var person2 = new Person(“Greg”);
person1.friends.push(“Van”);alert(person1.friends);//”Shelby,Van”alert(person2.friends);//”Shelby”alert(person1.friends === person2.friends); //falsealert(person1.sayName === person2.sayName); //true
Dynamic Prototype Patternfunction Person(name){
//propertiesthis.name = name;
//methodsif (typeof this.sayName != “function”){
Person.prototype.sayName = function(){alert(this.name);};
}}
var friend = new Person(“Nicholas”);friend.sayName();
Parasitic Constructor Pattern
function Person(name, age, job){var o = new Object();o.name = name;o.age = age;o.job = job;o.sayName = function(){
alert(this.name);};return o;
}var friend = new Person(“Nicholas”, 29, “Software Engineer”);friend.sayName(); //”Nicholas
Durable Constructor Pattern
this
function Person(name, age, job){//create the object to returnvar o = new Object();//optional: define private variables/functions here//attach methodso.sayName = function(){
alert(name);};//return the objectreturn o;
}
A durable constructor vs parasitic constructor
In durable constructor - instance methods on the created object don’t refer to this- he constructor is never called using the new operator.
Prototype ChainingThe basic idea is to use the concept of prototypes to inherit properties and methods between two reference types.What if the prototype were actually an instance of another type?
- the prototype itself would 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.
Examplefunction SuperType(){
this.property = true;}
SuperType.prototype.getSuperValue = function(){return this.property;
};
function SubType(){this.subproperty = false;
}//inherit from SuperTypeSubType.prototype = new SuperType();SubType.prototype.getSubValue = function (){
return this.subproperty;};var instance = new SubType();alert(instance.getSuperValue());//true
Default PrototypesThe 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()
Prototype and Instance Relationships
The relationship between prototypes and instances is discernible in two ways. use the instanceof operator - returns true whenever an instance is used with a constructor that appears in its prototype chain
alert(instance instanceof Object); //truealert(instance instanceof SuperType); //truealert(instance instanceof SubType);//true
use the isPrototypeOf() method -
alert(Object.prototype.isPrototypeOf(instance));//truealert(SuperType.prototype.isPrototypeOf(instance));//truealert(SubType.prototype.isPrototypeOf(instance));//true
Working with Methodsoverride a supertype method or introduce new methods
- the methods must be added to the prototype after the prototype has been assigned.
...//inherit from SuperTypeSubType.prototype = new SuperType();//new methodSubType.prototype.getSubValue = function (){
return this.subproperty;};//override existing methodSubType.prototype.getSuperValue = function (){
return false;};
Problems with Prototype Chaining
The major issue revolves around prototypes that contain reference valuesfunction SuperType(){
this.colors = [“red”, “blue”, “green”];}
function SubType(){}
//inherit from SuperTypeSubType.prototype = new SuperType();var instance1 = new SubType();
instance1.colors.push(“black”);alert(instance1.colors);//”red,blue,green,black”var instance2 = new SubType();alert(instance2.colors);//”red,blue,green,black”
Problems with Prototype Chaining
we cannot pass arguments into the supertype constructor when the subtype instance is being created. In fact, there is no way to pass arguments into the supertype constructor without affecting all of the object instances.
Constructor Stealingthe apply() and call() methods can be used to execute a constructor on the newly created object.
function SuperType(){ this.colors = [“red”, “blue”, “green”];}function SubType(){
//inherit from SuperTypeSuperType.call(this);
}
var instance1 = new SubType();instance1.colors.push(“black”);alert(instance1.colors);//”red,blue,green,black”var instance2 = new SubType();alert(instance2.colors);//”red,blue,green”
Constructor Stealing - Passing Argumentsfunction SuperType(name){
this.name = name;}
function SubType(){//inherit from SuperType passing in an argumentSuperType.call(this, “Nicholas”);//instance propertythis.age = 29;
}var instance = new SubType();alert(instance.name);//”Nicholas”;alert(instance.age);//29
Problems with Constructor StealingMethods must be defined inside the constructor, so there’s no function reuse. Furthermore, methods defined on the supertype’s prototype are not accessible on the subtype, so all types can use only the constructor pattern
Combination InheritanceCombination inheritance (sometimes also called pseudoclassical inheritance) combines prototype chaining and constructor stealing to get the best of each approach
Addressing the downsides of both prototype chaining and constructor stealing, combination inheritance is the most frequently used inheritance pattern in JavaScript.
Examplefunction SuperType(name){
this.name = name;this.colors = [“red”, “blue”, “green”];
}
SuperType.prototype.sayName = function(){alert(this.name);
};
function SubType(name, age){//inherit propertiesSuperType.call(this, name);this.age = age;
}//inherit methodsSubType.prototype = new SuperType();
Prototypal Inheritancevar person = {name: “Nicholas”,friends: [“Shelby”, “Court”, “Van”]};
var anotherPerson = Object.create(person);anotherPerson.name = “Greg”;anotherPerson.friends.push(“Rob”);
var yetAnotherPerson = Object.create(person);yetAnotherPerson.name = “Linda”;yetAnotherPerson.friends.push(“Barbie”);
alert(person.friends);//”Shelby,Court,Van,Rob,Barbie”
Prototypal inheritance is useful when there is no need for the overhead of creating separate constructors, but you still need an object to behave similarly to another.
Parasitic Inheritancecreate a function that does the inheritance, augments the object in some way, and then returns the object as if it did all the work
function createAnother(original){var clone = object(original); //create a new object by calling a functionclone.sayHi = function(){
alert(“hi”);};return clone;
}
var person = {name: “Nicholas”,friends: [“Shelby”, “Court”, “Van”]
};
var anotherPerson = createAnother(person);anotherPerson.sayHi(); //”hi”