JavaScript interview volume -- complex object model creation

Posted by bbauder on Wed, 19 Jan 2022 08:11:44 +0100

Then, the previous article said that the object created in the previous article does not provide an entry to set the attribute value directly to the outside. The default value is given when the new object is created.

This article specifies the property value when creating a new instance.

A special idiom for setting default values is used in the above JavaScript definition:

this.name = name || "";

The JavaScript logic or operator (|) will judge the first parameter. If the result of the parameter value operation is true, the operator returns the value. Otherwise, the operator returns the value of the second parameter. Therefore, this line of code first checks whether name is a valid value for the name attribute. If so, set it to this The value of name. Otherwise, set this The value of name is an empty string.

As defined above, when creating an instance of an object, you can specify values for locally defined properties.

var jane = new Engineer("belau");

At this point, Jane's properties are as follows:

jane.name == "";
jane.dept == "engineering";
jane.projects == [];
jane.machine == "belau"

Note that from the class definition above, you cannot specify an initial value for an inherited property such as name. If you want to specify initial values for inherited properties in JavaScript, you need to add more code to the constructor function.

The following is the redefinition of the Engineer constructor:

function Engineer (name, projs, mach) {
 this.base = WorkerBee;
 this.base(name, "engineering", projs);
 this.machine = mach || "";}

Suppose you create a new Engineer object, as follows:

var jane = new Engineer(
    "Doe, Jane", 
    ["navigator", "javascript"], 
    "belau");

When executed, JavaScript will have the following steps:

  1. The new operator creates a new generic object and__ proto__ Property is set to engineer prototype.
  2. The new operator passes the new object to the Engineer constructor as the value of this.
  3. The constructor creates a new property named base for the new object and points to the constructor of WorkerBee. This makes the WorkerBee constructor a method of the Engineer object. The name of the base attribute is not special. We can use any other legal name instead; Base is just to get close to its purpose.
  4. The constructor calls the base method, passes two of the parameters passed to the constructor to the base method as parameters, and also passes a string parameter "engineering". The explicit use of "engineering" in the constructor indicates that the dept attribute inherited by all Engineer objects has the same value, and the value overloads the value inherited from Employee.
  5. Because base is a method of Engineer, when calling base, JavaScript binds the object created in step 1 to this keyword. Thus, the WorkerBee function then passes the "Doe, Jane" and "engineering" parameters to the Employee constructor function. When returned from the Employee constructor function, the WorkerBee function sets the projects property with the remaining parameters.
  6. When returned from the base method, the Engineer constructor initializes the machine attribute of the object to "belau".
  7. When returned from the constructor, JavaScript assigns the new object to the Jane variable.

It can be considered that the constructor of WorkerBee is called in the constructor of Engineer, and the inheritance relation is set for Engineer object. This is not the case. Calling the workerbee paparazzi ensures that the engineer object is called with all properties in the constructor. However, if properties are subsequently added to the Employee or workerbee prototype, those properties will not be inherited by the engineer object. For example, suppose the following statement:

function Engineer (name, projs, mach) {
 this.base = WorkerBee;
 this.base(name, "engineering", projs);
 this.machine = mach || "";
}
var jane = new Engineer(
        "Doe, Jane", 
        ["navigator", "javascript"], 
        "belau");
Employee.prototype.specialty = "none";

The object jane does not inherit the specialty property. You must explicitly prototype to ensure dynamic inheritance. If it is modified to the following statement:

function Engineer (name, projs, mach) {
 this.base = WorkerBee;
 this.base(name, "engineering", projs);
 this.machine = mach || "";
}
var jane = new Engineer(
         "Doe, Jane", 
         ["navigator", "javascript"], 
         "belau");
Employee.prototype.specialty = "none";

Now the specialty attribute of the jane object is "none".

Another way to inherit is to use the call() / apply() method.

function Engineer (name, projs, mach) {
 WorkerBee.call(this, name, "engineering", projs);
 this.machine = mach || "";}

2, Local and inherited values

