Object-Oriented Programming
Understanding objects
There are two ways to create objects:
First: Create an Object object and add attributes and methods to it
var person = new Object(); person.name = "Nicholas"; person.age = 29; person.job = "Software Engineer"; person.sayName = function(){ alert(this.name); };
The second is object literal grammar
var person = { name: "Nicholas", age: 29, job: "Software Engineer", sayName: function(){ alert(this.name); } };
Two attribute types:
The first data attribute
- [[Configurable]: Indicates whether attributes can be redefined by deleting attributes, whether attributes can be modified, or whether attributes can be changed to accessor attributes. As in the previous example, attributes defined directly on objects have a default value of true.
- [[Enumerable]: Indicates whether properties can be returned through the for-in loop. As in the previous example, attributes defined directly on objects have a default value of true.
- [[Writable]: Indicates whether the value of an attribute can be modified. As in the previous example, attributes defined directly on objects have a default value of true.
- [[Value]: Data value containing this attribute. When reading attribute values, read from this location; when writing attribute values, save the new values in this location. The default value for this feature is undefined.
Modify the default properties of attributes: Object. defineProperty (attributes belong to objects, attributes name, attributes of descriptor objects)
var person = {}; Object.defineProperty(person, "name", { writable: false, value: "Nicholas" }); alert(person.name); //"Nicholas" person.name = "Greg"; alert(person.name); //"Nicholas"
This example creates an attribute called name whose value "Nicholas" is read-only. The value of this property is immutable. If you try to specify a new value for it, the assignment operation will be ignored in the non-strict mode. In the strict mode, the assignment operation will cause an error.
Note: When calling the Object.defineProperty() method, if not specified, the default values for configurable, enumerable, and writable properties are false.
The second accessor attribute
- [[Configurable]: Indicates whether attributes can be redefined by deleting attributes, whether attributes can be modified, or whether attributes can be changed to accessor attributes. As in the previous example, attributes defined directly on objects have a default value of true.
- [[Enumerable]: Indicates whether properties can be returned through the for-in loop. As in the previous example, attributes defined directly on objects have a default value of true.
- [[Get]: A function called when reading a property. The default value is undefined.
- [[Set]: A function that is called when a property is written. The default value is undefined.
Modify the default properties of attributes: Object. defineProperty (attributes belong to objects, attributes name, attributes of descriptor objects)
var book = { _year: 2004, edition: 1 }; Object.defineProperty(book, "year", { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }); book.year = 2005; alert(book.edition); //2
Define multiple attributes object. defineProperties (objects that add and modify their attributes, attributes that are added or modified in the object correspond to one another)
var book = {}; Object.defineProperties(book, { _year: { value: 2004 }, edition: { value: 1 }, year: { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } } )};
create object
- Factory model
- Constructor Pattern
- Prototype pattern
- Combining constructor pattern with prototype pattern
- Dynamic prototype model
- Parasitic constructor pattern
- Steady constructor model
Factory model:
function 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");
Defect: Not solving the problem of object recognition (that is, how to know the type of an object)
Constructor Pattern
function 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");
Differentiation from factory model
- Created objects are not displayed
- Assign attributes and methods directly to this object
- No return statement
The only difference between constructors and other functions is that they are called differently. However, constructors are functions after all, and there is no special grammar for defining constructors. Any function can be called as a constructor as long as it is called by a new operator, and any function, if it is not called by a new operator, is no different from a normal function.
// Use as a constructor var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //"Nicholas" // Call as a normal function Person("Greg", 27, "Doctor"); // Add to window window.sayName(); //"Greg" // Called in the scope of another object var o = new Object(); Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen"
Defect: The main problem with using constructors is that each method is recreated on each instance.
However, it is not necessary to create two Function instances that accomplish the same task; moreover, there is no need for this object to bind functions to specific objects before executing code at all. Therefore, this problem can be solved by transferring the function definition to the outside of the constructor as follows.
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
In this example, we move the definition of the sayName() function to the outside of the constructor. Inside the constructor, we set the sayName property to a global sayName function. Thus, since sayName contains a pointer to a function, the person1 and person2 objects share the same sayName() function defined in the global scope. This really solves the problem of two functions doing the same thing, but a new problem arises: a function defined in a global scope can actually only be called by an object, which makes the global scope a little misnomer. What is even more unacceptable is that if an object needs to define many methods, it needs to define many global functions, so our custom reference type is not encapsulated at all.
Prototype attributes of prototype patterns
function 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() person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
Understanding prototype objects:
Creating a function automatically creates a prototype attribute that points to the prototype object. By default, the prototype object automatically creates a constructor attribute
This attribute points to the function where the prototype attribute is located.
When a constructor is called to create a new instance, the instance contains a pointer that points to the prototype object of the constructor. In es5, this pointer is called [[protoType] (this property cannot be accessed directly)
Note: This link only exists between the instance and the prototype object of the constructor, not between the approximate instance and the constructor.
Although [[Prototype]] is not accessible in all implementations, it can be determined by the isPrototype Of () method.
Is there such a relationship between them?
alert(Person.prototype.isPrototypeOf(person1)); //true alert(Person.prototype.isPrototypeOf(person2)); //true
ECMAScript 5 adds a new method called Object. getPrototype Of (), which returns the value of [[Prototype]] in all supported implementations.
alert(Object.getPrototypeOf(person1) == Person.prototype); //true alert(Object.getPrototypeOf(person1).name); //"Nicholas"
The hasOwnProperty() method can detect whether an attribute exists in an instance or in a prototype. This method (don't forget that it inherits from Object) returns true only if a given attribute exists in an object instance
function 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(); var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false person1.name = "Greg"; alert(person1.name); //"Greg" - from an example alert(person1.hasOwnProperty("name")); //true alert(person2.name); //"Nicholas" - from the prototype alert(person2.hasOwnProperty("name")); //false delete person1.name; alert(person1.name); //"Nicholas" - from the prototype alert(person1.hasOwnProperty("name")); //false
The in operator returns true when a given attribute can be accessed through an object, whether it exists in an instance or in a prototype
function 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(); var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false alert("name" in person1); //true person1.name = "Greg"; alert(person1.name); //"Greg" - from instance alert (person1. hasOwnProperty ("name"); //true alert ("name" in person1); //true alert ("name" in person1); //true alert(person2.name); //"Nicholas" - from the prototype alert (person2. hasOwnProperty ("name"); //false alert ("name" in person2); //true delete person1.name; alert(person1.name); //"Nicholas" - from the prototype alert (person1. hasOwnProperty ("name"); //false alert ("name" in person1); //true
In the for-in loop, all enumerated attributes that can be accessed by objects are returned, including both attributes that exist in instances and attributes that exist in prototypes.
The Object.keys() method takes all enumerable instance attributes on an object. This method takes an object as a parameter and returns an array of strings containing all enumerable attributes.
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var keys = Object.keys(Person.prototype); alert(keys); //"name,age,job,sayName" var p1 = new Person(); p1.name = "Rob"; p1.age = 31; var p1keys = Object.keys(p1); alert(p1keys); //"name,age"
Literal Quantity Prototype Grammar
function Person(){} Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); }; }; //Note that resetting the constructor attribute in this way causes its [[Enumerable]] feature to be set to true. By default, so if you use a JavaScript engine compatible with ECMAScript 5, try Object.defineProperty(). Next, the native constructor attribute is not enumerable //Reset the constructor for ECMAScript 5 compatible browsers only Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person });
Defect: For attributes that refer to type values, multiple instances share the same attribute
function Person(){ } Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", friends : ["Shelby", "Court"], sayName : function () { alert(this.name); } }; 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
Here, the Person.prototype object has an attribute called friends, which contains an array of strings. Then, two instances of Person are created. Next, you modify the array referenced by person1.friends and add a string to the array. Since the friends array exists in Person.prototype rather than person1, the changes just mentioned are also reflected through person2. friends (pointing to the same array as person1.friends). If our original intention is to share an array in all instances like this, then I have nothing to say about this result. However, instances usually have all their own attributes. And that's why we seldom see people using prototype patterns alone.
Combining the constructor pattern with the prototype pattern:
function Person(name, age, job){ this.name = name; 3 this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; } Person.prototype = { constructor : Person, sayName : function(){ alert(this.name); } } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Count,Van" alert(person2.friends); //"Shelby,Count" alert(person1.friends === person2.friends);//false alert(person1.sayName === person2.sayName);//true
Essentially, constructor patterns are used to define instance attributes, while prototype patterns are used to define methods and shared attributes.
Dynamic prototype mode:
function Person(name, age, job){ //attribute this.name = name; this.age = age; this.job = job; //Method if (typeof this.sayName != "function"){ Person.prototype.sayName = function(){ alert(this.name); }; }; } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName();
The sayName() method is added to the prototype only if it does not exist. This code will only be executed when the constructor is first called. Since then, the prototype has been initialized and no further modifications are required.
Parasitic tectonic model:
The basic idea of this pattern is to create a function that simply encapsulates the code that created the object and then returns the newly created object; but on the surface, the function looks like a typical constructor.
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"
Essentially, apart from using the new operator and calling the wrapper function used a constructor, this pattern is exactly the same as the factory pattern.
This pattern adds a layer of encapsulation to the native constructor
function SpecialArray(){ //Create arrays var values = new Array() //add value values.push.apply(values, arguments); //Adding Method values.toPipedString = function(){ return this.join("|"); }; //Return Array return values; } var colors = new SpecialArray("red", "blue", "green"); alert(colors.toPipedString()); //"red|blue|green"
As for the parasitic constructor pattern, one thing needs to be explained: first, there is no relationship between the returned object and the constructor or the prototype attributes of the constructor; that is to say, the object returned by the constructor is not different from the object created outside the constructor. For this reason, the instanceof operator cannot be relied on to determine the object type. Because of the above problems, we recommend not to use this mode when other modes can be used.
A sound construction model:
A robust constructor follows a pattern similar to a parasitic constructor, but differs in two ways: one is that the instance method of the newly created object does not reference this; the other is that the constructor is called without using the new operator.
function Person(name, age, job){ //Create the object to return var o = new Object(); //Private variables and functions can be defined here //. . . //Adding Method o.sayName = function(){ alert(name); }; //Return object return o; } var friend = Person("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas"
inherit
- Prototype chain
- Borrowing constructors
- Combinatorial Inheritance
- Prototype inheritance
- Parasitic inheritance
- Parasitic combinatorial inheritance
Prototype chain:
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //Inherited SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ return this.subproperty; } var instance = new SubType(); alert(instance.getSuperValue());//true
The pointer of the SubType prototype object should have pointed to the SubType function. Now assign the SuperType instance to SubType.prototyped, and then the pointer of the SubType prototype object points to the SuperType prototype object (the pointer of the SuperType instance points to the SuperType prototype object).
All reference types inherit Object by default. Here is the complete prototype chain
Two methods to determine the relationship between prototype and instance: instanceof (), isPrototypeof ()
The instance of operator, which is used to test constructors that appear in the instance and prototype chains, returns true.
alert(instance instanceof Object);//true alert(instance instanceof SuperType);//true alert(instance instanceof SubType);//true
isPrototypeOf() method. Similarly, as long as the prototype appears in the prototype chain, it can be said to be the prototype of the instance derived from the prototype chain, so the isPrototypeOf() method also returns true.
alert(Object.prototype.isPrototypeOf(instance));//true alert(SuperType.prototype.isPrototypeOf(instance));//true alert(SubType.prototype.isPrototypeOf(instance));//true
Carefully define methods:
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //Inherited SuperType SubType.prototype = new SuperType(); //Adding new methods SubType.prototype.getSubValue = function (){ return this.subproperty; }; //Rewrite the method SubType.prototype.getSuperValue = function () in the supertype{ return false; }; var instance = new SubType(); alert(instance.getSuperValue()); //false
The first method, getSubValue(), was added to SubType. The second method, getSuperValue(), is a method that already exists in the prototype chain, but rewriting this method will shield the original method. In other words, when getSuperValue() is invoked through an instance of SubType, the redefined method is called; but when getSuperValue() is invoked through an instance of SuperType, the original method will continue to be invoked. It is particularly important to note here that these two methods must be defined after replacing the prototype with an instance of SuperType.
Note: When implementing inheritance through prototype chains, you cannot use object literals to create prototype methods. Because doing so will rewrite the prototype chain
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //Inherited SuperType SubType.prototype = new SuperType(); //Using literal quantities to add new methods will invalidate the previous line of code SubType.prototype={ getSubValue : function (){ return this.subproperty; }, someOtherMethod : function (){ return false; } }; var instance = new SubType(); alert(instance.getSuperValue()); //error!
Defect: The main problem comes from prototypes that contain reference type values, and the inability to pass parameters to supertype constructors when creating instances of subtypes
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ } //Inherited SuperType SubType.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"
Borrowing constructors
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ //Inherited SuperType SuperType.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"
Transfer parameters to supertype constructors in subtype constructors:
function SuperType(name){ this.name = name; } function SubType(){ //It inherits SuperType and passes parameters. SuperType.call(this, "Nicholas"); //Instance attributes this.age = 29; } var instance = new SubType(); alert(instance.name); //"Nicholas"; alert(instance.age); //29
Defect: If you borrow only constructors, you will inevitably have problems with constructor patterns - methods are defined in constructors, so function reuse is out of the question. Moreover, the methods defined in the supertype prototype are also invisible to the subtypes, resulting in the use of constructor patterns only for all types. Considering these problems, the technique of borrowing constructors is seldom used alone.
Combinatorial Inheritance
The prototype chain and borrowed constructor technology are combined together to give full play to the advantages of both an inheritance model. The idea behind it is to inherit prototype attributes and methods by using prototype chains, and to inherit instance attributes by borrowing constructors.
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ //Inheritance attributes this.name, this.colors SuperType.call(this, name); this.age = age; } //Inheritance method sayName () SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); alert(instance1.colors);//"red,blue,green,black" instance1.sayName();//"Nicholas"; instance1.sayAge();//29 var instance2 = new SubType("Greg", 27); alert(instance2.colors);//"red,blue,green" instance2.sayName();//"Greg"; instance2.sayAge();//27
Prototype inheritance
With prototypes, new objects can be created based on existing objects without creating custom types.
function object(o){ function F(){}; F.prototype = 0; return new F(); }
Inside the object() function, a temporary constructor is created, then the incoming object is used as the prototype of the constructor, and finally a new instance of the temporary type is returned. Essentially, object() performs a shallow copy of the object passed in.
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
This prototype inheritance requires that you have one object as the basis for another. If there is such an object, you can pass it to the object() function, and then modify the object according to the specific requirements. In the above example, person is the base object
ECMAScript 5 normalizes prototype inheritance by adding Object. create (objects, (optional) objects with additional attributes defined by new objects)
var 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"
The second parameter and the second parameter type of the Object.defineProperties method
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = Object.create(person, { name: { value: "Greg" } }); alert(anotherPerson.name); //"Greg"
Parasitic inheritance
The idea of parasitic inheritance is similar to that of parasitic constructors and factory models.
function createAnother(original){ varclone=object(original); //Create a new object by calling a function clone.sayHi = function(){ //Enhance the object in some way alert("hi"); }; return clone;//Enhance the object in some way } //Call mode var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); //"hi"
Defect: Reduced efficiency due to inability to reuse functions; this is similar to the constructor pattern.
Parasitic combinatorial inheritance
That is to say, attributes are inherited by borrowing constructors and methods are inherited by mixing prototype chains. The basic idea behind this is that we don't need to call a supertype constructor to specify a prototype of a subtype. All we need is a copy of the supertype prototype. Essentially, parasitic inheritance is used to inherit a supertype prototype, and then the result is assigned to a prototype of a subtype.
function inheritPrototype(subType, superType){ var prototype = object(superType.prototype);//create object prototype.constructor = subType;//Enhanced Objects subType.prototype = prototype;//Specified object }
This function takes two parameters: a subtype constructor and a supertype constructor. Inside the function, the first step is to create a copy of the supertype prototype. The second step is to add constructor attributes to the created copy to compensate for the default constructor attributes lost by rewriting the prototype. The last step is to assign the newly created object (i.e., copy) to the prototype of the subtype
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function(){ alert(this.age); }