4

JS has 6 simple data types (also known as primitive data types): Undefined, Null, Boolean, Number, String and Symbol. There is also a complex data type, Object, which is essentially composed of an unordered set of name-value pairs. JS doesn't support any mechanism for creating custom types, and all values will end up being one of the 7 data types mentioned above.
Array and Function are both Objects, and both inherit the prototype object of Object.

understand the object

Object is an unordered collection of name-value pairs

 方式一
const person = new Object();
person.name = '张三';
person.age = 16;
person.sayName = () => {
    console.log(this.name);
}


// 方式二
const person = {
    name: '张三',
    age: 16,
    sayName: ()=>{
        console.log(this.name);
    }

}

1. Data attributes

     Object.defineProperty(person, 'name', {
        writable: true, // 是否能被重写
        configurable: true, // 能否被删除
        enumerable: true, // 能否被for-in遍历
        value: '张三',
    });

When writable and configurable are false, modification and deletion do not take effect. And it throws an error in strict mode.

2. Accessor properties

     let person = {age: 16};
    Object.defineProperty(person, 'name', {
        get() {
            console.log('get..');
            return this.name_;
        },
        set(newV) {
            console.log('set。。。。', newV);
            this.name_ = newV;
        }
    });

    person.name = '李四';
    console.log(person.name);

In addition, multiple properties can be defined at once

     const person = {};
    Object.defineProperties(person, {
        _name: {
            configurable: true,
            enumerable: true,
        },
        name: {
            get: function() {
                return this._name;
            },
            set: function(newV) {
                this._name = newV;
            }
        },
        age: {
            get: function() {
                return this.age;
            },
            set: function(newV) {
                this.age = newV;
            }
        }
    })

3. Read the properties of the property

Use the Object.getOwnPropertyDescriptor() method to get the property descriptor of the specified property.

     const person = {age: 16, _name: 'zhang'};
    const descriptor = Object.getOwnPropertyDescriptor(person, 'age');
    console.log('...', descriptor.writable);  // true
    console.log('...', descriptor.enumerable);  // true
    console.log('...', descriptor.configurable);  // true
    console.log('...', descriptor.value);  // 16

create object

1. Factory mode

     function createPerson(name, age) {
        return {
            name,
            age,
            sayName: function () { // 注意这里不能用箭头函数,会影响this的指向
                console.log(this.name);
            }
        };
    }
    let person1 = createPerson("张三", 29);
    let person2 = createPerson("李四", 27);

    person1.sayName(); // 张三
    person2.sayName(); // 李四

Disadvantage: Can't solve the object identification problem (i.e. what type is the newly created object).

2. Constructor pattern

Precautions

  • new create function
  • Function names should be capitalized.

       function Person(name, age) {
          this.name = name;
          this.age = age;
          this.sayName = ()=>{
              console.log(this.name);
          };
      }
      let person1 = new Person("张三", 29);
      let person2 = new Person("李四", 27);
    
      person1.sayName(); // 张三
      person2.sayName(); // 李四

    operation performed by new

  • Create a new object in memory.
  • The [[Prototype]] property inside this new object is assigned the prototype property of the constructor.
  • This inside the constructor is assigned the new object (ie this points to the new object).
  • Execute the code inside the constructor (add properties to the new object).
  • If the constructor returns a non-null object, return that object; otherwise, return the new object just created.

Constructor Pattern Disadvantage: The main problem with constructors is that the methods they define are created on every instance.

 console.log(person1.sayName == person2.sayName); // false

3. Prototype Mode

     function Person() {}
    Person.prototype.name = "张三";
    Person.prototype.age = 29;
    Person.prototype.sayName = function() {
        console.log(this.name);
    };
    let person1 = new Person();
    person1.sayName(); // "张三"
    let person2 = new Person();
    person2.sayName(); // "张三"
    console.log(person1.sayName === person2.sayName); // true

object prototype, prototype chain

1. Understand the prototype

Whenever a function is created, a prototype property (pointing to the prototype object) is created for the function according to certain rules. By default, all prototype objects automatically get a property called constructor that points back to the constructor function associated with them. For the previous example, Person.prototype.constructor points to Person. Then, depending on the constructor, additional properties and methods may be added to the prototype object.

     function Person() {}
    console.log(Person.prototype.constructor === Person); // true

原型1.jpg

When customizing the constructor, the prototype object only gets the constructor property by default, and all other methods inherit from Object. Every time a constructor is called to create a new instance, the instance's internal [[Prototype]] pointer is assigned the constructor's prototype object. There is no standard way to access this [[Prototype]] property in scripts, but Firefox, Safari, and Chrome expose the __proto__ property on every object through which the object's prototype can be accessed. In other implementations, this feature is completely hidden. The key is to understand this: there is a direct connection between instances and constructor prototypes, but not between instances and constructors.
原型2.jpg
Notice:

  1. Constructor, prototype object and instance are 3 completely different objects

       console.log(person1 !== Person); // true
      console.log(person1 !== Person.prototype); // true
      console.log(Person.prototype !== Person);  // true
  2. The instance has no direct connection with the constructor, but has a direct connection with the prototype object

     // 实例通过__proto__链接到原型对象,它实际上指向隐藏特性[[Prototype]]。
    // 构造函数通过 prototype 属性链接到原型对象
    console.log(person1.__proto__ === Person.prototype); // true
    conosle.log(person1.__proto__.constructor === Person); // true
  3. Two instances created by the same constructor share the same prototype object:

     console.log(person1.__proto__ === person2.__proto__); // true

Prototype related methods

  • isPrototypeOf()
    This relationship between two objects can be determined using the isPrototypeOf() method. Essentially, isPrototypeOf() will return true if the passed parameter [[Prototype]] points to the object on which it was called, as follows:

     console.log(Person.prototype.isPrototypeOf(person1)); // true 
    console.log(Person.prototype.isPrototypeOf(person2)); // true

    Here person1 and person2 are checked by calling the isPrototypeOf() method on the prototype object. Since both examples have links to Person.prototype internally, both results return true.

  • Object.getPrototypeOf()
    The Object type has a method called Object.getPrototypeOf() that returns the value of the parameter's internal property [[Prototype]].

     console.log(Object.getPrototypeOf(person1) == Person.prototype); // true 
    console.log(Object.getPrototypeOf(person1).name); // "张三"
  • Rewrite the inheritance relationship method of the object 1: Object.setPrototypeOf()

       let person = {name:'zhang'};
      let student = {grade: '一年级'};
    
      Object.setPrototypeOf(student, person);
    
      console.log(student.grade); // 一年级
      console.log(student.name); // zhang
      console.log(Object.getPrototypeOf(student) === person); // true

    Object.setPrototypeOf() can seriously affect code performance. So it is not recommended to use

  • Rewrite the object's inheritance relationship method 2: Object.create()
    To avoid possible performance degradation using Object.setPrototypeOf(), create a new object with Object.create() specifying its prototype:

       let person = {name:'zhang'};
      let student = Object.create(person)
      student.grade = '一年级';
    
      console.log(student.grade); // 一年级
      console.log(person.name); // zhang
      console.log(Object.getPrototypeOf(student) === person); // true

2. Prototype chain

