javascript Advanced Programming Chapter 6 Object-Oriented Programming

Keywords: Attribute ECMAScript Javascript Programming

6. Object-Oriented Programming

6.1 Understanding Objects

6.1.1 Attribute Type

There are two kinds of attributes: data attributes and accessor attributes.
js engine, js can not be directly accessed.
Four characteristics describing their behavior.
Can [[Configurable]] redefine attributes by deleting attributes, modify attributes, or modify attributes to accessor attributes? Default true
Whether [[Enumerable]] can return properties through the for-in loop, defaulting to true
Can [[Writable]] Modify the default true value of the attribute
[[Value]] contains the data value of this attribute. Default undefined

var person={};
Object.defineProperty(person,"name",{
    writable:false,
    value:"Nicholas"
});
alert(person.name);//"Nicholas"
person.name="Greg";
alert(person.name);//"Nicholas"

When using Object.defineProperty(), if not specified, configurable, enumerable and writable default false

Accessor properties
[[Get]] The function called when reading attributes defaults undefined
[[Set]] The function invoked when setting properties defaults undefined

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

6.1.2 Defines multiple attributes

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;
            }
        }
    }
});

6.1.3 Reading Properties

var descriptor=Object.getOwnPropertyDescriptor(book,"_year");
alert(descriptor.value);//2004
alert(descriptor.configurable);//false

6.2 Create Objects

6.2.1 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("a",10,"engineer");
var person2=createPerson("b",20,"teacher");

Advantages: Solves the problem of creating multiple similar objects
Disadvantage: There is no solution to the problem of object recognition, that is, how to know the type of an object. Only person1 instance of Object is true

6.2.2 Constructor Model

//Created objects are not displayed
//Assign attributes and methods directly to this object
//No return statement
function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=function(){
        alert(this.name);
    }
}

var person1=new Person("a",10,"engineer");
var person2=new Person("b",20,"teacher");

alert(person1.constructor == Person);//true
alert(person1 instanceof Object);//true
alert(person1 instanceof Person);//true

The constructor must be invoked using the new operator through four steps:
1. Create a new object
2. Assign the scope of the constructor to the new object, so this points to the new object.
3. Code to execute constructors
4. Return a new object

If you call a constructor as a function, it's the same as a normal function.

//Constructor
var person=new Person("a",10,"engineer");
person.sayName();//"a"

//Ordinary function
Person("a",10,"engineer");
window.sayName();//"a"

//Called in the scope of another object
var o=new Object();
Person.call(o,"a",10,"engineer");
o.sayName();//"a"

The Problem of Constructor
Both person1 and person2 have a method called sayName(), but it's not an instance of the same Function that wastes memory. Functions in ECMAScript are objects, so for each function defined, an object is instantiated.

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=new Function("alert(this.name);");//Logically equivalent to declarative functions
}

alert(person1.sayName == person2.sayName);//false

Solution

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=sayName;
}

function sayName(){
    alert(this.name);
}

New problem: Functions defined in global scope can only be invoked by an object. If an object needs to define many methods, it needs to define many global functions, which is unreasonable.

6.2.3 Prototype Model

Each function has a prototype attribute, which is a pointer to an object and is used to contain properties and methods that can be shared by all instances of a particular type. The advantage is that all object instances share the attributes and methods contained in the prototype object.

function Person(){
}
Person.prototype.name="a";
Person.prototype.sayName=function(){
    alert(this.name);
}
var person1=new Person();
person1.sayName();
var person2=new Person();
person2.sayName();
alert(person1.name == person2.name);//true

1. Understanding prototype objects

By default, all prototype objects automatically get a constructor (constructor) property that points to the pointer to the function in which the prototype property resides.

//isPrototypeOf()
alert(Person.prototype.isPrototypeOf(person1));//true

//getPrototypeOf()
alert(Object.getPrototypeOf(person1) == Person.prototype);//true

//Find if the instance has a name attribute, and then look for the prototype. Even if the instance attribute is set to null, it will not restore the attributes pointing to the prototype. It can only delete the instance attribute by delete.
alert(person1.name);

//hasOwnProperty() checks for instance attributes
person1.name="a";
alert(person1.hasOwnProperty("name"));//true

delete person1.name;
alert(person1.hasOwnProperty("name"));//false

2. Prototype and in operator

The in operator returns true when a given attribute can be accessed by an object, whether in an instance or in a prototype.

//Check whether the prototype has this property
function hasPrototypeProperty(object,name){
    return !object.hasOwnProperty(name) && (name in object);
}

//Gets all enumerable instance attributes on an object
var keys = Object.keys(Person.prototype);
alert(keys);//"name,obj,job,sayName"

//Gets all instance attributes on an object, whether enumerable or not
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys);//"constructor,name,obj,job,sayName"

