JS中的继承

134 阅读6分钟

JS中的继承

需要复习原型链相关知识new相关知识

继承:子类继承父类中的属性和方法

  1. JS中的继承主要利用的是原型链。可以对应原型链的文章。
  2. 原型链的原理:让一个引用类型继承另一个引用类型的属性和方法。
  3. JS中的继承机制和其它后台语言是不一样的,有自己的独特处理方式。

JAVA中类的多态:重载和重写

  1. JAVA中重载:函数名相同,但是传参类型、数量不同或者返回值不一样,这相当与把一个函数重载了。
  2. JS中没有类似于后台语言中的重载机制:JS中的重载只的是同一个方法,根据传参不同,实现不同的业务逻辑。
  3. 重写:子类重写父类上的方法。

方案一:原型链继承

子类的原型指向父类的实例(B子类 => A父类)

    function A() {
            this.x = 100;
            this.colors=['red','black','blue','green']
    }
    A.prototype.getX = function getX() {
            console.log(this.x);
    };

    function B() {
            this.y = 200;
    }
    B.prototype.sum=function(){
               console.log('sum')
    }

    //原型链继承
    B.prototype = new A(); // 子类的原型指向父类的实例 。B继承A
    B.prototype.constructor = B


    B.prototype.getY = function getY() {
            console.log(this.y);
    };


    let b1 = new B;
    let b2 = new B;

    b2.colors.push('yellow');
    console.log(b2.colors); // ["red", "black", "blue", "green", "yellow"]
    console.log(b1.colors) // ["red", "black", "blue", "green", "yellow"]
    // 在A构造函数定义了一个colors属性,当B通过原型链继承后,这个属性就会出现B.prototype中,就跟专门创建了B.prototype.colors一样.
    //所以会导致B的所有实例都会共享这个属性,所以b2修改colors这个引用类型值,也会反映到b1中
  1. 并不会把父类中的方法克隆一份给子类,而是建立了子类和父类之前的原型链查找机制。
  2. 重定向子类的原型后,默认丢失了原本的constructor属性(或者原本在原型上设置的属性和方法)
  3. 子类或者子类的实例,可以基于原型链“肆意”修改父类上的属性,对父类造成一些破坏。
  4. 会把父类中的私有属性方法作为子类共有属性方法继承过来(父类中不管是共有还是私有属性,最后都变为子类的共有属性)
  5. 优缺点:
  • 优点:
    • 继承了⽗类的模板,⼜继承了⽗类的原型对象
  • 缺点:
    • 可以在⼦类构造函数中,为⼦类实例增加实例属性。如果要新增原型属性和⽅法,则必须放在 SubType.prototype = new SuperType('SubType'); 这样的语句之后执⾏。
    • 无法实现多继承
    • 来⾃原型对象的所有属性被所有实例共享
      // ⽗类
      function SuperType () {
      this.colors = ["red", "blue", "green"];
      this.name = "SuperType";
      }
      // ⼦类
      function SubType () {}
      // 原型链继承
      SubType.prototype = new SuperType();
      // 实例1
      var instance1 = new SubType();
      instance1.colors.push("blcak");
      instance1.name = "change-super-type-name";
      console.log(instance1.colors); // ["red", "blue", "green", "blcak"]
      console.log(instance1.name); // change-super-type-name
      // 实例2
      var instance2 = new SubType();
      console.log(instance2.colors); // ["red", "blue", "green", "blcak"]
      console.log(instance2.name); // SuperType
      
      • 更改 父 引⽤类型属性时,会使 子 所有实例共享这⼀更新。基础类型属性更新则不会。
    • 创建⼦类实例时,⽆法向⽗类构造函数传参,或者说是,没办法在不影响所有对象实例的情况下,向超类的构造函数传递参数