原型3.jpg

   /**
  * 正常的原型链都会终止于 Object 的原型对象 * Object 原型的原型是 null
  */
  console.log(Person.prototype.__proto__ === Object.prototype);  // true
  console.log(Person.prototype.__proto__.constructor === Object); // true
  console.log(Person.prototype.__proto__.__proto__ === null); // true
  console.log(Person.prototype.__proto__ === Person);
  • Inspect the prototype chain with instanceof

     console.log(person1 instanceof Person);  // true
    console.log(person1 instanceof Object);  // true
    console.log(Person.prototype instanceof Object);  // true
  • When accessing a property through an object, the prototype hierarchy starts searching by the property's name. The search begins with the object instance itself. If the given name is found on this instance, the value corresponding to that name is returned. If the property is not found, the search follows the pointer into the prototype object, finds the property on the prototype object, and returns the corresponding value. Therefore, when person1.sayName() is called, a two-step search occurs. First, the JavaScript engine will ask: "Does the person1 instance have a sayName property?" The answer is no. Then, continue the search and ask: "Does the prototype of person1 have a sayName property?" The answer is yes. So the function saved on the prototype is returned. When calling person2.sayName(), the same search process occurs and the same results are returned. This is how prototypes are used to share properties and methods across multiple object instances.
    While it is possible to read the values on the prototype object through the instance, it is not possible to override those values through the instance. If a property with the same name as the prototype object is added to the instance, that property will be created on the instance, which will shadow the property on the prototype object. Here's an example:

       function Person() {}
      Person.prototype.name = "张三";
      Person.prototype.age = 29;
      Person.prototype.job = "码农";
      Person.prototype.sayName = function() {
          console.log(this.name);
      };
      let person1 = new Person();
      let person2 = new Person();
      person1.name = "李四";
      console.log(person1.name); // "李四",来自实例
      console.log(person2.name); // "张三",来自原型
  • Use the hasOwnProperty() method to determine whether the property is on the instance or the prototype object

       function Person() {}
      Person.prototype.name = "张三";
      Person.prototype.age = 29;
      Person.prototype.job = "码农";
      Person.prototype.sayName = function() {
          console.log(this.name);
      };
      let person1 = new Person();
      let person2 = new Person();
      console.log(person1.hasOwnProperty("name")); // false
    
    
      person1.name = "李四";
      console.log(person1.name); // "李四",来自实例
      console.log(person1.hasOwnProperty("name")); // true
    
      console.log(person2.name); // "张三",来自原型
      console.log(person2.hasOwnProperty("name")); // false
    
    
      delete person1.name;
      console.log(person1.name); // "张三",来自原型
      console.log(person1.hasOwnProperty("name")); // false
  • in operator
    The in operator returns true if the specified property can be accessed through the object, whether on the instance or the prototype.

      function Person() {}
      Person.prototype.name = "张三";
      Person.prototype.age = 29;
      Person.prototype.job = "码农";
      Person.prototype.sayName = function() {
          console.log(this.name);
      };
      let person1 = new Person();
      let person2 = new Person();
      console.log(person1.hasOwnProperty("name")); // false
    
    
      person1.name = "李四";
      console.log(person1.name); // "李四",来自实例
      console.log(person1.hasOwnProperty("name")); // true
      console.log("name" in person1); // true
    
      console.log(person2.name); // "张三",来自原型
      console.log(person2.hasOwnProperty("name")); // false
      console.log("name" in person1); // true
    
      delete person1.name;
      console.log(person1.name); // "张三",来自原型
      console.log(person1.hasOwnProperty("name")); // false
      console.log("name" in person1); // true

inherit

Inheritance is the most discussed topic in object-oriented programming. Many object-oriented languages support two types of inheritance: interface inheritance and implementation inheritance. The former only inherits the method signature, the latter inherits the actual method. Interface inheritance is not possible in js because functions don't have signatures. Implementation inheritance is the only inheritance method supported by js, and this is mainly achieved through the prototype chain.

1. Prototype chain

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

    Person.prototype.sayName = function (){
        console.log(`我是${this.name}`);
    }

    function Student(grade) {
        this.grade = grade;
    }

    Student.prototype = new Person('张三');
    Student.prototype.sayGrade = function () {
        console.log(`我已经${this.grade}了`);
    }

    const stu1 = new Student('一年级');
    console.log(stu1.name); // 张三
    console.log(stu1.grade); // 一年级
    stu1.sayName(); // 我是张三
    stu1.sayGrade(); // 我已经一年级了

Disadvantages of Prototype Chains The main problem occurs when the prototype contains reference values. As mentioned earlier when talking about the prototype, the reference value contained in the prototype is shared among all instances, which is why properties are usually defined in the constructor and not on the prototype. When using a prototype to implement inheritance, the prototype actually becomes an instance of another type. This means that the original instance properties have been transformed into prototype properties.

     function Person() {
        this.hobby = ['唱', '跳'];
    }

    function Student() {}

    Student.prototype = new Person();

    const stu1 = new Student();
    console.log(stu1.hobby); // ['唱', '跳']
    stu1.hobby.push('rapper');
    console.log(stu1.hobby); // ['唱', '跳', 'rapper']

    const stu2 = new Student();
    console.log(stu2.hobby); // ['唱', '跳', 'rapper']

