【JS核心概念】Class實現繼承


一、簡單使用

  • Class通過extends關鍵字實現繼承,其實質是先創造出父類的this對象,然后用子類的構造函數修改this
  • 子類的構造方法中必須調用super方法,且只有在調用了super()之后才能使用this,因為子類的this對象是繼承父類的this對象,然后對其進行加工,而super方法表示的是父類的構造函數,用來新建父類的this對象
    class Animal {
        constructor(kind) {
            this.kind = kind
        }
        getKind() {
            return this.kind
        }
    }
    
    // 繼承Animal
    class Cat extends Animal {
        constructor(name) {
            // 子類的構造方法中必須先調用super方法
            super('cat');
            this.name = name;
        }
        getCatInfo() {
            console.log(this.name + ':' + super.getKind())
        }
    }
    
    const cat1 = new Cat('xiaohei');
    cat1.getCatInfo(); // xiaohei:cat

二、super關鍵字

從上面的例子中發現,super關鍵字不僅可以作為函數使用,還可以作為對象使用。在這兩種情況下,super的用處也是不一樣的。

2.1 super函數

super作為函數使用時,表示父類的構造函數。

不過要注意的是,super雖然代表父類的構造函數,但是返回的是子類的實例。這里的super相當於父類.prototype.constructor.call(子類this);

    class Animal {
        constructor(kind) {
            this.kind = kind
            console.log(this)
        }
    }
    
    class Cat extends Animal {
        constructor(name) {
            super('cat');
            this.name = name;
        }
    }
    
    // 調用super()函數,相當於:
    // Animal.prototype.constrctor.call(this)
    // 最后再返回this
    const cat1 = new Cat('xiaohei'); // Cat { kind: 'cat' }

注意:super作為函數時,只能在子類的構造函數中調用,在其他地方調用會報錯。

2.2 super對象
  • 在普通方法中指向父類的原型對象
    class Animal {
        constructor(kind) {
            this.kind = kind
            console.log(this)
        }
        getKind() {
            return this.kind
        }
    }
    Animal.prototype.color = 'black'
    
    class Cat extends Animal {
        constructor(name) {
            super('cat');
            this.name = name;
        }
        getCatInfo() {
            // super在普通方法中表示的是Animal.prototype:
            // super.color相當於Animal.prototype.color
            console.log(super.color); // black
            // super.getKind()相當於Animal.prototype.getKind()
            console.log(this.name + ':' + super.getKind())
        }
    }
    
    const cat1 = new Cat('xiaohei'); // Cat { kind: 'cat' }
    cat1.getCatInfo(); // xiaohei:cat

注意:

1)由於super表示的是父類的原型,因此在父類實例上的屬性和方法都無法通過super調用

    getCatInfo() {
        // kind是Animal的實例屬性,因此無法通過super訪問
        console.log(super.kind); // undefined
        console.log(this.name + ':' + super.getKind()); // xiaohei:cat
    }

2)ES6規定,通過super調用父類的方法時,super會綁定子類的this

    class Foo {
        constructor() {
            this.num = 1;
        }
        print() {
            console.log(this.num);
        }
    }
    class Bar extends Foo {
        constructor() {
            super();
            this.num = 2;
        }
        write() {
            // super.print()相當於Foo.prototype.print.call(this)
            super.print();
        }
    }
    
    const bar = new Bar();
    bar.write(); // 2

3)通過super對某個屬性賦值,相當於在子類上添加了一個實例屬性,因此super會綁定子類的this

    class Foo {
        constructor() {
            this.num = 1;
        }
    }
    
    class Bar extends Foo {
        constructor() {
            super();
            this.num = 2;
            console.log(this.num); // 2
            // 相當於this.num = 3
            super.num = 3;
            console.log(this.num); // 3
            // 通過super.num讀取值,相當於Foo.prototype.num
            console.log(super.num); // undefined
        }
    }
    
    const bar = new Bar();
  • 在靜態方法中指向父類
    class Foo {
        static print() {
            console.log('static method');
        }
        write() {
            console.log('normal method');
        }
    }
    class Bar extends Foo {
        static print() {
            super.print()
        }
        write() {
            super.write()
        }
    }
    
    const bar = new Bar();
    Bar.print(); // static method
    bar.write(); // normal method

三、類的prototype屬性和_proto_屬性

在大多數瀏覽器的ES5實現中,每一個對象都有一個_proto_屬性,指向其構造函數的prototype屬性。Class作為構造函數的語法糖,同時有prototype屬性和_proto_屬性,因此同時存在兩條繼承鏈:

  • 子類的_proto_屬性表示構造函數的繼承,指向父類 => 在ES5中,對象的_proto_屬性指向其原型對象,在Class中的理解是一樣的,子類的_proto_屬性也是指向其原型對象,因此指向其父類。
  • 子類的prototype屬性的_proto_屬性表示方法的繼承,指向父類的prototype屬性 => 在ES5中,只有函數對象才有prototype屬性,ES6中的Class實際上可以看作是一個語法糖,Class的數據類型是函數,也具有prototype屬性,在子類繼承父類的時候,子類的prototype也繼承了父類的prototype屬性,因此子類的prototype屬性的_proto_屬性指向父類的prototype。

    用代碼表示上面的描述就是:
    class Foo {}
    class Bar {}
    
    Object.setPrototypeOf(Bar, Foo)
    Object.setPrototypeOf(Bar.prototype, Foo.prototype)
    
    // 子類Bar的_proto_屬性指向父類Foo
    console.log(Bar._proto_ === Foo) // true
    console.log(Object.getPrototypeOf(Bar) === Foo) // true
    
    // 子類Bar的prototype屬性的_proto_屬性指向父類Foo的prototype屬性
    console.log(Bar.prototype._proto_ === Foo.prototype) // true
    console.log(Object.getPrototypeOf(Bar.prototype) === Foo.prototype) // true
    class Foo {}
    
    class Bar extends Foo {}
    
    // 子類Bar的_proto_屬性指向父類Foo
    console.log(Bar._proto_ === Foo) // true
    console.log(Object.getPrototypeOf(Bar) === Foo) // true
    
    // 子類Bar的prototype屬性的_proto_屬性指向父類Foo的prototype屬性
    console.log(Bar.prototype._proto_ === Foo.prototype) // true
    console.log(Object.getPrototypeOf(Bar.prototype) === Foo.prototype) // true

  • 子類實例的_proto_屬性的_proto_屬性指向父類實例的_proto_屬性,也就是說子類的原型的原型是父類的原型
    class Foo {}
    
    class Bar extends Foo {}
    
    const foo = new Foo()
    const bar = new Bar()
    
    console.log(Object.getPrototypeOf(Object.getPrototypeOf(bar)) === Object.getPrototypeOf(foo)) // true
    
    console.log(bar._proto_._proto_ === foo._proto_) // true

因此可以通過子類的原型的原型來修改父類的原型,蜜汁操作,不建議

    // bar._proto_.proto_.print = function() {
    //   console.log('haha')
    // }
    
    Object.getPrototypeOf(Object.getPrototypeOf(bar)).print = function() {
      console.log('haha')
    }
    bar.print() // haha
    foo.print() // haha

四、extends的繼承目標

目標:含有prototype屬性的對象,因此可以是任意函數(除了Function.prototype,不要忘記了Function.prototype也是函數,typeof Function.prototype === 'function')。

  • 通過Class繼承原生構造函數
    ECMAScript中的原生構造函數大致有以下這些:
    • Boolean()
    • Number()
    • String()
    • Object()
    • Array()
    • Function()
    • Date()
    • RegExp()
    • Error()




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM