zoukankan      html  css  js  c++  java
  • javascript 继承

    什么是继承?
    继承就是你爸爸很多钱,所以你就继承你爸爸,变成了富二代,也是个有钱的主,突然你爸爸世界杯赌球,输了个精光,于是你也变成了穷光蛋。这个就是继承
    非也,非也。

    C# 继承

    public class A {
       private int a;
       private int b;
    }
    
    public class B :A {
     private int c;
     private int b;
    }
    
    //B 继承 A
    

    ES6 继承

    export default class A {
        constructor(props){
           super(props)
        }
        test() { alert('test') }
    }
    
    export default class B extends A {
        constructor(props){
           super(props)
        }
        test1() { this.test() }
    }
    
    //B 继承 A
    

    继承机制的实现

    要用 ECMAScript 实现继承机制,您可以从要继承的基类入手。所有开发者定义的类都可作为基类。出于安全原因,本地类和宿主类不能作为基类,这样可以防止公用访问编译过的浏览器级的代码,因为这些代码可以被用于恶意攻击。
    选定基类后,就可以创建它的子类了。是否使用基类完全由你决定。有时,你可能想创建一个不能直接使用的基类,它只是用于给子类提供通用的函数。在这种情况下,基类被看作抽象类。

    尽管 ECMAScript 并没有像其他语言那样严格地定义抽象类,但有时它的确会创建一些不允许使用的类。通常,我们称这种类为抽象类。

    创建的子类将继承超类的所有属性和方法,包括构造函数及方法的实现。记住,所有属性和方法都是公用的,因此子类可直接访问这些方法。子类还可添加超类中没有的新属性和方法,也可以覆盖超类的属性和方法。

    继承的方式

    和其他功能一样,ECMAScript 实现继承的方式不止一种。这是因为 JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者,你有权决定最适用的继承方式。

    一、对象冒充(构造继承)

    所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。

    原理:构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。因为构造函数只是一个函数,所以可使 ClassA 构造函数成为 ClassB 的方法,然后调用它。ClassB 就会收到 ClassA 的构造函数中定义的属性和方法。

    function ClassA(name){
        this.name = name;
        this.say = function(){
            console.log(this.name)
        }
    }
    
    function ClassB(name){
        this.newSay = ClassA
        this.newSay(name)
        delete this.newSay;
    }
    

    为ClassA赋予了newSay方法(函数名只是指向他的指针),然后调用该方法,传递他的是ClassB构造函数的参数name,所有新属性和新方法都必须在新方法的代码行后定义,否则,可能回覆盖超类的相关属性和方法。

    运行调用:

    var a = new ClassA('one')
    var b = new ClassB('two')
    a.say() //输出 'one'
    b.say() //输出 'two'
    
    对象冒充可以实现多重继承
    function ClassA(name){
        this.name1 = name;
        this.say = function(){
            console.log(this.name1)
        }
    }
    function ClassB(name){
        this.name2 = name;
        this.method = function(){
            console.log(this.name2) //注意这里的变量name2 不能和ClassA里都变量相同,否则回覆盖掉ClassA的值
        }
    }
    
    function ClassC(name1,name2){
        this.newSay = ClassA
        this.newSay(name1)
        delete this.newSay;
    
        
        this.newMethod = ClassB
        this.newMethod(name2)
        delete this.newMethod
    }
    // test
    var a = new ClassA('one')
    var b = new ClassB('two')
    var c = new ClassC('three','four')
    a.say() // 输出 'one'
    b.method() // 输出 'two'
    c.say() // 输出 'three'
    c.method() // 输出 'four'
    

    弊端:如果存在两个类 ClassA 和 ClassB 具有同名的属性或方法,ClassB 具有高优先级。因为它从后面的类继承。除这点小问题之外,用对象冒充实现多重继承机制轻而易举。
    由于这种继承方法的流行,ECMAScript 的第三版为 Function 对象加入了两个方法,即 call() 和 apply()。

    call()

    function ClassA(name){
        this.name = name;
        this.say = function(){
            console.log(this.name)
        }
    }
    
    function ClassB(name){
        ClassA.call(this,name)  //让ClassA里面的this等于新创建的ClassB对象
    }
    
    //test
    var a = new ClassA('one')
    var b = new ClassB('two')
    a.say() //输出 'one'
    b.say() //输出 'two'
    

    apply()

    function ClassA(name){
        this.name = name;
        this.say = function(){
            console.log(this.name)
        }
    }
    
    function ClassB(name){
        ClassA.apply(this,arguments)  //把 ClassB 的整个 arguments 对象作为第二个参数传递给 apply() 方法
    }
    
    //test
    var a = new ClassA('one')
    var b = new ClassB('two')
    a.say() //输出 'one'
    b.say() //输出 'two'
    

    缺点:无法继承原型链上的属性和方法

    二、原型链(prototype chaining)继承

    原理:继承这种形式在 ECMAScript 中原本是用于原型链的,prototype 对象是个模板,要实例化的对象都以这个模板为基础,prototype 对象的任何属性和方法都被传递给那个类的所有实例。原型链利用这种功能来实现继承机制。

    与对象冒充相似,子类的所有属性和方法都必须出现在 prototype 属性被赋值后,因为在它之前赋值的所有方法都会被删除。为什么?因为 prototype 属性被替换成了新对象,添加了新方法的原始对象将被销毁。

    注意:调用 ClassA 的构造函数,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数。

    function ClassA(){ }
    
    ClassA.prototype.name = 'chuchur'
    ClassA.prototype.say = function(){
        console.log(this.name)
    }
    
    function ClassB(){}
    /**
     * 完全删除了prototype对象原先的值,然后赋予一个新的值。
     * 此时 Cat.prototype.constructor == Animal   ==>true
     */
    ClassB.prototype = new ClassA()
    
    //注意下面一行代码的意思
    /** 
     * 任何一个prototype对象都有一个constructor属性,指向它的构造函数
     * 没有ClassB.prototype = new ClassA(),那么ClassB.prototype.constructor是指向ClassB,加了之后指向ClassA
     * 更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。
     * 所以  (new ClassB()).constructor == ClassA ==>true,
     * 这显然会导致继承链的紊乱,因此我们必须手动纠正,将ClassB.prototype对象的constructor值改为ClassB,
     * 当每次改变prototype的值时,必然要复原construcotr
    */
    ClassB.prototype.constructor = ClassB
    
    ClassB.prototype.name = ''
    ClassB.prototype.method = function(){
        console.log(this.name)
    }
    
    //test
    var a = new ClassA()
    var b = new ClassB()
    a.name = 'one'
    b.name = 'two' //这里不重新复制,则name的取值为chuchur,也就是初始化值
    a.say() // 输出 one
    b.say() // 输出 two
    b.method() // 输出 two
    

    缺点:原型链的弊端是不支持多重继承。记住,原型链会用另一类型的对象重写类的 prototype 属性。

    三、混合方式继承

    这种继承方式使用构造函数定义类,并非使用任何原型。对象冒充的主要问题是必须使用构造函数方式,这不是最好的选择。不过如果使用原型链,就无法使用带参数的构造函数了。

    创建类的最好方式是 用构造函数定义属性用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 prototype 对象的方法。

    function ClassA(mail){
        this.mail = mail
    }
    ClassA.prototype.sayMail = function(){
        console.log(this.mail)
    }
    
    function ClassB(mail,name){
        ClassA.call(this,mail)
        this.name = name
    }
    ClassB.prototype = new ClassA()
    ClassB.prototype.constructor = ClassB
    ClassB.prototype.sayName = function(){
        console.log(this.name)
    }
    
    //test
    var a = new ClassA('chuchur@qq.com')
    var b = new ClassB('dev@chuchur.com','chuchur')
    a.sayMail() //输出 chuchur@qq.com
    b.sayMail() //输出 dev@chuchur.com
    b.sayName() //输出 chuchur
    
    

    ES5继承和ES6继承的区别

    class A {
        sayName(){
            console.log('chuchur')
        }
    }
    
    class B extends A {
      constructor() {
        super();
      }
    }
    

    ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。

    super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。

    注意,super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此super()在这里相当于A.prototype.constructor.call(this)。

    super还可以被当作对象使用,这时,super在普通方法之中,指向A.prototype,所以super.sayName()就相当于A.prototype.sayName()。

    class A {
        sayName(){
            return 'chuchur'
        }
    }
    
    class B extends A {
      constructor() {
        super();
        console.log(super.sayName())
      }
    }
    

    ES6 允许继承原生构造函数定义子类,可以自定义原生数据结构(比如Array、String等)的子类,这是 ES5 无法做到的。extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上,定义自己的数据结构.

    class SuperArray extends Array {
      constructor() {
        super();
        this.history = [[]];
      }
      commit() {
        this.history.push(this.slice());
      }
      revert() {
        this.splice(0, this.length, ...this.history[this.history.length - 1]);
      }
    }
    
    var x = new SuperArray();
    
    x.push(1);
    x.push(2);
    x // [1, 2]
    x.history // [[]]
    
    x.commit();
    x.history // [[], [1, 2]]
    

    类的 prototype 属性和__proto__属性

    浏览器的 ES5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

    (1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

    (2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

    class A {
    }
    
    class B extends A {
    }
    
    B.__proto__ === A // true
    B.prototype.__proto__ === A.prototype // true
    

    上面代码中,子类B的__proto__属性指向父类A,子类B的prototype属性的__proto__属性指向父类A的prototype属性。

    用图形来表示
    ES5继承

    ES6继承

    实例讲解

    请转到这篇文章:js-继承-jquery

  • 相关阅读:
    【WPF on .NET Core 3.0】 Stylet演示项目
    【WPF on .NET Core 3.0】 Stylet演示项目
    [译]ABP v1.0终于发布了!
    Centos7 安装配置 Rabbitmq Cluster
    新系统添加sshkey/pexpect基本使用
    ansible Templates
    Jenkins Sonar
    Haproxy 安装及配置
    keepalived 安装及配置
    docker etcd
  • 原文地址:https://www.cnblogs.com/chuchur/p/10462217.html
Copyright © 2011-2022 走看看