2. Stealing the constructor

To solve inheritance problems caused by prototypes containing reference values, a technique called "constructor stealing" has become popular in the development community (this technique is also sometimes called "object cloaking" or "classical inheritance"). The basic idea is simple: call the parent class constructor in the child class constructor. Because after all functions are simple objects that execute code in a specific context, you can use the apply() and call() methods to execute constructors with the newly created object as the context.

      function Person() {
        this.hobby = ['唱', '跳'];
    }

    function Student() {
        Person.call(this);
    }

    const stu1 = new Student();
    console.log(stu1.hobby); // ['唱', '跳']
    stu1.hobby.push('rapper');
    console.log(stu1.hobby); // ['唱', '跳', 'rapper']

    const stu2 = new Student();
    console.log(stu2.hobby); // ['唱', '跳']

Advantages: One advantage of stealing constructors compared to using prototype chains is that you can pass parameters to the parent class constructor in the child class constructor.

Disadvantage: The main disadvantage of stealing a constructor is also a problem with customizing types using the constructor pattern: methods must be defined in the constructor, so functions cannot be reused. In addition, subclasses cannot access methods defined on the prototype of the superclass, so all types can only use the constructor pattern. Because of these problems, stealing the constructor basically doesn't work on its own either.

3. Compositional Inheritance

Compositional inheritance (sometimes called pseudo-classical inheritance) combines prototype chaining and stealing constructors, bringing together the best of both worlds. The basic idea is to use the prototype chain to inherit properties and methods on the prototype, and inherit instance properties by stealing the constructor. This allows both to define methods on the prototype for reuse, and to allow each instance to have its own properties.

     function Person(name, age) {
        this.name = name;
        this.age = age;
        this.hobby = ['唱', '跳'];
    }
    Person.prototype.sayName = function (){
        console.log(`我是${this.name}`);
    }

    function Student({name, age}) {
        Person.call(this, name, age);
    }

    Student.prototype = new Person();

    const stu1 = new Student({name:'张三', age: 16});
    console.log(stu1.hobby); // ['唱', '跳']
    stu1.hobby.push('rapper');
    console.log(stu1.hobby); // ['唱', '跳', 'rapper']
    stu1.sayName(); // 我是张三


    const stu2 = new Student({name:'李四', age: 24});
    console.log(stu2.hobby); // ['唱', '跳', 'rapper']
    stu2.sayName(); // 我是李四

Combinatorial inheritance makes up for the lack of prototype chains and stealing constructors, and is the most used inheritance pattern in JavaScript. And compositional inheritance also preserves the instanceof operator and the isPrototypeOf() method's ability to identify composite objects.

4. Prototypal Inheritance

Prototypal inheritance works in this situation: you have an object, and you want to create a new object based on it. You need to pass this object to object() first, and then make appropriate modifications to the returned object.

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


    let person = {
        name: "张三",
        friends: ["唱", "跳"]
    };
    let anotherPerson = object(person);
    anotherPerson.name = "李四";
    anotherPerson.friends.push("rapper");
    let yetAnotherPerson = object(person);
    yetAnotherPerson.name = "王二麻子";
    yetAnotherPerson.friends.push("篮球");
    console.log(person.friends); // ['唱', '跳', 'rapper', '篮球']

The new Object.create() in js has the same effect as the object() method here.
Prototypal inheritance is great for situations where you don't need to create a separate constructor, but you still need to share information between objects. But keep in mind that the reference value contained in a property is always shared between related objects, just like using the prototype pattern.

5. Parasitic inheritance

An inheritance approach that is closer to prototypal inheritance is parasitic inheritance, which is also a pattern pioneered by Crockford. The idea behind parasitic inheritance is similar to the parasitic constructor and factory pattern: create a function that implements inheritance, enhances an object in some way, and returns that object. The basic parasitic inheritance pattern is as follows:

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

    function createObject(original) {
        let clone = object(original); // 通过调用函数创建一个新对象
        clone.sayName = function () { // 以某种方式增强这个对象
            console.log(this.name);
        };
        return clone; // 返回这个对象
    }

    let person = {
        name: "张三",
    };
    let anotherPerson = createObject(person);
    anotherPerson.sayName();  // 张三

