Each Object is created based on a reference type, which can be a native type such as Object type, Array type, Date type, RegExp type, primitive wrapper type, or custom type.

Understanding object

Ok, first let’s look at creating a custom object as an object literal:

var person = {
 name: 'Fly_001'.age: 22.sayName: function() {
     alert(this.name); }};Copy the code

These attributes are created with eigenvalues that JavaScript uses to define their behavior.

ECMAScript defines properties that are only used internally. It describes the various characteristics of a property. To indicate that a property is an internal value, the specification places them in two pairs of square brackets, such as [[Enumerable]], and cannot be accessed directly in JavaScript.

There are two types of properties in ECMAScript, data properties and accessor properties:

  • Data attributes

    Data attributes have four characteristics that describe their behavior:

    The name of the describe
    [[Configurable]] Indicates whether an attribute can be redefined by deleting it via delete. Default is true.
    [[Enumerable]] Indicates whether a for-in loop ♻️ can return a property. Default is true.
    [[Writable]] Indicates whether the value of an attribute can be modified. Default is true.
    [[Value]] Contains the data value for this property. Read and write property values are in this position, default is undefined.

    To modify the properties of a property, use the object.defineProperty () method (in other words, this is how Vue implements two-way data binding), which takes three arguments: The object of the property, the name of the property, and a descriptor object, which can be any or more of different, Enumerable, writable, and Value:

    var person = {};
    Object.defineProperty(person, 'name', {
        writable: false.value: 'Fly_001'
    });
    
    alert(person.name); // 'Fly_001';
    
    person.name = 'juejin'; // Modify the name attribute of person;
    alert(person.name); / / 'Fly_001';
    Copy the code

    The above code creates a name attribute whose value is read-only and unmodifiable, and attempts to specify a new value for it will be ignored. (Cannot assign to read only property ‘name’ of object thrown in strict mode)

    The writable feature can be modified by calling the Object.defineProperty() method without any additional information. The additional information is freely configurable, and the writable property is configurable again.

    The object-.defineProperty () method may not provide these advanced features in most cases, but understanding these concepts is useful for understanding JavaScript objects.

  • Accessor properties

    The accessor property contains a pair of getter and setter functions and has the following four properties:

    Characteristics of describe
    [[Configurable]] Indicates whether an attribute can be redefined by deleting it through delete and whether the attributes of the attribute can be modified.
    [[Enumerable]] Indicates whether the attribute can be returned through the for-in loop ♻️.
    [[Get]] Function to be called when reading a property. Default is undefined.
    [[Set]] Function to be called when a property is written. Default is undefined.

    Similarly, the drop accessor property cannot be defined directly and must be defined using Object.defineProperty() :

    var book = {
        _year = 2018.edition: 1
    }
    
    Object.defineProperty(book, 'year', {
        get: function() {
            return this._year;
        },
        set: function(newYear) {
            if (newYear > 2018) {
                this._year = newYear;
                this.edition += newYear - 2018; }}}); book.year =2020;
    alert(book.edition); / / 3;
    Copy the code

    The underscore before _year is a common notation for properties that can only be accessed through object methods.

    Also, you don’t have to specify both the getter and setter.

    Specifying getter only means that the property is unwritable 🙅, and specifying setter only means that it is unreadable 🙅 ~

Defining multiple properties

Because it is possible to define multiple properties for an Object, ECMAScript also defines an object.defineProperties () method that can define multiple properties at once. This method takes two Object parameters: the target Object and the property to be added or modified:

var book = {};

Object.defineProerties(book, {
    _year: {
        value: 2018
    },
    edition: {
        value: 1
    },
    year: {
        get: function() {
            return this._year;
        },
        set: function(newYear) {
            if (newYear > 2018) {
                this._year = newYear;
                this.edition += newYear - 2018; }}}});Copy the code

The code above defines two data attributes (_year and edition) and one access attribute (year) on book, and it’s worth noting that the attributes here are all created at the same time.