3. Simpler Prototype Grammar

function Person(){
}
//This syntax essentially overrides the default prototype object, so the constructor attribute no longer points to Person.
Person.prototype = {
    //Manually add constructor:Person,
    name:"a",
    sayName:function(){
        alert(this.name);
    }
};
var person1=new Person();
alert(person1 instanceof Person);//true
alert(person1.constructor == Person);//false

4. Dynamics of Prototypes

//Any modification to the prototype object can be immediately reflected from the instance, even if the instance is created first and then the prototype is modified.
var p=new Person();
Person.prototype.sayHi=function(){
    alert("hi");
}
p.sayHi();//No problem

Rewrite the object pointed by the pointer that modifies the prototype properties of the constructor

//But rewriting doesn't work.
var p=new Person();
Person.prototype={
    constructor:Person,
    name:"a",
    sayName:function(){
        alert(this.name);
    }
};

p.sayName();//error

5. Prototype of Native Object

Methods such as Object, Array, String, etc. all define methods on the prototype of their constructors.

6. The Problem of Prototype Objects

Object attributes are generally defined in constructors because definitions are shared on prototypes.

6.2.4 combines constructor pattern and prototype pattern

//The constructor pattern is used to define instance attributes, and the prototype pattern is used to define methods and share attributes. The most widely used
function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.friends=["a","b"];
}

Person.prototype={
    constructor:Person,
    sayName:function(){
        alert(this.name);
    }
}

6.2.5 Dynamic Prototype Model

Developers with other OO language experience may be confused when they see independent constructors and prototypes. The dynamic prototype pattern is dedicated to solving this problem, that is, encapsulating all information in constructors.

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.friends=["a","b"];

    //The constructor is not executed until it is first called
    if(typeof this.sayName != "function"){
        Person.prototype.sayName=function(){
            alert(this.name);
        };
    }
}

6.2.6 Parasitic Constructor Model

//Just encapsulate the code that creates the object, and then return the new object
function SpecialArray(){
    var values = new Array();
    values.push.apply(values,arguments);
    //Do not modify the Array constructor to add additional methods, but create a function object every time you create it, wasting memory?
    values.toPipedString = function(){
        return this.join("|");
    };
    //The constructor returns a new object instance by default without returning a value. By adding a return statement at the end of the constructor, you can override the value returned when the constructor is called.
    return values;
}

var colors = new SpecialArray("reb","green","blue");
var colors2 = new SpecialArray("reb","green","blue");

alert(colors.toPipedString());
alert(colors instanceof SpecialArray);//false
alert(colors instanceof Array);//true
alert(colors instanceof Object);//true
alert(colors.toPipedString ==  colors2.toPipedString);//false

In this mode, there is no relationship between the returned object and the constructor or prototype attributes, that is to say, it is no different from the object created outside the constructor. You cannot use instanceof to determine the type of object, you can use other modes instead of using this mode.

6.2.7 Steady Constructor Model

A secure object means that there are no public attributes, and its methods do not reference this object, nor do they use the new operator to call the constructor.
Reference link: https://www.zhihu.com/question/25101735/answer/36695742

function Person(name,age,job){
    var o = new Object();

    //Private variables and functions can be defined here
    //Every member who is supposed to be private should not be attached to the property of the object o returned by Person, but to public. Of course, privates and publics here are formally analogous to other OO languages, and their implementation principle is the set of domains, closures and objects in js. It feels very ingenious.
    var name2=name;

    o.sayName=function(){
        alert(name);
    };

    o.sayName2=function(){
        alert(name2);
    };

    return o;
}

var friend = Person("Nicholas",29,"Software Engineer");
friend.sayName();//"Nicholas"
friend.sayName2();//"Nicholas"
alert(friend.name2);//undefined

This saves a secure object that has no way to access its data members except by calling the sayName() method. Even if other code adds methods or data members to the object, there is no other way to access the raw data passed into the constructor.

6.3 inheritance

ECMAScript only supports the implementation of inheritance, and its implementation of inheritance mainly depends on prototype chains.

6.3.1 prototype chain

The essence of implementation is to rewrite the prototype object and replace it with a new type of instance.

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
alert(instance.constructor);//function SuperType()....

By implementing the prototype chain, the prototype search mechanism is essentially extended.
When searching for attributes:
1. Search examples
2. Search SubType.prototype
3. Search SuperType.prototype
4. Search Object.prototype
Until it is found, when attributes or methods cannot be found, the search process will not stop until the end of the prototype chain is moved one by one.

1. Don't forget the default prototype

All reference types inherit Object by default, and this inheritance is also achieved through prototype chains.
The default prototype of all functions is an instance of Object, so the default prototype contains an internal pointer to Object.prototype. This is the fundamental reason why all custom types inherit default methods such as toString.