Parasitic inheritance is also suitable for scenarios where the main focus is on objects, not on types and constructors. The object() function is not required for parasitic inheritance, any function that returns a new object can be used here.
Disadvantage: Adding functions to objects through parasitic inheritance can make functions difficult to reuse, similar to the constructor pattern.

6. Parasitic composition inheritance

Combinatorial inheritance also has efficiency problems. The main efficiency problem is that the parent class constructor is always called twice: once when the subclass prototype is created, and once in the subclass constructor. In essence, the subclass prototype ultimately contains all the instance properties of the superclass object, and the subclass constructor only needs to override its own prototype at execution time.
Parasitic composition inheritance inherits properties by stealing constructors, but uses a hybrid prototype chain inheritance approach. The basic idea is not to assign a value to the prototype of the subclass by calling the constructor of the superclass, but to obtain a copy of the prototype of the superclass. In the final analysis, it is to use parasitic inheritance to inherit the parent class prototype, and then assign the returned new object to the child class prototype.

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

    function inheritPrototype(child, parent) {
        let prototype = object(parent.prototype); // 创建对象
        prototype.constructor = child; // 增强对象
        child.prototype = prototype; // 赋值对象
    }


    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.hobby = ['唱', '跳'];
    }
    Person.prototype.sayName = function (){
        console.log(`我是${this.name}`);
    }

    function Student({name, age}) {
        Person.call(this, name, age);
    }

    inheritPrototype(Student, Person);


    Student.prototype.sayAge = function (){
        console.log(`我今年${this.age}`);
    }


    const stu1 = new Student({name:'张三', age: 16});
    console.log(stu1.hobby); // ['唱', '跳']
    stu1.hobby.push('rapper');
    console.log(stu1.hobby); // ['唱', '跳', 'rapper']
    stu1.sayName(); // 我是张三
    stu1.sayAge(); // 我今年16


    const stu2 = new Student({name:'李四', age: 24});
    console.log(stu2.hobby); // ['唱', '跳', 'rapper']
    stu2.sayName(); // 我是李四
    stu2.sayAge(); // 我今年24
   

The inheritPrototype() function implements the core logic of parasitic composition inheritance. This function takes two parameters: the child class constructor and the parent class constructor. Inside this function, the first step is to create a copy of the parent's prototype. Then, set the constructor property to the returned prototype object to solve the problem that the default constructor is lost due to overriding the prototype. Finally assign the newly created object to the prototype of the subtype.
The Person constructor is called only once, avoiding unnecessary and unused properties on Student.prototype, so it can be said that this example is more efficient. Also, the prototype chain remains unchanged, so the instanceof operator and the isPrototypeOf() method work normally. Parasitic compositional inheritance can be considered the best pattern for reference type inheritance.

kind

class is a new definition of es6, in fact, it can be understood as a kind of syntactic sugar, which is essentially a function.

 class Person {}

console.log(Person); // class Person {}
console.log(typeof Person); // function

The constituent classes of a class can contain constructor methods, instance methods, getter functions, setter functions, and static class methods, but none of these are required.

     class Person {
        constructor(name) {
            console.log('constructor');
            this.name = name;
        }

        sayAge(){
            console.log('age....')
        }

        static sayHi() {
            console.log('sayHi');
        }
    }
  • class inheritance

       class Person {
          constructor({name, age}) {
              this.name = name;
              this.age = age;
          }
    
          sayName() {
              console.log(`我是${this.name}`)
          }
      }
    
    
      class Student extends Person{
          constructor(props) {
              super(props);
              this.grade = props.grade;
          }
      }
    
      const stu1 = new Student({name: '张三', age: 14, grade:'一年级'});
    
      console.log(stu1.name); // 张三
      console.log(stu1.grade); // 一年级
      stu1.sayName(); // 我是张三

    Although class inheritance uses the new syntax extends, the prototype chain is still used behind it.


小学生
495 声望4 粉丝

路漫漫其修远兮,吾将上下而求索。