Read properties of properties

Now that can modify properties of features, it should be able to retrieve attributes feature, so the ECMAScript and provide us with the Object. The getOwnPropertyDescriptior () method, this method accepts two parameters: the attributes of objects and to read the name.

Its return value is an object.

If the object is a data property, the properties of the object can be different, Enumerable, Writable and Value.

If it is an accessor property, the object properties are Different, Enumerable, GET, and set.

For example 🌰 :

var descriptior = Object.getOwnPropertyDescriptior(book, '_year');
alert(descriptior.value); / / 2018;
alert(descriptior.configurable); // false;
Copy the code

Tips: in JavaScript, for any Object, including DOM and BOM Object, use Object. GetOwnPropertyDescriptior () method.

Create an object

While it is possible to create a single Object using either the Object constructor or Object literals, creating many objects using the same interface creates a lot of duplicate code.

To solve this problem, we can use a variation of the factory pattern.

  • The factory pattern

    The factory pattern encapsulates the details of the object created by the 📦 interface with functions:

    function createPerson(name, age) {
        var o = new Object(a); o.name = name; o.age = age; o.sayName =function() {
            alert(this.name);
        };
        return o;
    }
    
    var person1 = createPerson('Fly_001'.22);
    var person1 = createPerson('juejin'.24);
    Copy the code

    The factory pattern solved the problem of creating multiple similar objects, but it didn’t solve the problem of object recognition (how to know the type of an object), so another pattern, the constructor pattern, emerged.

  • Constructor pattern

    ECMAScript constructors can be used to create objects of a specific type to define their properties and methods:

    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.sayName = function() {
            alert(this.name);
        };
    }
    
    var person1 = new Person('Fly_001'.22);
    var person1 = new Person('juejin'.24);
    Copy the code

    We notice that the code in Person() differs from that in createPerson() :

    1. Create objects without showing them
    2. Attributes and methods are assigned directly to this object
    3. No return statement

    Tips: By convention, constructors should always start with a capital letter.

    To create a new instance of Person, you must use the new operator, and calling the constructor in this way goes through four steps:

    1. Create a new object;
    2. Assign the constructor’s scope to the new object (so this refers to the new object);
    3. Execute the code in the constructor (add attributes for the new object);
    4. Returns a new object.

    Thus, person1 and person2 each hold a different instance of Perosn:

    alert(person1.constructor == Person); // true;
    alert(person2.constructor == Person); // true;
    
    alert(person1 instanceof Person); // true;
    alert(person2 instanceof Person); // true;
    Copy the code

    The constructor pattern can be used to identify its instance as a specific type, which is where the constructor pattern prevails over the factory pattern.

    The constructor pattern is useful, but it has the disadvantage that each method has to be recreated on each instance. From a logical point of view, constructors can also be defined like this:

    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.sayName = new function('alert(this.name)');
    }
    Copy the code

    Also, functions with the same name on different instances are not equal:

    alert(person1.sayName == person2.sayName); // false;
    Copy the code

    So creating two instances of Function that do the same thing isn’t really necessary, but these problems can be solved with the prototype pattern.

  • The prototype pattern

    Each function we create has a Prototype property, which is a pointer to an object whose purpose is to contain properties and methods that can be shared by all instances of a particular type.

    Prototype is the prototype object of the instance of the object created by calling the constructor. (ok, still very fan 🤕), then put the code out ~

    function Person() {}
    
    Person.prototype.name = 'Fly_001';
    Person.prototype.age = 22;
    Person.prototype.sex = 'male';
    Person.property.sayName = function() {
        alert(this.name);
    };
    
    var person1 = new Person();
    person1.sayName(); // 'Fly_001';
    
    var person2 = new Person();
    alert(person1.sayName == person2.sayName); // true;
    Copy the code

    The advantage of using a prototype object is that all object instances can share the properties and methods it contains.

    Unlike the constructor pattern, the properties and methods of the new object are shared by all instances; in other words, person1 and person2 access the same set of properties and the same sayName() method.

    To understand how the stereotype pattern works, you must first understand the nature of the stereotype object in ECMAScript.

  • Understanding prototype objects

    Whenever a new function is created, a Prototype property is created for the function according to a specific set of rules, which points to the function’s prototype object. By default, all prototype objects automatically get a constructor property that contains a pointer to the function of the Prototype property.

    Take chestnut 🌰, in front of the Person. The prototype. The constructor to Person, but by the constructor, we still can continue to add other attributes and methods as the prototype object.

    The following diagram shows the relationship between the objects:

Here, the Person. The prototype to the prototype object, and the Person. The prototype. The constructor refers back to the Person.

In addition to the constructor attribute, the prototype object contains other attributes that were added later.

Each instance of Person — person1 and person2 — contains an internal property that only points to Person.prototype; in other words, they have no direct relationship to the constructor. Also, note that although neither instance contains properties or methods, you can call the sayName() method, which is done by looking up the object’s properties.

We can also use the isPrototypeOf() method to determine whether there is such a relationship between objects:

alert(Person.prototype.isPrototypeOf(person1)); // true;
alert(Person.prototype.isPrototypeOf(person2)); // true;
Copy the code

Here, because person1 and person2 both have a pointer inside to Person.prototype, both return true.

Every time the code reads an attribute of an object, a search is performed for the given attribute name. The search starts from the object instance itself. If the corresponding attribute is found, the attribute value is returned and the search stops. If not, search continues for the prototype object to which the pointer points.

That is, when we call person1.sayname (), we perform two searches, first on the person1 instance itself, then on the prototype of Person1, finally finding the sayName() method definition and returning it.

The hasOwnProperty() method can also be used to check whether an attribute exists in an instance or in a stereotype, returning true only if the attribute exists in an object instance:

alert(person1.hasOwnProperty('name')); // false;
Copy the code
  • Simpler prototype syntax

    In the previous example, typing Person.prototype every time you add a property or method is a bit cumbersome, so it’s more common to use object literals to encapsulate 📦 :

    function Person() {}
    
    Person.prototype = {
        name: 'Fly_001'.age: 22.sex: 'male'.sayName: function() {
            alert(this.name); }};Copy the code

    In the above code, we set Person.prototype to equal a new object created as an object literal, with the same result, except that the constructor property no longer points to Person.

    As described earlier, every time a function is created, its Prototype object is created, which also automatically gets the constructor attribute.

    The code we just wrote essentially overrides the default Prototype Object so that the constructor property becomes the constructor property of the new Object (pointing to the Object constructor) instead of pointing to the Person function.

    At this point, although the instanceof operator still returns the correct result, constructor no longer determines the type of the object:

    var friend = new Person();
    
    alert(friend instanceof Person); // true;
    alert(friend.constructor == Person); //false;
    alert(friend.constructor == Object); // true;
    Copy the code

    If the constructor value is really important, you can deliberately set it back to the appropriate value as follows:

    function Person () {};
    
    Person.prototype = {
        constructor: Person,
        // Set other properties and methods;
    }
    Copy the code

    Note, however, that resetting the constructor property in this way results in its [[Enumerable]] property being set to true. By default, the native constructor property is not enumerable, so we can use the object.defineProperty () method:

    // reset the constructor;
    Object.defineProperty(Person.prototype, 'constructor', {
        enumerable: false.value: Person
    });
    Copy the code
  • The dynamics of prototypes

    Since finding the value in the prototype is a search, any changes we make to the prototype object are immediately reflected in the instance — even if we create the instance first and then modify the prototype:

    var friend = new Person();
    
    Person.prototype.sayName = function() {
        alert('hi');
    };
    
    friend.sayName(); // 'hi', no problem ~
    Copy the code

    The reason can be attributed to the loose connection between instance and prototype.

    When we call friend.sayname (), we first search for the method named sayName in the instance, and continue searching for the prototype if none is found. Because the connection between the instance and the stereotype is a pointer and not a copy, the function stored there can be found in the stereotype and returned.

    While attributes and methods can be added to the prototype at any time and are immediately reflected in all object instances, rewriting the entire object is a different story:

    function Person() {}
    
    var friend = new Person();
    
    Person.prototype = {
        constructor: Person,
        name: 'Fly_001'.age: 22.sex: 'male'.sayName: function() {
            alert(this.name); }}; friend.sayName();// error!
    Copy the code

    Because the prototype that friend points to does not contain an attribute named by that name, the following diagram shows the inside story of this process:

  • A prototype of a native object

    The stereotype pattern is important not only for creating custom types, but also for all native reference types. All native reference types (Object, Array, String, and so on) define methods on archetypes of their constructors.

    For example, you can find the sort() method in array.prototype, and the substring() method in String.prototype:

    alert(typeof Array.prototype.sort); // 'function';
    alert(typeof String.prototype.substring); // 'function';
    Copy the code

    Using a prototype of a native object, you can not only get references to all default methods, but also define new methods:

    String.prototype.startWith = function(text) {
        return this.indexOf(text) == 0;
    };
    
    var msg = 'Hello World';
    alert(msg.startWith('Hello')); // true;
    Copy the code

    The above code 👆 adds a startWith() method to the base wrapper type String. Now that the method is added to String.prototype, all strings in the current environment can call this method.

  • Problems with prototype objects

    The archetypal pattern is not without its drawbacks, but its biggest problem stems from its shareable nature.

    All attributes in the stereotype are shared by many instances, which is fine for functions, and makes sense for properties that contain base values, since you can hide the corresponding attributes in the stereotype by adding an attribute of the same name to the instance.

    However, for attributes that contain reference type values, the problem is more pronounced:

    function Person() {}
    
    Person.prototype = {
        constructor: Person,
        name: 'Fly_001'.age: 22.friends: ['Jack'.'Tom'].sayName: function() {
            alert(this.name); }};var person1 = new Person();
    var person2 = new Person();
    
    person1.friends.push('Daniel');
    
    alert(person1.friends); // 'Jack, Tom, Daniel';
    alert(person2.friends); // 'Jack, Tom, Daniel';
    alert(person1.friends === person2.friends); // true;
    Copy the code

    Since the Friends array is in Person.prototype and not person1, the changes you just made are also reflected in person2.friends.

    So that’s the problem, we can’t use the prototype pattern alone

  • Combining constructor and prototype patterns (debut ✨)

    The most common way to create custom types is to combine the constructor pattern with the stereotype pattern.

    The constructor pattern is used to define instance properties, while the stereotype pattern is used to define method and shared properties.

    This way, each instance has its own copy of the instance attributes, but at the same time shares references to methods, maximizing memory savings. In addition, this hybrid mode supports passing parameters to constructors; It’s a long collection of two modes:

    funciton Person(name, age, sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.friends = ['Jack'.'Tom'];
    }
    
    Person.prototype = {
        constructor: Person,
        sayName: function() {
            alert(this.name); }}var person1 = new Perosn('Fly_001'.22.'male');
    var person2 = new Person('juejin'.24.'unknown');
    
    person1.friends.push('Daniel');
    
    alert(person1.friends); // 'Jack, Tom, Daniel';
    alert(person2.friends); // 'Jack, Tom';
    
    alert(person1.friends === person2.friends); // false;
    alert(person1.sayName === person2.sayName); // true;
    Copy the code

    Changing person1.friends here does not affect person2.friends because they reference different arrays.

    This constructor/stereotype hybrid pattern is the most widely used and recognized way to create custom types, so to speak, as a default pattern for defining reference types.

Some superficial knowledge about OBJECTS in JS, will stop here, the next article will talk about several inheritance methods in JS, please look forward to ~ ❤️