Post

JavaScript Learning Notes - Objects and Inheritance

JavaScript Learning Notes - Objects and Inheritance

Object properties

There are two types of properties in ES5, data properties and accessor properties.

Data attributes include [[writable]] (whether the value of an attribute can be modified), [[value]], and so on;

Accessor attributes include [[Configurable]] (whether the attribute can be deleted by delete, whether the attribute’s characteristics can be modified), [[Enumerable]] (whether the attribute can be returned by a for-in loop), [[Get]], [[Set]]

To modify a property, use Object.defineProperty(), which takes three arguments: the object the property is on, the name of the property, and a descriptor object. The properties of the descriptor object must be: configurable, enumerable, writable, and value, as in the following code

1
2
3
4
5
6
7
8
9
var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Nicholas"
});

alert(person.name); //"Nicholas"
delete person.name;
alert(person.name); //"Nicholas"

Multiple properties can also be defined using Object.defineProperties()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 objects

The factory pattern

1
2
3
4
5
6
7
8
9
10
11
12
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");

Abstracts the process of coming up with case-specific objects, each call to the function returns an object

Constructor mode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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");

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

The constructor does not explicitly create the object; it assigns properties and methods directly to the this object; and there is no return statement.

The steps to create an instance are as follows: create a new object; assign the constructor’s scope to the new object (this points to this new object); execute the code in the constructor; return the new object.

We can also treat the constructor as a function:

1
2
3
4
5
6
7
Person("Greg", 27, "Doctor");
window.sayName(); //"Greg"

//在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName();

And the main problem with constructors is that each method has to be created again on each instance, and functions of the same name on different instances are not equal.

Prototype object

The benefit of a prototype object is that it allows all object instances to share the properties and methods it contains. That is, instead of having to define information about the object instance in the constructor, this information can be added directly to the prototype object. Look at the following code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function Person(){}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    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"  ——来自实例
alert(hasPrototypeProperty(person, "name"));//false
alert(person1.hasOwnProperty("name"));//true
alert("name" in Person1);//true

alert(person2.name);//"Nicholas"  ——来自原型
alert(person2.hasOwnProperty("name"));//false
alert("name" in Person1);//true

delete person1.name;
alert(person1.name);//"Nicholas"  ——来自原型
alert(person1.hasOwnProperty("name"));//false
alert("name" in Person1);//true

Of course prototype objects have a drawback: all instances acquire the same property values by default, i.e., shareability. This problem is especially acute for properties containing values of reference types.

Therefore, it is better to use a combination of the constructor pattern and the prototype pattern. That is, instance properties are defined in the constructor and properties shared by all instances are defined in the prototype.

What is a prototype chain

JavaScript relies heavily on prototype chaining for inheritance.

What is a prototype chain? Let’s look at the relationship between constructors, prototypes, and instances: every constructor has a prototype object, prototype objects all contain a pointer to the constructor, and instances all contain an internal pointer to the prototype object. Look at the following code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}
function SubType(){
    this.subProperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
    return this.subproperty;
};

var instance = new SubType();
alert(instance.getSuperValue()); //true

instance.getSuperValue() goes through three steps when called:

  • Search for instances;
  • Search for SubType.prototype;
  • Searching for SuperType.prototype before finding the method in the last step.

And the default prototype of all functions is an instance of Object, so the complete prototype chain is shown below:

from "Advanced Programming in JavaScript (3rd Edition)"

That is, instance.__proto___ (the prototype of instance) points to SubType.prototype, SubType.prototype.__proto___ points to SuperType.prototype, and SuperType.prototype.__ proto___ points to Object.prototype, and finally Object.prototype.__proto___ points to null.

Sure enough, the object is still null at the end of the day! (Just kidding ==)

Problems with prototype chains

Prototype chaining can be used to implement inheritance, but it has some problems.

The main problem is that, as mentioned in the creation of the prototype object, prototype properties that contain values of reference types are shared by all instances.

Borrowing constructors

Given the above problem, we can execute the constructor on the newly created object by using apply() and call().

1
2
3
4
5
6
7
8
9
10
11
12
13
function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
    SuperType.call(this);//继承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"

In effect, we call the SuperType constructor in the context of a newly created instance of SubType. This way, each instance of SubType will have its own copy of the colors property.

There are also combinatorial inheritance, proto-type inheritance, parasitic inheritance, and so on. Head memory is limited, and will not be explored here. This article mainly refers to “Advanced Programming in JavaScript (3rd Edition)”.

Reference

Advanced Programming for JavaScript (3rd Edition)

Front-end key knowledge organization (JavaScript) IV: objects and inheritance

This post is licensed under CC BY 4.0 by the author.