2. Determine the relationship between prototype and instance

alert(instance instanceof Object);//true
alert(instance instanceof SuperType);//true
alert(instance instanceof SubType);//true

alert(Object.prototype.isPrototypeOf(instance));//true
alert(SuperType.prototype.isPrototypeOf(instance));//true
alert(SubType.prototype.isPrototypeOf(instance));//true

As long as the prototype appears in the prototype chain, it can be said that it is the prototype of the instance derived from the prototype chain.

3. Carefully define methods

The code that adds methods to the prototype must be placed after the statement that replaces the prototype.

4. The Problem of Prototype Chain

1. When inheritance is realized through prototype, prototype will actually become another type of instance. The original instance attributes are now prototype attributes. If it is a prototype that contains reference type values, one instance modification affects another instance.
2. When creating an instance of a subtype, you cannot pass parameters to a supertype constructor.
Therefore, prototype chains are seldom used alone in practice.

6.3.2 Borrowing constructors

Sometimes it's called forgery or classical inheritance.

function SuperType(){
    this.color = ["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"

1. Transfer parameters

function SuperType(name){
    this.name=name;
}

function SubType(){
    SuperType.call(this,"a");
    this.age=29;
}

var instance = new SubType();
alert(instance.name);
alert(instance.age);
console.log(instance);

When the SuperType constructor is called inside the SubType constructor, the name attribute is actually set for the instance of SubType. To ensure that the SuperType constructor does not override the attributes of the subtype, you can add attributes that should be defined in the subtype after calling the supertype constructor.

2. The Problem of Borrowing Constructor

If we just borrow constructors, we will not be able to avoid the problem of constructor patterns - methods are defined in constructors, so function reuse is impossible. And the methods defined in the supertype prototype are also invisible to the subtypes. So it is seldom used alone.

6.3.3 Combinatorial Inheritance

The idea behind the combination of prototype chain and borrowed constructor technology is to use prototype chain to inherit prototype attributes and methods, and borrow constructor to inherit instance attributes.

function SuperType(name){
    this.name = name;
    this.color = ["red","blue","green"];
}

SuperType.prototype.sayName = function(){
    alert(this.name);
}

function SubType(name.age){
    //Inheritance attribute
    SuperType.call(this,name);

    this.age = age;
}

//Inheritance method
SubType.prototype = new SuperType();

SubType.prototype.sayAge = function(){
    alert(this.age);
}

Combinatorial inheritance avoids the drawbacks of prototype chains and borrowed constructors, combines their advantages and becomes the most commonly used inheritance mode in javascript. instanceof and isPrototypeOf() can also identify objects created based on composite inheritance.

6.3.4 Prototype Inheritance

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

Essentially, object() performs a shallow copy of the object passed in.
ECMAScript5 standardizes prototype inheritance by adding Object.create(). Attributes that contain reference type values always share the corresponding values, with the same problem as prototype chains.

6.3.5 Parasitic Inheritance

The idea of parasitic inheritance is similar to that of parasitic constructors and factory models.

function createAnother(original){
    var clone = object(original);//Create a new object by calling a function
    clone.sayHi = function(){//Enhance the object in some way
        alert("hi");
    };
    return clone;//Return this object
}

The problem is that function reuse can not be achieved to reduce efficiency.

6.3.6 Parasitic Combinatorial Inheritance

Composite inheritance is the most commonly used inheritance pattern in Javascript. The problem is that in any case, two supertype constructors are called: one when creating a prototype of a subtype and the other inside the subtype constructor. Subtypes eventually contain all instance attributes of supertype objects, but we have to override these attributes when we call a subtype constructor.

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);//The second call to SuperType()

    this.age = age;
}

SubType.prototype = new SuperType();//The first call to SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
}

Ultimately, there are two sets of name and age attributes, one in the instance and one in the Subtype prototype. The solution is to use parasitic combinatorial inheritance.
Parasitic combinatorial inheritance refers to inheriting attributes by borrowing constructors and inheriting methods 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.

//Two parameters: a subtype constructor and a parent constructor
function inheritPrototype(subType,superType){
    var prototype = object(superType.prototype);//create object
    prototype.constructor = subType;//Enhance objects to compensate for default constructor attributes lost by rewriting prototypes
    subType.prototype = prototype;//Specified object
}

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);
}

Efficiency is reflected in the fact that it only calls the SuperType constructor once, and thus avoids creating unnecessary and redundant attributes on SubType.prototype. At the same time, the prototype chain can remain unchanged. Therefore, instanceof and isPrototype Of () can be used normally. Parasitic combinatorial inheritance is the most ideal inheritance paradigm for reference types.

Posted by uramagget on Tue, 04 Jun 2019 12:29:38 -0700