When accessing the properties of an object, JavaScript will perform the following steps:

  1. Check whether the local value exists. If it exists, this value is returned
  2. If the local value does not exist, check the prototype chain (through the _proto attribute)
  3. If an object in the prototype chain has a value for the specified property, the value is returned.
  4. If such a property does not exist, the object does not have it.

The results of the above steps depend on how they are defined. The earliest examples have the following definitions:

function Employee () {
 this.name = "";
 this.dept = "general";
}
function WorkerBee () {
 this.projects = [];
}
WorkerBee.prototype = new Employee;

Based on these definitions, it is assumed that the instance amy of WorkerBee is created through the following statement:

var amy = new WorkerBee;

The amy object will have a local property, projects. The name and dept attributes are not local to the amy object, but from the amy object__ proto__ Property. Therefore, amy will have the following attribute values:

amy.name == "";
amy.dept == "general";
amy.projects == [];

1. Priority local value

Now, suppose you modify the value of the name attribute in the prototype related to Employee:

Employee.prototype.name = "Unknown"

At first glance, you may feel that the new value is passed to all instances of Employee. However, this is not the case.

When you create any instance of an Employee object, the name property of that instance gets a local value (an empty string). This means that when creating a new Employee object as the prototype of WorkerBee, WorkerBee The name attribute of prototype will have a local value. Therefore, when JavaScript looks for the name attribute of the amy object (an instance of WorkerBee), JavaScript will find WorkerBee Local value in prototype. Therefore, you will not continue to find Employee up in the prototype chain Prototype.

2. Modify an attribute value of all descendants

If you want to modify the property value of an object at run time and want the value to be inherited by all descendants of the object, you cannot define the property in the constructor function of the object. Instead, you should add this property to the prototype associated with the object. For example, suppose you modify the previous code as follows:

function Employee () {
 this.dept = "general";
}
Employee.prototype.name = "";
function WorkerBee () {
 this.projects = [];
}
WorkerBee.prototype = new Employee;
var amy = new WorkerBee;
Employee.prototype.name = "Unknown";

3. Judge the relationship between instances

The attribute lookup mechanism of JavaScript first looks in the attributes of its own object. If the specified attribute name is not found, it will be found in the special attributes of the object__ proto__ Find in. This process is recursive; It is called "find in prototype chain".

Special__ proto__ Property is set when the object is built; Set to the value of the prototype property of the constructor. All expressions new Foo() will create an object whose__ proto__ == Foo.prototype. Therefore, modify foo The properties of prototype will change the search of the properties of all objects created through new Foo().

Each object has one__ proto__ Object attributes (except object); Each function has a prototype object attribute. Therefore, through "prototype inheritance", objects form relationships with other objects. By comparing objects__ proto__ Property and the prototype property of the function can detect the inheritance relationship of the object. JavaScript provides a convenient method: the instanceof operator can be used to detect an object and a function. If the object inherits the prototype of the sub function, the operator returns true. For example:

var f = new Foo();
var isTrue = (f instanceof Foo);

As a more detailed example, suppose we use the same set of definitions as in inherited properties. Create the Engineer object as follows:

var chris = new Engineer("Pigman, Chris", ["jsd"], "fiji");

All of the following statements are true for this object:

chris.__proto__ == Engineer.prototype;
chris.__proto__.__proto__ == WorkerBee.prototype;
chris.__proto__.__proto__.__proto__ == Employee.prototype;
chris.__proto__.__proto__.__proto__.__proto__ == Object.prototype;
chris.__proto__.__proto__.__proto__.__proto__.__proto__ == null;

Based on this, you can write an instanceOf function as follows:

function instanceOf(object, constructor) {
  while (object != null) {
     if (object == constructor.prototype)
        return true;
     if (typeof object == 'xml') {
       return constructor.prototype == XML.prototype;
     }
     object = object.__proto__;
  }
  return false;
}
instanceOf (chris, Engineer)
instanceOf (chris, WorkerBee)
instanceOf (chris, Employee)
instanceOf (chris, Object)

But the following expression is false:

instanceOf (chris, SalesPerson)