方案二: call继承(构造函数继承)

  • 基本思想:在⼦类型的构造函数内部调⽤⽗类型构造函数。
  • 注意:函数只不过是在特定环境中执⾏代码的对象,所以这⾥使⽤ apply/call 来实现。
  • 使⽤⽗类的构造函数来增强⼦类实例,等于是复制⽗类的实例属性给⼦类(没⽤到原型) 把父类当做普通函数执行,让其执行的时候,方法中的this变为子类的实例即可
  1. 特点
  • 只能继承父类中的私有属性(继承的私有属性赋值给子类实例的私有属性),而且是类似拷贝过来一份,而不是链式查找。
  • 因为只是把父类当做普通的方法执行,所以父类原型上的公有属性方法无法被继承过来。
    function A() {
            this.x = 100;
            this.colors = ['red', 'blue', 'green']
    }
    A.prototype.getX = function getX() {
            console.log(this.x);
    };

    function B() {
            //CALL继承
            A.call(this);  //=>this.x = 100;  b.x=100;
            this.y = 200;
    }
    B.prototype.getY = function getY() {
            console.log(this.y);
    };

    let b1 = new B; // 运⾏⼦类构造函数,并在⼦类构造函数中运⾏⽗类构造函数,this绑定到⼦类
    let b2 = new B;

    console.log(b1); // B {x:100, y:200}
    b1.getX() // Uncaught TypeError: b.getX is not a function
    b1.getY() // 200

    b1.colors.push('black')
    console.log(b1.colors) //["red", "blue", "green", "black"]
    console.log(b2.colors) //["red", "blue", "green"]
    //在新建B实例是调用了A构造函数,这样以来,就会在新B对象上执行A函数中定义的所有对象初始化代码 结果,B的每个实例就会具有自己的colors属性的副本了
  1. 借助call可以传递参数
function A(name) {
    this.name = name
}

function B() {
    // 继承SuperType
    A.call(this, '小明')
    this.job = 'student'
}

let b = new B()
console.log(b.name) // 小明
console.log(b.job) // student
  1. 有点缺点
  • 优点:
    • 解决了1中⼦类实例共享⽗类引⽤对象的问题,实现多继承,创建⼦类实例时,可以向⽗类传递参数
  • 缺点:
    • 实例并不是⽗类的实例,只是⼦类的实例
    • 只能继承⽗类的实例属性和⽅法,不能继承原型属性/⽅法
    • ⽆法实现函数复⽤,每个⼦类都有⽗类实例函数的副本,影响性能

方案三:组合继承

CALL继承+变异版的原型继承共同完成的

  • CALL继承实现:私有到私有(实现对实例属性的继承)
  • 原型继承实现:公有到公有 (原型属性和方法的继承)
              function A(name) {
		   this.x = 100;
		   this.name = name ;
		   this.colors = ['red', 'blue', 'green']
		}
		// 父类添加方法
		A.prototype.getX = function getX(x) {
		   this.x = x
			console.log(this.x);
		};
		A.prototype.sayName = function sayName() {
			console.log(this.name);
		};

		function B(name, job) {
                // call 继承!!!!!!!!!!!!!!!!!
			A.call(this,name);
			this.y = 200;
			this.job = job
		}
                
                  //原型链继承
		B.prototype = Object.create(A.prototype); 	
                //=>Object.create(OBJ) 创建一个空对象,让其__proto__指向OBJ(把OBJ作为空对象的原型)
		B.prototype.constructor = B;
                
                
		//为子类添加一些方法
		B.prototype.getY = function getY() {
			console.log(this.y);
		};
		B.prototype.sayJob = function sayJob() {
			console.log(this.job);
		};
		
		let b = new B('小明','student');
		let b1 = new B('小红','teacher');
		
		 console.log(b) //B {x: 100, name: "小明", colors: Array(3), y: 200, job: "student"}
		 b.colors.push('black') 
		 console.log(b.colors) //["red", "blue", "green", "black"]
                 b.sayName() // 小明
                 b.sayJob() // 'student'
                 b.getX(3); //3
                 b.getY(); //200

                 console.log(b1)// B {x: 100, name: "小红", colors: Array(3), y: 200, job: "teacher"}
                 console.log(b1.colors)  //["red", "blue", "green"]
                 b1.sayName() // 小红
                 b1.sayJob() // 'teacher'
                 b1.getX(50); // 50 
                 b1.getY(); //200

                 console.log(b instanceof A) //true
                 console.log(b instanceof B)//true

原理图:

使用ES6创建class

参考廖雪峰的网站

class A {
        constructor(name) {
                this.x = 100;
                this.name = name
        }
        getX() {
                console.log(this.x);
        }
}
        //=>extends继承和寄生组合继承基本类似
class B extends A {
        constructor() {
                super(name); //=>一但使用extends实现继承,只要自己写了constructor,就必须写super  <=> A.call(this,100)
                this.y = 200;
        }
        getY() {
                console.log(this.y);
        }
}

let b = new B; 
console.log(b); // B {x: 100, y: 200}

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

真实项目中用到继承的情况

  1. REACT创建类组件
    class Vote extends React.Component{

		}
  1. 自己写插件或者类库的时候
class Utils{
        //=>项目中公共的属性和方法
}

class Dialog extends Utils{

}