js constructor implements inheritance

My last article introduced the prototype chain inheritance pattern . But the pure prototype chain pattern does not implement inheritance very well.

1. Disadvantages of Prototype Chain

1.1 The biggest disadvantage of pure prototype chain inheritance comes from the fact that the prototype contains the value of the reference type.

Originally, before we implemented inheritance through the prototype chain, all instances of the subclass shared all the properties and methods on the prototype. Properties on the prototype can be accessed through subclass instances, however, properties on the prototype cannot be overridden.

copy code
// Define a student class 
function Student(stuID, schoolName) {
     this .stuID = stuID;
     this .schoolName = schoolName;
}
// All students have such a characteristic 
Student.prototype.characteristic = 'Young and energetic' ;
            
var stu1 = new Student(1001,'First Primary School' );
console.log(stu1.stuID);                 // 1001 
console.log(stu1.characteristic);        // 'Young and energetic'
            
// Rewrite characteristic 
stu1.characteristic = 'Lively and cute' ; 
console.log(stu1.characteristic);        // 'Lively and cute'

var stu2 = new Student(1002,'First Primary School' );
 console.log(stu2.characteristic);        // 'Young and energetic' 
console.log(Student.prototype);          // {characteristic: "Young and energetic" }
copy code

  In the above code, the stu1 instance can access the attribute characteristic on the prototype, which prints "young and energetic"; then, we set stu1.characteristic = 'lively and lovely', then printing stu1.characteristic becomes "" energetic and cute". Does that mean that the characteristic attribute value on the prototype has been rewritten by us through the instance stu1? not at all. We found that the value of accessing stu2.characteristic is still "young and energetic". So the property value on the prototype will not be overridden by the instance.

  When there is a property with the same name as the prototype in the instance, the property with the same name on the prototype will be automatically blocked. stu1.characteristic = = 'lively and cute' actually adds a local attribute characteristic to the instance stu1, so when we visit stu1.characteristic again, we access the local attribute of the instance, not the characteristic attribute on the prototype (it is due to The same name as the local property name has been blocked).

  The characteristic value on the prototype is a value of a primitive type, what if it is a reference type? There will be a bunch of small nine-nine among them.

  In fact, any type of value on the prototype will not be overridden by the instance. Setting the value of a property on the instance with the same name as the prototype will only create a local property on the instance with the same name. However, the value of the reference type on the prototype can be modified by the instance, and the value of the reference type accessed by all instances will also change accordingly.

copy code
// Define a student class 
function Student(stuID, schoolName) {
     this .stuID = stuID;
     this .schoolName = schoolName;
}
// All students have such a characteristic 
Student.prototype.characteristic = 'Young and energetic' ;
 Student.prototype.examItems = ['Chinese','Math','English'];   // Exam items


var stu1 = new Student(1001, '第一小学');
console.log(stu1.examItems); //['语文','数学','英语']

//修改examItems
stu1.examItems.push('科学');
console.log(stu1.examItems); //['语文','数学','英语','科学']

var stu2 = new Student(1002, '第一小学');
console.log(stu2.examItems); //['语文','数学','英语','科学']
copy code

  我们在刚才的 Student 原型上又添加了一个考试项目属性 examItems, 这是个引用类型的值。最初实例 stu1 访问 examItems 时,得到的是 ['语文','数学','英语'],然后我们通过stu1.examItems.push('科学') 给examItems 这个数组又添加了一个“科学”项,再次打印时,数组变成了 ['语文','数学','英语','科学']。可怕的是,此后打印实例 stu2.examItems 也变成了 ['语文','数学','英语','科学']。

  因此,我们得出结论,原型上任何类型的属性值都不会通过实例被重写,但是引用类型的属性值会受到实例的影响而修改

1.2 原型链不能实现子类向父类中传参。这里就不细说了。

 

二、借用构造函数

2.1 实现原理

实现原理是,在子类的构造函数中,通过 apply( ) 或 call( )的形式,调用父类构造函数,以实现继承。

copy code
//定义一个超类/父类: 人
function Person (name, age) {
    //人都有姓名,年龄,会吃饭,会睡觉
    //传入出生年份 year,自动计算年龄
    this.name = name;
    this.age = age;
    this.eat = function () {
        alert('吃饭');
    }
    this.sleep = function () {
        alert('睡觉');
    }
}

//定义一个子类: 学生
//学生Student也是人,自然要继承超类 Person 的所有属性和方法
//学生都应当有姓名、年龄、会吃饭、会睡觉
//当然学生也有自己的一些属性:学号,学校名称等,和方法,比如都要去做一件事:写作业
function Student (stuID, schoolName, name, age) {
    this.stuID = stuID;
    this.schoolName = schoolName;
    //用call调用 Person,以实现继承
    Person.call(this, name, age);
}

Student.prototype.doHomework = function () {
    alert('做作业');
}

//实例化一个学生
var stu1 = new Student(1001, '第一小学', '王宝宝',20);
console.log(stu1.stuID);       //1001
console.log(stu1.schoolName);  //'第一小学'
console.log(stu1.name);        //'王宝宝'
console.log(stu1.age);         //20
stu1.eat();                    //'吃饭'
stu1.sleep();                  //'睡觉'
stu1.doHomework();             //'做作业'    
copy code

  上面代码定义了一个父类函数 Person 和一个子类函数 Student, 在子类构造函数中,我们通过 call 的方式调用了父类构造函数 Person实现了继承。别忘了,函数只不过是一段可以在特定作用域执行代码的特殊对象,我们可以通过 call 方法指定我函数的作用域。

  在 stu1 = new Student() 构造函数时,Student 内部 this 的值指向的是 stu1, 所以 this.stuID =stu1.stuID, 所以 Person.call(this, name, age) 就相当于Person.call(stu1, '王宝宝', 20),就相当于 stu1.Person('王宝宝',20)。最后,stu1 去调用 Person 方法时,Person 内部的 this 指向就指向了 stu1。那么Person 内部this 上的所有属性和方法,都被拷贝到了stu1上。说到这里,大家应该清楚一点点了吧。

  总之,在子类函数中,通过call() 方法调用父类函数后,子类实例 stu1, 可以访问到 Student 构造函数和 Person 构造函数里的所有属性和方法。这样就实现了子类向父类的继承。

 

2.2 缺点

  这种形式的继承,每个子类实例都会拷贝一份父类构造函数中的方法,作为实例自己的方法,比如 eat()。这样做,有几个缺点:

  1. 每个实例都拷贝一份,占用内存大,尤其是方法过多的时候。

  2. 方法都作为了实例自己的方法,当需求改变,要改动其中的一个方法时,之前所有的实例,他们的该方法都不能及时作出更新。只有后面的实例才能访问到新方法。

copy code
// Define a superclass/parent class: Person 
function Person (name, age) {
     // Everyone has a name, age, can eat, can sleep 
    // Pass in the year of birth, and automatically calculate the age 
    this .name = name;
     this .age = age;
     this .eat = function () {
        alert( 'eat' );
    }
    this.sleep = function () {
        alert( 'sleep' );
    }
}
copy code

 

  Therefore, in fact, using prototype chain inheritance alone or borrowing constructor inheritance has its own great disadvantages. The best way is to use the two together to give full play to their respective advantages . I will explain in the next article.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324731866&siteId=291194637