继承

作者: 你喜欢吃青椒吗_c744 | 来源:发表于2019-08-06 11:49 被阅读0次

    原型链继承

    function Animal() {
    
      this.name = 'animal';
    
      this.type = ['pig', 'cat'];
    
    }
    
    // 为父类添加共有方法
    
    Animal.prototype.greet = function(sound) {
    
      console.log(sound);
    
    }
    
    // 声明子类
    
    function Dog() {
    
      this.name = 'dog';
    
    }
    
    // 继承父类
    
    Dog.prototype = new Animal();
    var dog = new Dog();
    
    dog.greet('汪汪');  //  "汪汪"
    
    console.log(dog.type); // ["pig", "cat"]
    
    

    在上面中,创建了一个构造函数Animal,并且它的原型中还有一个greet方法。接着又让Dog.prototype成为Animal的实例,意味着Dog.prototype会继承Animal原型的方法greet,同时dog又是Dog的实例,意味着dog会继承Dog.prototype的方法,所以dog才有greet的方法。

    //原型链继承
    Animal.prototype --> Dog.prototype --> dog
    

    构造函数继承

    基本思想:即在子类型构造函数的内部调用超类型构造函数(父亲辈,爷爷辈)

    function Father(){
        this.colors = ["red","blue","green"];
    }
    function Son(){
        Father.call(this);//继承了Father,且向父类型传递参数
    }
    var instance1 = new Son();
    instance1.colors.push("black");
    console.log(instance1.colors);//"red,blue,green,black"
    
    var instance2 = new Son();
    console.log(instance2.colors);//"red,blue,green" 可见引用类型值是独立的
    

    很明显,借用构造函数一举解决了原型链的两大问题:

    其一, 保证了原型链中引用类型值的独立,不再被所有实例共享;

    其二, 子类型创建时也能够向父类型传递参数

    但是。函数复用不可用,加重内存负担。构造函数的技术也很少单独使用。

    组合继承(原型链继承+构造函数继承)

    // 声明父类   
    
    function Animal(color) {    
    
      this.name = 'animal';    
    
      this.type = ['pig','cat'];    
    
      this.color = color;   
    
    }     
    
    // 添加共有方法  
    
    Animal.prototype.greet = function(sound) {    
    
      console.log(sound);   
    
    }     
    
    // 声明子类   
    
    function Dog(color) { 
    
      // 构造函数继承    
    
      Animal.apply(this, arguments);   
    
    }   
    
    // 类式继承
    
    Dog.prototype = new Animal();   
    
    var dog = new Dog('白色');   
    
    var dog2 = new Dog('黑色');     
    
    dog.type.push('dog');   
    
    console.log(dog.color); // "白色"
    
    console.log(dog.type);  // ["pig", "cat", "dog"]
    
    console.log(dog2.type); // ["pig", "cat"]
    
    console.log(dog2.color);  // "黑色"
    
    dog.greet('汪汪');  // "汪汪"
    

    原型链继承可以拿到父构造函数的属性和方法;构造函数继承可以修改父构造函数的内容。它综合了类式继承和构造函数继承的优点,同时去除了缺陷。

    我们访问到的引用类型(比如上面的type)其实是通过apply复制到子类中的,所以不会发生共享。

    这种组合继承也是有点小缺陷的,那就是它调用了两次父类的构造函数。

    寄生组合式继承(据说是最好用的继承方式)

    前面讲过,组合继承是 JavaScript 最常用的继承模式; 不过, 它也有自己的不足. 组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数: 一次是在创建子类型原型的时候, 另一次是在子类型构造函数内部. 寄生组合式继承就是为了降低调用父类构造函数的开销而出现的 .

    其背后的基本思路是: 不必为了指定子类型的原型而调用超类型的构造函数

    function Animal(color) {
    
      this.color = color;
    
      this.name = 'animal';
    
      this.type = ['pig', 'cat'];
    
    }
    
    Animal.prototype.greet = function(sound) {
    
      console.log(sound);
    
    }
    
    function Dog(color) {
    
      Animal.apply(this, arguments);
    
      this.name = 'dog';
    
    }
    
    /* 注意下面两行 */
    
    Dog.prototype = Object.create(Animal.prototype);
    
    Dog.prototype.constructor = Dog;
    
    Dog.prototype.getName = function() {
    
      console.log(this.name);
    
    }
    
    var dog = new Dog('白色');   
    
    var dog2 = new Dog('黑色');     
    
    dog.type.push('dog');   
    
    console.log(dog.color);   // "白色"
    
    console.log(dog.type);   // ["pig", "cat", "dog"]
    
    console.log(dog2.type);  // ["pig", "cat"]
    
    console.log(dog2.color);  // "黑色"
    
    dog.greet('汪汪');  //  "汪汪"
    

    在上面的例子中,我们并不像构造函数继承一样直接将父类Animal的一个实例赋值给Dog.prototype,而是使用Object.create()进行一次浅拷贝,将父类原型上的方法拷贝后赋给Dog.prototype,这样子类上就能拥有了父类的共有方法,而且少了一次调用父类的构造函数。

    这里还需注意一点,由于对Animal的原型进行了拷贝后赋给Dog.prototype,因此Dog.prototype上的constructor属性也被重写了,所以我们要修复这一个问题:

    Dog.prototype.constructor = Dog;
    

    extends继承

    Class和extends是在ES6中新增的,Class用来创建一个类,extends用来实现继承

    //原先的写法
    function Student(name) {
        this.name = name;
    }
    
    Student.prototype.hello = function () {
        alert('Hello, ' + this.name + '!');
    }
    
    //用新的class关键字来编写Student
    class Student {
        constructor(name) {
            this.name = name;
        }
    
        hello() {
            alert('Hello, ' + this.name + '!');
        }
    }
    
    //继承
    class PrimaryStudent extends Student {
        constructor(name, grade) {
            super(name); // 记得用super调用父类的构造方法!
            this.grade = grade;
        }
    
        myGrade() {
            alert('I am at grade ' + this.grade);
        }
    }
    

    注意PrimaryStudent的定义也是class关键字实现的,而extends则表示原型链对象来自Student。子类的构造函数可能会与父类不太相同,例如,PrimaryStudent需要namegrade两个参数,并且需要通过super(name)来调用父类的构造函数,否则父类的name属性无法正常初始化。

    PrimaryStudent已经自动获得了父类Studenthello方法,我们又在子类中定义了新的myGrade方法。

    子类必须在constructor方法中调用super方法。如果不调用super方法,子类就得不到this对象

    ES6引入的class和原有的JavaScript原型继承有什么区别呢?实际上它们没有任何区别,class的作用就是让JavaScript引擎去实现原来需要我们自己编写的原型链代码。简而言之,用class的好处就是极大地简化了原型链代码。

    参考文章

    js原型链继承,借用构造函数继承,组合继承,寄生式继承,寄生组合继承

    廖雪峰的个人网站-class继承

    JavaScript实现继承的方式

    JS原型链与继承别再被问倒了

    相关文章

      网友评论

          本文标题:继承

          本文链接:https://www.haomeiwen.com/subject/wsxidctx.html